aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/ixp2000/ixpdev.h
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ixp2000/ixpdev.h')
-rw-r--r--drivers/net/ixp2000/ixpdev.h29
1 files changed, 0 insertions, 29 deletions
diff --git a/drivers/net/ixp2000/ixpdev.h b/drivers/net/ixp2000/ixpdev.h
deleted file mode 100644
index 391ece62324..00000000000
--- a/drivers/net/ixp2000/ixpdev.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * IXP2000 MSF network device driver
- * Copyright (C) 2004, 2005 Lennert Buytenhek <buytenh@wantstofly.org>
- * Dedicated to Marija Kulikova.
- *
- * This program is free software; you can redistribute 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 __IXPDEV_H
-#define __IXPDEV_H
-
-struct ixpdev_priv
-{
- struct net_device *dev;
- struct napi_struct napi;
- int channel;
- int tx_queue_entries;
-};
-
-struct net_device *ixpdev_alloc(int channel, int sizeof_priv);
-int ixpdev_init(int num_ports, struct net_device **nds,
- void (*set_port_admin_status)(int port, int up));
-void ixpdev_deinit(void);
-
-
-#endif
99.6%;'/> -rw-r--r--drivers/staging/android/ashmem.c886
-rw-r--r--drivers/staging/android/ashmem.h27
-rw-r--r--drivers/staging/android/binder.c3637
-rw-r--r--drivers/staging/android/binder.h30
-rw-r--r--drivers/staging/android/binder_trace.h329
-rw-r--r--drivers/staging/android/ion/Kconfig35
-rw-r--r--drivers/staging/android/ion/Makefile10
-rw-r--r--drivers/staging/android/ion/compat_ion.c195
-rw-r--r--drivers/staging/android/ion/compat_ion.h30
-rw-r--r--drivers/staging/android/ion/ion.c1645
-rw-r--r--drivers/staging/android/ion/ion.h204
-rw-r--r--drivers/staging/android/ion/ion_carveout_heap.c194
-rw-r--r--drivers/staging/android/ion/ion_chunk_heap.c195
-rw-r--r--drivers/staging/android/ion/ion_cma_heap.c218
-rw-r--r--drivers/staging/android/ion/ion_dummy_driver.c158
-rw-r--r--drivers/staging/android/ion/ion_heap.c383
-rw-r--r--drivers/staging/android/ion/ion_page_pool.c182
-rw-r--r--drivers/staging/android/ion/ion_priv.h405
-rw-r--r--drivers/staging/android/ion/ion_system_heap.c446
-rw-r--r--drivers/staging/android/ion/ion_test.c282
-rw-r--r--drivers/staging/android/ion/tegra/Makefile1
-rw-r--r--drivers/staging/android/ion/tegra/tegra_ion.c82
-rw-r--r--drivers/staging/android/logger.c854
-rw-r--r--drivers/staging/android/logger.h89
-rw-r--r--drivers/staging/android/lowmemorykiller.c202
-rw-r--r--drivers/staging/android/sw_sync.c268
-rw-r--r--drivers/staging/android/sw_sync.h59
-rw-r--r--drivers/staging/android/sync.c1029
-rw-r--r--drivers/staging/android/sync.h344
-rw-r--r--drivers/staging/android/timed_gpio.c167
-rw-r--r--drivers/staging/android/timed_gpio.h33
-rw-r--r--drivers/staging/android/timed_output.c118
-rw-r--r--drivers/staging/android/timed_output.h37
-rw-r--r--drivers/staging/android/trace/sync.h82
-rw-r--r--drivers/staging/android/uapi/android_alarm.h62
-rw-r--r--drivers/staging/android/uapi/ashmem.h47
-rw-r--r--drivers/staging/android/uapi/binder.h351
-rw-r--r--drivers/staging/android/uapi/ion.h196
-rw-r--r--drivers/staging/android/uapi/ion_test.h70
-rw-r--r--drivers/staging/android/uapi/sw_sync.h32
-rw-r--r--drivers/staging/android/uapi/sync.h97
-rw-r--r--drivers/staging/bcm/Adapter.h457
-rw-r--r--drivers/staging/bcm/Bcmchar.c2652
-rw-r--r--drivers/staging/bcm/Bcmnet.c241
-rw-r--r--drivers/staging/bcm/CmHost.c2253
-rw-r--r--drivers/staging/bcm/CmHost.h62
-rw-r--r--drivers/staging/bcm/DDRInit.c1239
-rw-r--r--drivers/staging/bcm/DDRInit.h9
-rw-r--r--drivers/staging/bcm/Debug.h242
-rw-r--r--drivers/staging/bcm/HandleControlPacket.c235
-rw-r--r--drivers/staging/bcm/HostMIBSInterface.h192
-rw-r--r--drivers/staging/bcm/IPv6Protocol.c451
-rw-r--r--drivers/staging/bcm/IPv6ProtocolHdr.h85
-rw-r--r--drivers/staging/bcm/InterfaceAdapter.h79
-rw-r--r--drivers/staging/bcm/InterfaceDld.c316
-rw-r--r--drivers/staging/bcm/InterfaceIdleMode.c275
-rw-r--r--drivers/staging/bcm/InterfaceIdleMode.h15
-rw-r--r--drivers/staging/bcm/InterfaceInit.c717
-rw-r--r--drivers/staging/bcm/InterfaceInit.h26
-rw-r--r--drivers/staging/bcm/InterfaceIsr.c190
-rw-r--r--drivers/staging/bcm/InterfaceIsr.h15
-rw-r--r--drivers/staging/bcm/InterfaceMacros.h18
-rw-r--r--drivers/staging/bcm/InterfaceMisc.c217
-rw-r--r--drivers/staging/bcm/InterfaceMisc.h42
-rw-r--r--drivers/staging/bcm/InterfaceRx.c233
-rw-r--r--drivers/staging/bcm/InterfaceRx.h7
-rw-r--r--drivers/staging/bcm/InterfaceTx.c174
-rw-r--r--drivers/staging/bcm/InterfaceTx.h7
-rw-r--r--drivers/staging/bcm/Ioctl.h226
-rw-r--r--drivers/staging/bcm/Kconfig6
-rw-r--r--drivers/staging/bcm/LeakyBucket.c317
-rw-r--r--drivers/staging/bcm/Macros.h352
-rw-r--r--drivers/staging/bcm/Makefile12
-rw-r--r--drivers/staging/bcm/Misc.c1580
-rw-r--r--drivers/staging/bcm/PHSDefines.h94
-rw-r--r--drivers/staging/bcm/PHSModule.c1448
-rw-r--r--drivers/staging/bcm/PHSModule.h50
-rw-r--r--drivers/staging/bcm/Protocol.h128
-rw-r--r--drivers/staging/bcm/Prototypes.h222
-rw-r--r--drivers/staging/bcm/Qos.c777
-rw-r--r--drivers/staging/bcm/Queue.h31
-rw-r--r--drivers/staging/bcm/TODO22
-rw-r--r--drivers/staging/bcm/Transmit.c245
-rw-r--r--drivers/staging/bcm/Typedefs.h47
-rw-r--r--drivers/staging/bcm/cntrl_SignalingInterface.h311
-rw-r--r--drivers/staging/bcm/headers.h78
-rw-r--r--drivers/staging/bcm/hostmibs.c150
-rw-r--r--drivers/staging/bcm/led_control.c916
-rw-r--r--drivers/staging/bcm/led_control.h84
-rw-r--r--drivers/staging/bcm/nvm.c4613
-rw-r--r--drivers/staging/bcm/nvm.h286
-rw-r--r--drivers/staging/bcm/sort.c52
-rw-r--r--drivers/staging/bcm/target_params.h57
-rw-r--r--drivers/staging/bcm/vendorspecificextn.c142
-rw-r--r--drivers/staging/bcm/vendorspecificextn.h18
-rw-r--r--drivers/staging/ced1401/Kconfig6
-rw-r--r--drivers/staging/ced1401/Makefile3
-rw-r--r--drivers/staging/ced1401/TODO10
-rw-r--r--drivers/staging/ced1401/ced_ioc.c1494
-rw-r--r--drivers/staging/ced1401/ced_ioctl.h336
-rw-r--r--drivers/staging/ced1401/machine.h119
-rw-r--r--drivers/staging/ced1401/usb1401.c1582
-rw-r--r--drivers/staging/ced1401/usb1401.h246
-rw-r--r--drivers/staging/ced1401/use1401.h288
-rw-r--r--drivers/staging/ced1401/use14_ioc.h299
-rw-r--r--drivers/staging/ced1401/userspace/use1401.c3035
-rw-r--r--drivers/staging/comedi/Kconfig1293
-rw-r--r--drivers/staging/comedi/Makefile14
-rw-r--r--drivers/staging/comedi/TODO11
-rw-r--r--drivers/staging/comedi/comedi.h979
-rw-r--r--drivers/staging/comedi/comedi_buf.c511
-rw-r--r--drivers/staging/comedi/comedi_compat32.c452
-rw-r--r--drivers/staging/comedi/comedi_compat32.h37
-rw-r--r--drivers/staging/comedi/comedi_fops.c2661
-rw-r--r--drivers/staging/comedi/comedi_internal.h56
-rw-r--r--drivers/staging/comedi/comedi_pci.c146
-rw-r--r--drivers/staging/comedi/comedi_pcmcia.c156
-rw-r--r--drivers/staging/comedi/comedi_usb.c116
-rw-r--r--drivers/staging/comedi/comedidev.h540
-rw-r--r--drivers/staging/comedi/comedilib.h35
-rw-r--r--drivers/staging/comedi/drivers.c726
-rw-r--r--drivers/staging/comedi/drivers/8253.h345
-rw-r--r--drivers/staging/comedi/drivers/8255.c380
-rw-r--r--drivers/staging/comedi/drivers/8255.h33
-rw-r--r--drivers/staging/comedi/drivers/8255_pci.c329
-rw-r--r--drivers/staging/comedi/drivers/Makefile142
-rw-r--r--drivers/staging/comedi/drivers/addi-data/addi_common.c284
-rw-r--r--drivers/staging/comedi/drivers/addi-data/addi_common.h165
-rw-r--r--drivers/staging/comedi/drivers/addi-data/addi_eeprom.c360
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c485
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c2413
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c551
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c2190
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c3165
-rw-r--r--drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c174
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_035.c77
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1032.c384
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1500.c76
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1516.c227
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_1564.c192
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_16xx.c188
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_2032.c378
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_2200.c153
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3120.c250
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3200.c125
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3501.c447
-rw-r--r--drivers/staging/comedi/drivers/addi_apci_3xxx.c982
-rw-r--r--drivers/staging/comedi/drivers/addi_watchdog.c161
-rw-r--r--drivers/staging/comedi/drivers/addi_watchdog.h9
-rw-r--r--drivers/staging/comedi/drivers/adl_pci6208.c260
-rw-r--r--drivers/staging/comedi/drivers/adl_pci7x3x.c280
-rw-r--r--drivers/staging/comedi/drivers/adl_pci8164.c164
-rw-r--r--drivers/staging/comedi/drivers/adl_pci9111.c903
-rw-r--r--drivers/staging/comedi/drivers/adl_pci9118.c2090
-rw-r--r--drivers/staging/comedi/drivers/adq12b.c293
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1710.c1338
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1723.c323
-rw-r--r--drivers/staging/comedi/drivers/adv_pci1724.c401
-rw-r--r--drivers/staging/comedi/drivers/adv_pci_dio.c1222
-rw-r--r--drivers/staging/comedi/drivers/aio_aio12_8.c272
-rw-r--r--drivers/staging/comedi/drivers/aio_iiro_16.c114
-rw-r--r--drivers/staging/comedi/drivers/amcc_s5933.h172
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200.c305
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200.h90
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200_common.c1223
-rw-r--r--drivers/staging/comedi/drivers/amplc_dio200_pci.c480
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc236.c572
-rw-r--r--drivers/staging/comedi/drivers/amplc_pc263.c114
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci224.c1372
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci230.c2825
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci263.c120
-rw-r--r--drivers/staging/comedi/drivers/c6xdigio.c307
-rw-r--r--drivers/staging/comedi/drivers/cb_das16_cs.c372
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidas.c1652
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidas64.c4119
-rw-r--r--drivers/staging/comedi/drivers/cb_pcidda.c429
-rw-r--r--drivers/staging/comedi/drivers/cb_pcimdas.c302
-rw-r--r--drivers/staging/comedi/drivers/cb_pcimdda.c225
-rw-r--r--drivers/staging/comedi/drivers/comedi_bond.c358
-rw-r--r--drivers/staging/comedi/drivers/comedi_fc.c132
-rw-r--r--drivers/staging/comedi/drivers/comedi_fc.h127
-rw-r--r--drivers/staging/comedi/drivers/comedi_parport.c320
-rw-r--r--drivers/staging/comedi/drivers/comedi_test.c450
-rw-r--r--drivers/staging/comedi/drivers/contec_pci_dio.c128
-rw-r--r--drivers/staging/comedi/drivers/dac02.c172
-rw-r--r--drivers/staging/comedi/drivers/daqboard2000.c815
-rw-r--r--drivers/staging/comedi/drivers/das08.c580
-rw-r--r--drivers/staging/comedi/drivers/das08.h52
-rw-r--r--drivers/staging/comedi/drivers/das08_cs.c117
-rw-r--r--drivers/staging/comedi/drivers/das08_isa.c205
-rw-r--r--drivers/staging/comedi/drivers/das08_pci.c108
-rw-r--r--drivers/staging/comedi/drivers/das16.c1259
-rw-r--r--drivers/staging/comedi/drivers/das16m1.c648
-rw-r--r--drivers/staging/comedi/drivers/das1800.c1608
-rw-r--r--drivers/staging/comedi/drivers/das6402.c547
-rw-r--r--drivers/staging/comedi/drivers/das800.c771
-rw-r--r--drivers/staging/comedi/drivers/dmm32at.c806
-rw-r--r--drivers/staging/comedi/drivers/dt2801.c654
-rw-r--r--drivers/staging/comedi/drivers/dt2811.c490
-rw-r--r--drivers/staging/comedi/drivers/dt2814.c304
-rw-r--r--drivers/staging/comedi/drivers/dt2815.c225
-rw-r--r--drivers/staging/comedi/drivers/dt2817.c151
-rw-r--r--drivers/staging/comedi/drivers/dt282x.c1384
-rw-r--r--drivers/staging/comedi/drivers/dt3000.c814
-rw-r--r--drivers/staging/comedi/drivers/dt9812.c880
-rw-r--r--drivers/staging/comedi/drivers/dyna_pci10xx.c281
-rw-r--r--drivers/staging/comedi/drivers/fl512.c178
-rw-r--r--drivers/staging/comedi/drivers/gsc_hpdi.c751
-rw-r--r--drivers/staging/comedi/drivers/icp_multi.c612
-rw-r--r--drivers/staging/comedi/drivers/ii_pci20kc.c534
-rw-r--r--drivers/staging/comedi/drivers/jr3_pci.c830
-rw-r--r--drivers/staging/comedi/drivers/jr3_pci.h681
-rw-r--r--drivers/staging/comedi/drivers/ke_counter.c181
-rw-r--r--drivers/staging/comedi/drivers/me4000.c1615
-rw-r--r--drivers/staging/comedi/drivers/me_daq.c603
-rw-r--r--drivers/staging/comedi/drivers/mf6x4.c351
-rw-r--r--drivers/staging/comedi/drivers/mite.c643
-rw-r--r--drivers/staging/comedi/drivers/mite.h430
-rw-r--r--drivers/staging/comedi/drivers/mpc624.c361
-rw-r--r--drivers/staging/comedi/drivers/multiq3.c310
-rw-r--r--drivers/staging/comedi/drivers/ni_6527.c476
-rw-r--r--drivers/staging/comedi/drivers/ni_65xx.c778
-rw-r--r--drivers/staging/comedi/drivers/ni_660x.c1240
-rw-r--r--drivers/staging/comedi/drivers/ni_670x.c300
-rw-r--r--drivers/staging/comedi/drivers/ni_at_a2150.c821
-rw-r--r--drivers/staging/comedi/drivers/ni_at_ao.c409
-rw-r--r--drivers/staging/comedi/drivers/ni_atmio.c494
-rw-r--r--drivers/staging/comedi/drivers/ni_atmio16d.c781
-rw-r--r--drivers/staging/comedi/drivers/ni_daq_700.c267
-rw-r--r--drivers/staging/comedi/drivers/ni_daq_dio24.c99
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc.c1515
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc.h90
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_cs.c135
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_isadma.c227
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_isadma.h57
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_pci.c133
-rw-r--r--drivers/staging/comedi/drivers/ni_labpc_regs.h75
-rw-r--r--drivers/staging/comedi/drivers/ni_mio_common.c5740
-rw-r--r--drivers/staging/comedi/drivers/ni_mio_cs.c310
-rw-r--r--drivers/staging/comedi/drivers/ni_pcidio.c1088
-rw-r--r--drivers/staging/comedi/drivers/ni_pcimio.c1706
-rw-r--r--drivers/staging/comedi/drivers/ni_stc.h1507
-rw-r--r--drivers/staging/comedi/drivers/ni_tio.c1698
-rw-r--r--drivers/staging/comedi/drivers/ni_tio.h156
-rw-r--r--drivers/staging/comedi/drivers/ni_tio_internal.h404
-rw-r--r--drivers/staging/comedi/drivers/ni_tiocmd.c496
-rw-r--r--drivers/staging/comedi/drivers/pcl711.c570
-rw-r--r--drivers/staging/comedi/drivers/pcl724.c155
-rw-r--r--drivers/staging/comedi/drivers/pcl726.c455
-rw-r--r--drivers/staging/comedi/drivers/pcl730.c337
-rw-r--r--drivers/staging/comedi/drivers/pcl812.c1437
-rw-r--r--drivers/staging/comedi/drivers/pcl816.c811
-rw-r--r--drivers/staging/comedi/drivers/pcl818.c1258
-rw-r--r--drivers/staging/comedi/drivers/pcm3724.c247
-rw-r--r--drivers/staging/comedi/drivers/pcmad.c159
-rw-r--r--drivers/staging/comedi/drivers/pcmda12.c176
-rw-r--r--drivers/staging/comedi/drivers/pcmmio.c849
-rw-r--r--drivers/staging/comedi/drivers/pcmuio.c690
-rw-r--r--drivers/staging/comedi/drivers/plx9052.h79
-rw-r--r--drivers/staging/comedi/drivers/plx9080.h422
-rw-r--r--drivers/staging/comedi/drivers/quatech_daqp_cs.c816
-rw-r--r--drivers/staging/comedi/drivers/rtd520.c1426
-rw-r--r--drivers/staging/comedi/drivers/rti800.c378
-rw-r--r--drivers/staging/comedi/drivers/rti802.c143
-rw-r--r--drivers/staging/comedi/drivers/s526.c628
-rw-r--r--drivers/staging/comedi/drivers/s626.c3110
-rw-r--r--drivers/staging/comedi/drivers/s626.h774
-rw-r--r--drivers/staging/comedi/drivers/serial2002.c807
-rw-r--r--drivers/staging/comedi/drivers/skel.c729
-rw-r--r--drivers/staging/comedi/drivers/ssv_dnp.c187
-rw-r--r--drivers/staging/comedi/drivers/unioxx5.c508
-rw-r--r--drivers/staging/comedi/drivers/usbdux.c1822
-rw-r--r--drivers/staging/comedi/drivers/usbduxfast.c1139
-rw-r--r--drivers/staging/comedi/drivers/usbduxsigma.c1715
-rw-r--r--drivers/staging/comedi/drivers/vmk80xx.c963
-rw-r--r--drivers/staging/comedi/kcomedilib/Makefile5
-rw-r--r--drivers/staging/comedi/kcomedilib/kcomedilib_main.c252
-rw-r--r--drivers/staging/comedi/proc.c97
-rw-r--r--drivers/staging/comedi/range.c166
-rw-r--r--drivers/staging/cptm1217/Kconfig12
-rw-r--r--drivers/staging/cptm1217/Makefile2
-rw-r--r--drivers/staging/cptm1217/TODO5
-rw-r--r--drivers/staging/cptm1217/clearpad_tm1217.c666
-rw-r--r--drivers/staging/cptm1217/cp_tm1217.h8
-rw-r--r--drivers/staging/crystalhd/Kconfig6
-rw-r--r--drivers/staging/crystalhd/Makefile6
-rw-r--r--drivers/staging/crystalhd/TODO15
-rw-r--r--drivers/staging/crystalhd/bc_dts_defs.h572
-rw-r--r--drivers/staging/crystalhd/bc_dts_glob_lnx.h300
-rw-r--r--drivers/staging/crystalhd/bcm_70012_regs.h758
-rw-r--r--drivers/staging/crystalhd/crystalhd.h13
-rw-r--r--drivers/staging/crystalhd/crystalhd_cmds.c1066
-rw-r--r--drivers/staging/crystalhd/crystalhd_cmds.h92
-rw-r--r--drivers/staging/crystalhd/crystalhd_fw_if.h370
-rw-r--r--drivers/staging/crystalhd/crystalhd_hw.c2458
-rw-r--r--drivers/staging/crystalhd/crystalhd_hw.h407
-rw-r--r--drivers/staging/crystalhd/crystalhd_lnx.c782
-rw-r--r--drivers/staging/crystalhd/crystalhd_lnx.h93
-rw-r--r--drivers/staging/crystalhd/crystalhd_misc.c1044
-rw-r--r--drivers/staging/crystalhd/crystalhd_misc.h232
-rw-r--r--drivers/staging/cxt1e1/Kconfig21
-rw-r--r--drivers/staging/cxt1e1/Makefile18
-rw-r--r--drivers/staging/cxt1e1/comet.c596
-rw-r--r--drivers/staging/cxt1e1/comet.h353
-rw-r--r--drivers/staging/cxt1e1/comet_tables.c538
-rw-r--r--drivers/staging/cxt1e1/comet_tables.h61
-rw-r--r--drivers/staging/cxt1e1/functions.c318
-rw-r--r--drivers/staging/cxt1e1/hwprobe.c382
-rw-r--r--drivers/staging/cxt1e1/libsbew.h549
-rw-r--r--drivers/staging/cxt1e1/linux.c1142
-rw-r--r--drivers/staging/cxt1e1/musycc.c1720
-rw-r--r--drivers/staging/cxt1e1/musycc.h427
-rw-r--r--drivers/staging/cxt1e1/pmc93x6_eeprom.c532
-rw-r--r--drivers/staging/cxt1e1/pmc93x6_eeprom.h39
-rw-r--r--drivers/staging/cxt1e1/pmcc4.h108
-rw-r--r--drivers/staging/cxt1e1/pmcc4_cpld.h91
-rw-r--r--drivers/staging/cxt1e1/pmcc4_defs.h68
-rw-r--r--drivers/staging/cxt1e1/pmcc4_drv.c1613
-rw-r--r--drivers/staging/cxt1e1/pmcc4_ioctls.h65
-rw-r--r--drivers/staging/cxt1e1/pmcc4_private.h295
-rw-r--r--drivers/staging/cxt1e1/pmcc4_sysdep.h63
-rw-r--r--drivers/staging/cxt1e1/sbe_bid.h47
-rw-r--r--drivers/staging/cxt1e1/sbe_promformat.h130
-rw-r--r--drivers/staging/cxt1e1/sbecom_inline_linux.h193
-rw-r--r--drivers/staging/cxt1e1/sbecrc.c133
-rw-r--r--drivers/staging/cxt1e1/sbeid.c205
-rw-r--r--drivers/staging/cxt1e1/sbeproc.c219
-rw-r--r--drivers/staging/cxt1e1/sbeproc.h42
-rw-r--r--drivers/staging/cxt1e1/sbew_ioc.h81
-rw-r--r--drivers/staging/dgap/Kconfig6
-rw-r--r--drivers/staging/dgap/Makefile1
-rw-r--r--drivers/staging/dgap/dgap.c7499
-rw-r--r--drivers/staging/dgap/dgap.h1249
-rw-r--r--drivers/staging/dgnc/Kconfig6
-rw-r--r--drivers/staging/dgnc/Makefile7
-rw-r--r--drivers/staging/dgnc/TODO17
-rw-r--r--drivers/staging/dgnc/dgnc_cls.c1434
-rw-r--r--drivers/staging/dgnc/dgnc_cls.h89
-rw-r--r--drivers/staging/dgnc/dgnc_driver.c934
-rw-r--r--drivers/staging/dgnc/dgnc_driver.h563
-rw-r--r--drivers/staging/dgnc/dgnc_kcompat.h64
-rw-r--r--drivers/staging/dgnc/dgnc_mgmt.c304
-rw-r--r--drivers/staging/dgnc/dgnc_mgmt.h31
-rw-r--r--drivers/staging/dgnc/dgnc_neo.c1942
-rw-r--r--drivers/staging/dgnc/dgnc_neo.h157
-rw-r--r--drivers/staging/dgnc/dgnc_pci.h75
-rw-r--r--drivers/staging/dgnc/dgnc_sysfs.c756
-rw-r--r--drivers/staging/dgnc/dgnc_sysfs.h49
-rw-r--r--drivers/staging/dgnc/dgnc_trace.c185
-rw-r--r--drivers/staging/dgnc/dgnc_trace.h44
-rw-r--r--drivers/staging/dgnc/dgnc_tty.c3446
-rw-r--r--drivers/staging/dgnc/dgnc_tty.h42
-rw-r--r--drivers/staging/dgnc/dgnc_types.h36
-rw-r--r--drivers/staging/dgnc/digi.h416
-rw-r--r--drivers/staging/dgnc/dpacompat.h115
-rw-r--r--drivers/staging/dgrp/Kconfig9
-rw-r--r--drivers/staging/dgrp/Makefile12
-rw-r--r--drivers/staging/dgrp/README2
-rw-r--r--drivers/staging/dgrp/TODO13
-rw-r--r--drivers/staging/dgrp/dgrp_common.c169
-rw-r--r--drivers/staging/dgrp/dgrp_common.h150
-rw-r--r--drivers/staging/dgrp/dgrp_dpa_ops.c534
-rw-r--r--drivers/staging/dgrp/dgrp_driver.c105
-rw-r--r--drivers/staging/dgrp/dgrp_mon_ops.c327
-rw-r--r--drivers/staging/dgrp/dgrp_net_ops.c3666
-rw-r--r--drivers/staging/dgrp/dgrp_ports_ops.c156
-rw-r--r--drivers/staging/dgrp/dgrp_specproc.c541
-rw-r--r--drivers/staging/dgrp/dgrp_sysfs.c558
-rw-r--r--drivers/staging/dgrp/dgrp_tty.c3337
-rw-r--r--drivers/staging/dgrp/digirp.h129
-rw-r--r--drivers/staging/dgrp/drp.h693
-rw-r--r--drivers/staging/et131x/Kconfig10
-rw-r--r--drivers/staging/et131x/Makefile5
-rw-r--r--drivers/staging/et131x/README20
-rw-r--r--drivers/staging/et131x/et131x.c4751
-rw-r--r--drivers/staging/et131x/et131x.h1670
-rw-r--r--drivers/staging/frontier/Kconfig5
-rw-r--r--drivers/staging/frontier/Makefile2
-rw-r--r--drivers/staging/frontier/README47
-rw-r--r--drivers/staging/frontier/TODO9
-rw-r--r--drivers/staging/frontier/alphatrack.c849
-rw-r--r--drivers/staging/frontier/alphatrack.h78
-rw-r--r--drivers/staging/frontier/tranzport.c973
-rw-r--r--drivers/staging/ft1000/Kconfig22
-rw-r--r--drivers/staging/ft1000/Makefile3
-rw-r--r--drivers/staging/ft1000/TODO9
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/Makefile3
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/boot.h158
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000.h73
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000.imgbin0 -> 305770 bytes-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c169
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c774
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c2266
-rw-r--r--drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c211
-rw-r--r--drivers/staging/ft1000/ft1000-usb/Makefile3
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_debug.c792
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_download.c1074
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_hw.c1604
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h123
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_proc.c241
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_usb.c268
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft1000_usb.h155
-rw-r--r--drivers/staging/ft1000/ft1000-usb/ft3000.imgbin0 -> 280414 bytes-rw-r--r--drivers/staging/ft1000/ft1000.h368
-rw-r--r--drivers/staging/fwserial/Kconfig31
-rw-r--r--drivers/staging/fwserial/Makefile2
-rw-r--r--drivers/staging/fwserial/TODO14
-rw-r--r--drivers/staging/fwserial/dma_fifo.c303
-rw-r--r--drivers/staging/fwserial/dma_fifo.h126
-rw-r--r--drivers/staging/fwserial/fwserial.c2955
-rw-r--r--drivers/staging/fwserial/fwserial.h369
-rw-r--r--drivers/staging/gdm724x/Kconfig15
-rw-r--r--drivers/staging/gdm724x/Makefile7
-rw-r--r--drivers/staging/gdm724x/TODO16
-rw-r--r--drivers/staging/gdm724x/gdm_endian.c67
-rw-r--r--drivers/staging/gdm724x/gdm_endian.h49
-rw-r--r--drivers/staging/gdm724x/gdm_lte.c944
-rw-r--r--drivers/staging/gdm724x/gdm_lte.h81
-rw-r--r--drivers/staging/gdm724x/gdm_mux.c692
-rw-r--r--drivers/staging/gdm724x/gdm_mux.h95
-rw-r--r--drivers/staging/gdm724x/gdm_tty.c346
-rw-r--r--drivers/staging/gdm724x/gdm_tty.h71
-rw-r--r--drivers/staging/gdm724x/gdm_usb.c1041
-rw-r--r--drivers/staging/gdm724x/gdm_usb.h109
-rw-r--r--drivers/staging/gdm724x/hci.h55
-rw-r--r--drivers/staging/gdm724x/hci_packet.h93
-rw-r--r--drivers/staging/gdm724x/netlink_k.c150
-rw-r--r--drivers/staging/gdm724x/netlink_k.h25
-rw-r--r--drivers/staging/gdm72xx/Kconfig46
-rw-r--r--drivers/staging/gdm72xx/Makefile6
-rw-r--r--drivers/staging/gdm72xx/TODO2
-rw-r--r--drivers/staging/gdm72xx/gdm_qos.c442
-rw-r--r--drivers/staging/gdm72xx/gdm_qos.h76
-rw-r--r--drivers/staging/gdm72xx/gdm_sdio.c708
-rw-r--r--drivers/staging/gdm72xx/gdm_sdio.h63
-rw-r--r--drivers/staging/gdm72xx/gdm_usb.c798
-rw-r--r--drivers/staging/gdm72xx/gdm_usb.h78
-rw-r--r--drivers/staging/gdm72xx/gdm_wimax.c962
-rw-r--r--drivers/staging/gdm72xx/gdm_wimax.h60
-rw-r--r--drivers/staging/gdm72xx/hci.h209
-rw-r--r--drivers/staging/gdm72xx/netlink_k.c158
-rw-r--r--drivers/staging/gdm72xx/netlink_k.h24
-rw-r--r--drivers/staging/gdm72xx/sdio_boot.c158
-rw-r--r--drivers/staging/gdm72xx/sdio_boot.h21
-rw-r--r--drivers/staging/gdm72xx/usb_boot.c359
-rw-r--r--drivers/staging/gdm72xx/usb_boot.h22
-rw-r--r--drivers/staging/gdm72xx/usb_ids.h82
-rw-r--r--drivers/staging/gdm72xx/wm_ioctl.h95
-rw-r--r--drivers/staging/goldfish/Kconfig13
-rw-r--r--drivers/staging/goldfish/Makefile6
-rw-r--r--drivers/staging/goldfish/README11
-rw-r--r--drivers/staging/goldfish/goldfish_audio.c384
-rw-r--r--drivers/staging/goldfish/goldfish_nand.c440
-rw-r--r--drivers/staging/goldfish/goldfish_nand_reg.h76
-rw-r--r--drivers/staging/gs_fpgaboot/Kconfig8
-rw-r--r--drivers/staging/gs_fpgaboot/Makefile2
-rw-r--r--drivers/staging/gs_fpgaboot/README71
-rw-r--r--drivers/staging/gs_fpgaboot/TODO7
-rw-r--r--drivers/staging/gs_fpgaboot/gs_fpgaboot.c421
-rw-r--r--drivers/staging/gs_fpgaboot/gs_fpgaboot.h56
-rw-r--r--drivers/staging/gs_fpgaboot/io.c300
-rw-r--r--drivers/staging/gs_fpgaboot/io.h90
-rw-r--r--drivers/staging/iio/Documentation/dac/max51741
-rw-r--r--drivers/staging/iio/Documentation/device.txt79
-rw-r--r--drivers/staging/iio/Documentation/generic_buffer.c340
-rw-r--r--drivers/staging/iio/Documentation/iio_event_monitor.c255
-rw-r--r--drivers/staging/iio/Documentation/iio_utils.h676
-rw-r--r--drivers/staging/iio/Documentation/inkernel.txt58
-rw-r--r--drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl25836
-rw-r--r--drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x13
-rw-r--r--drivers/staging/iio/Documentation/lsiio.c157
-rw-r--r--drivers/staging/iio/Documentation/overview.txt57
-rw-r--r--drivers/staging/iio/Documentation/ring.txt47
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-ad719220
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a21
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-dds96
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad593330
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-light107
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl258320
-rw-r--r--drivers/staging/iio/Documentation/sysfs-bus-iio-trigger-sysfs11
-rw-r--r--drivers/staging/iio/Documentation/trigger.txt35
-rw-r--r--drivers/staging/iio/Kconfig47
-rw-r--r--drivers/staging/iio/Makefile23
-rw-r--r--drivers/staging/iio/TODO84
-rw-r--r--drivers/staging/iio/accel/Kconfig78
-rw-r--r--drivers/staging/iio/accel/Makefile28
-rw-r--r--drivers/staging/iio/accel/adis16201.h66
-rw-r--r--drivers/staging/iio/accel/adis16201_core.c247
-rw-r--r--drivers/staging/iio/accel/adis16203.h59
-rw-r--r--drivers/staging/iio/accel/adis16203_core.c213
-rw-r--r--drivers/staging/iio/accel/adis16204.h68
-rw-r--r--drivers/staging/iio/accel/adis16204_core.c251
-rw-r--r--drivers/staging/iio/accel/adis16209.h105
-rw-r--r--drivers/staging/iio/accel/adis16209_core.c246
-rw-r--r--drivers/staging/iio/accel/adis16220.h140
-rw-r--r--drivers/staging/iio/accel/adis16220_core.c500
-rw-r--r--drivers/staging/iio/accel/adis16240.h129
-rw-r--r--drivers/staging/iio/accel/adis16240_core.c298
-rw-r--r--drivers/staging/iio/accel/lis3l02dq.h210
-rw-r--r--drivers/staging/iio/accel/lis3l02dq_core.c822
-rw-r--r--drivers/staging/iio/accel/lis3l02dq_ring.c422
-rw-r--r--drivers/staging/iio/accel/sca3000.h275
-rw-r--r--drivers/staging/iio/accel/sca3000_core.c1248
-rw-r--r--drivers/staging/iio/accel/sca3000_ring.c365
-rw-r--r--drivers/staging/iio/adc/Kconfig117
-rw-r--r--drivers/staging/iio/adc/Makefile18
-rw-r--r--drivers/staging/iio/adc/ad7192.c719
-rw-r--r--drivers/staging/iio/adc/ad7192.h47
-rw-r--r--drivers/staging/iio/adc/ad7280a.c984
-rw-r--r--drivers/staging/iio/adc/ad7280a.h38
-rw-r--r--drivers/staging/iio/adc/ad7291.c580
-rw-r--r--drivers/staging/iio/adc/ad7291.h12
-rw-r--r--drivers/staging/iio/adc/ad7606.h104
-rw-r--r--drivers/staging/iio/adc/ad7606_core.c603
-rw-r--r--drivers/staging/iio/adc/ad7606_par.c178
-rw-r--r--drivers/staging/iio/adc/ad7606_ring.c101
-rw-r--r--drivers/staging/iio/adc/ad7606_spi.c116
-rw-r--r--drivers/staging/iio/adc/ad7780.c276
-rw-r--r--drivers/staging/iio/adc/ad7780.h30
-rw-r--r--drivers/staging/iio/adc/ad7816.c447
-rw-r--r--drivers/staging/iio/adc/lpc32xx_adc.c216
-rw-r--r--drivers/staging/iio/adc/mxs-lradc.c1685
-rw-r--r--drivers/staging/iio/adc/spear_adc.c401
-rw-r--r--drivers/staging/iio/addac/Kconfig28
-rw-r--r--drivers/staging/iio/addac/Makefile7
-rw-r--r--drivers/staging/iio/addac/adt7316-i2c.c136
-rw-r--r--drivers/staging/iio/addac/adt7316-spi.c144
-rw-r--r--drivers/staging/iio/addac/adt7316.c2190
-rw-r--r--drivers/staging/iio/addac/adt7316.h35
-rw-r--r--drivers/staging/iio/cdc/Kconfig36
-rw-r--r--drivers/staging/iio/cdc/Makefile7
-rw-r--r--drivers/staging/iio/cdc/ad7150.c683
-rw-r--r--drivers/staging/iio/cdc/ad7152.c543
-rw-r--r--drivers/staging/iio/cdc/ad7746.c791
-rw-r--r--drivers/staging/iio/cdc/ad7746.h29
-rw-r--r--drivers/staging/iio/frequency/Kconfig61
-rw-r--r--drivers/staging/iio/frequency/Makefile11
-rw-r--r--drivers/staging/iio/frequency/ad5930.c140
-rw-r--r--drivers/staging/iio/frequency/ad9832.c354
-rw-r--r--drivers/staging/iio/frequency/ad9832.h126
-rw-r--r--drivers/staging/iio/frequency/ad9834.c457
-rw-r--r--drivers/staging/iio/frequency/ad9834.h112
-rw-r--r--drivers/staging/iio/frequency/ad9850.c126
-rw-r--r--drivers/staging/iio/frequency/ad9852.c253
-rw-r--r--drivers/staging/iio/frequency/ad9910.c371
-rw-r--r--drivers/staging/iio/frequency/ad9951.c209
-rw-r--r--drivers/staging/iio/frequency/dds.h110
-rw-r--r--drivers/staging/iio/gyro/Kconfig13
-rw-r--r--drivers/staging/iio/gyro/Makefile6
-rw-r--r--drivers/staging/iio/gyro/adis16060_core.c245
-rw-r--r--drivers/staging/iio/iio_dummy_evgen.c221
-rw-r--r--drivers/staging/iio/iio_dummy_evgen.h2
-rw-r--r--drivers/staging/iio/iio_simple_dummy.c588
-rw-r--r--drivers/staging/iio/iio_simple_dummy.h120
-rw-r--r--drivers/staging/iio/iio_simple_dummy_buffer.c199
-rw-r--r--drivers/staging/iio/iio_simple_dummy_events.c210
-rw-r--r--drivers/staging/iio/impedance-analyzer/Kconfig18
-rw-r--r--drivers/staging/iio/impedance-analyzer/Makefile5
-rw-r--r--drivers/staging/iio/impedance-analyzer/ad5933.c821
-rw-r--r--drivers/staging/iio/impedance-analyzer/ad5933.h28
-rw-r--r--drivers/staging/iio/light/Kconfig43
-rw-r--r--drivers/staging/iio/light/Makefile8
-rw-r--r--drivers/staging/iio/light/isl29018.c662
-rw-r--r--drivers/staging/iio/light/isl29028.c561
-rw-r--r--drivers/staging/iio/light/tsl2583.c950
-rw-r--r--drivers/staging/iio/light/tsl2x7x.h100
-rw-r--r--drivers/staging/iio/light/tsl2x7x_core.c2037
-rw-r--r--drivers/staging/iio/magnetometer/Kconfig18
-rw-r--r--drivers/staging/iio/magnetometer/Makefile5
-rw-r--r--drivers/staging/iio/magnetometer/hmc5843.c652
-rw-r--r--drivers/staging/iio/meter/Kconfig63
-rw-r--r--drivers/staging/iio/meter/Makefile15
-rw-r--r--drivers/staging/iio/meter/ade7753.c550
-rw-r--r--drivers/staging/iio/meter/ade7753.h72
-rw-r--r--drivers/staging/iio/meter/ade7754.c572
-rw-r--r--drivers/staging/iio/meter/ade7754.h90
-rw-r--r--drivers/staging/iio/meter/ade7758.h183
-rw-r--r--drivers/staging/iio/meter/ade7758_core.c958
-rw-r--r--drivers/staging/iio/meter/ade7758_ring.c188
-rw-r--r--drivers/staging/iio/meter/ade7758_trigger.c109
-rw-r--r--drivers/staging/iio/meter/ade7759.c496
-rw-r--r--drivers/staging/iio/meter/ade7759.h53
-rw-r--r--drivers/staging/iio/meter/ade7854-i2c.c258
-rw-r--r--drivers/staging/iio/meter/ade7854-spi.c331
-rw-r--r--drivers/staging/iio/meter/ade7854.c578
-rw-r--r--drivers/staging/iio/meter/ade7854.h174
-rw-r--r--drivers/staging/iio/meter/meter.h396
-rw-r--r--drivers/staging/iio/resolver/Kconfig30
-rw-r--r--drivers/staging/iio/resolver/Makefile7
-rw-r--r--drivers/staging/iio/resolver/ad2s1200.c166
-rw-r--r--drivers/staging/iio/resolver/ad2s1210.c742
-rw-r--r--drivers/staging/iio/resolver/ad2s1210.h17
-rw-r--r--drivers/staging/iio/resolver/ad2s90.c120
-rw-r--r--drivers/staging/iio/ring_hw.h22
-rw-r--r--drivers/staging/iio/trigger/Kconfig26
-rw-r--r--drivers/staging/iio/trigger/Makefile6
-rw-r--r--drivers/staging/iio/trigger/iio-trig-bfin-timer.c302
-rw-r--r--drivers/staging/iio/trigger/iio-trig-bfin-timer.h24
-rw-r--r--drivers/staging/iio/trigger/iio-trig-periodic-rtc.c203
-rw-r--r--drivers/staging/imx-drm/Kconfig53
-rw-r--r--drivers/staging/imx-drm/Makefile12
-rw-r--r--drivers/staging/imx-drm/TODO17
-rw-r--r--drivers/staging/imx-drm/imx-drm-core.c718
-rw-r--r--drivers/staging/imx-drm/imx-drm.h56
-rw-r--r--drivers/staging/imx-drm/imx-hdmi.c1768
-rw-r--r--drivers/staging/imx-drm/imx-hdmi.h1032
-rw-r--r--drivers/staging/imx-drm/imx-ldb.c613
-rw-r--r--drivers/staging/imx-drm/imx-tve.c735
-rw-r--r--drivers/staging/imx-drm/ipuv3-crtc.c515
-rw-r--r--drivers/staging/imx-drm/ipuv3-plane.c387
-rw-r--r--drivers/staging/imx-drm/ipuv3-plane.h55
-rw-r--r--drivers/staging/imx-drm/parallel-display.c293
-rw-r--r--drivers/staging/keucr/Kconfig14
-rw-r--r--drivers/staging/keucr/Makefile13
-rw-r--r--drivers/staging/keucr/TODO12
-rw-r--r--drivers/staging/keucr/common.h7
-rw-r--r--drivers/staging/keucr/init.c333
-rw-r--r--drivers/staging/keucr/init.h518
-rw-r--r--drivers/staging/keucr/scsiglue.c467
-rw-r--r--drivers/staging/keucr/scsiglue.h10
-rw-r--r--drivers/staging/keucr/smcommon.h29
-rw-r--r--drivers/staging/keucr/smil.h288
-rw-r--r--drivers/staging/keucr/smilecc.c211
-rw-r--r--drivers/staging/keucr/smilmain.c760
-rw-r--r--drivers/staging/keucr/smilsub.c679
-rw-r--r--drivers/staging/keucr/smscsi.c194
-rw-r--r--drivers/staging/keucr/transport.c865
-rw-r--r--drivers/staging/keucr/transport.h73
-rw-r--r--drivers/staging/keucr/usb.c642
-rw-r--r--drivers/staging/keucr/usb.h240
-rw-r--r--drivers/staging/line6/Kconfig38
-rw-r--r--drivers/staging/line6/Makefile14
-rw-r--r--drivers/staging/line6/audio.c71
-rw-r--r--drivers/staging/line6/audio.h21
-rw-r--r--drivers/staging/line6/capture.c432
-rw-r--r--drivers/staging/line6/capture.h35
-rw-r--r--drivers/staging/line6/driver.c1162
-rw-r--r--drivers/staging/line6/driver.h215
-rw-r--r--drivers/staging/line6/midi.c321
-rw-r--r--drivers/staging/line6/midi.h72
-rw-r--r--drivers/staging/line6/midibuf.c265
-rw-r--r--drivers/staging/line6/midibuf.h38
-rw-r--r--drivers/staging/line6/pcm.c576
-rw-r--r--drivers/staging/line6/pcm.h382
-rw-r--r--drivers/staging/line6/playback.c592
-rw-r--r--drivers/staging/line6/playback.h41
-rw-r--r--drivers/staging/line6/pod.c458
-rw-r--r--drivers/staging/line6/pod.h102
-rw-r--r--drivers/staging/line6/podhd.c154
-rw-r--r--drivers/staging/line6/podhd.h30
-rw-r--r--drivers/staging/line6/revision.h4
-rw-r--r--drivers/staging/line6/toneport.c460
-rw-r--r--drivers/staging/line6/toneport.h52
-rw-r--r--drivers/staging/line6/usbdefs.h116
-rw-r--r--drivers/staging/line6/variax.c235
-rw-r--r--drivers/staging/line6/variax.h72
-rw-r--r--drivers/staging/lustre/Kconfig3
-rw-r--r--drivers/staging/lustre/Makefile4
-rw-r--r--drivers/staging/lustre/TODO13
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/bitmap.h1
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/curproc.h97
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs.h187
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_cpu.h219
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_crypto.h201
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_debug.h274
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_fail.h170
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_hash.h849
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_heap.h200
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_ioctl.h220
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_kernelcomm.h119
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_prim.h88
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_private.h572
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_string.h107
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_time.h132
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/libcfs_workitem.h110
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/kp30.h93
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/libcfs.h121
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-bitops.h38
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-cpu.h166
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-crypto.h49
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-lock.h204
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-mem.h87
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-prim.h82
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-tcpip.h72
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-time.h274
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/linux-types.h36
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/linux/portals_compat25.h99
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/lucache.h162
-rw-r--r--drivers/staging/lustre/include/linux/libcfs/params_tree.h164
-rw-r--r--drivers/staging/lustre/include/linux/lnet/api-support.h44
-rw-r--r--drivers/staging/lustre/include/linux/lnet/api.h220
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lib-lnet.h875
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lib-types.h761
-rw-r--r--drivers/staging/lustre/include/linux/lnet/linux/api-support.h43
-rw-r--r--drivers/staging/lustre/include/linux/lnet/linux/lib-lnet.h72
-rw-r--r--drivers/staging/lustre/include/linux/lnet/linux/lib-types.h45
-rw-r--r--drivers/staging/lustre/include/linux/lnet/linux/lnet.h56
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lnet-sysctl.h51
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lnet.h51
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lnetctl.h80
-rw-r--r--drivers/staging/lustre/include/linux/lnet/lnetst.h491
-rw-r--r--drivers/staging/lustre/include/linux/lnet/ptllnd.h94
-rw-r--r--drivers/staging/lustre/include/linux/lnet/ptllnd_wire.h120
-rw-r--r--drivers/staging/lustre/include/linux/lnet/socklnd.h103
-rw-r--r--drivers/staging/lustre/include/linux/lnet/types.h492
-rw-r--r--drivers/staging/lustre/lnet/Kconfig40
-rw-r--r--drivers/staging/lustre/lnet/Makefile1
-rw-r--r--drivers/staging/lustre/lnet/klnds/Makefile1
-rw-r--r--drivers/staging/lustre/lnet/klnds/o2iblnd/Makefile5
-rw-r--r--drivers/staging/lustre/lnet/klnds/o2iblnd/o2iblnd.c3194
-rw-r--r--drivers/staging/lustre/lnet/klnds/o2iblnd/o2iblnd.h1052
-rw-r--r--drivers/staging/lustre/lnet/klnds/o2iblnd/o2iblnd_cb.c3534
-rw-r--r--drivers/staging/lustre/lnet/klnds/o2iblnd/o2iblnd_modparams.c231
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/Makefile7
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd.c2904
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd.h596
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd_cb.c2656
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd_lib-linux.c714
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd_lib-linux.h87
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd_modparams.c188
-rw-r--r--drivers/staging/lustre/lnet/klnds/socklnd/socklnd_proto.c799
-rw-r--r--drivers/staging/lustre/lnet/lnet/Makefile8
-rw-r--r--drivers/staging/lustre/lnet/lnet/acceptor.c506
-rw-r--r--drivers/staging/lustre/lnet/lnet/api-ni.c1948
-rw-r--r--drivers/staging/lustre/lnet/lnet/config.c1301
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-eq.c445
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-md.c451
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-me.c297
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-move.c2449
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-msg.c647
-rw-r--r--drivers/staging/lustre/lnet/lnet/lib-ptl.c938
-rw-r--r--drivers/staging/lustre/lnet/lnet/lo.c120
-rw-r--r--drivers/staging/lustre/lnet/lnet/module.c155
-rw-r--r--drivers/staging/lustre/lnet/lnet/peer.c338
-rw-r--r--drivers/staging/lustre/lnet/lnet/router.c1699
-rw-r--r--drivers/staging/lustre/lnet/lnet/router_proc.c938
-rw-r--r--drivers/staging/lustre/lnet/selftest/Makefile6
-rw-r--r--drivers/staging/lustre/lnet/selftest/brw_test.c509
-rw-r--r--drivers/staging/lustre/lnet/selftest/conctl.c929
-rw-r--r--drivers/staging/lustre/lnet/selftest/conrpc.c1396
-rw-r--r--drivers/staging/lustre/lnet/selftest/conrpc.h146
-rw-r--r--drivers/staging/lustre/lnet/selftest/console.c2108
-rw-r--r--drivers/staging/lustre/lnet/selftest/console.h232
-rw-r--r--drivers/staging/lustre/lnet/selftest/framework.c1814
-rw-r--r--drivers/staging/lustre/lnet/selftest/module.c171
-rw-r--r--drivers/staging/lustre/lnet/selftest/ping_test.c230
-rw-r--r--drivers/staging/lustre/lnet/selftest/rpc.c1667
-rw-r--r--drivers/staging/lustre/lnet/selftest/rpc.h302
-rw-r--r--drivers/staging/lustre/lnet/selftest/selftest.h612
-rw-r--r--drivers/staging/lustre/lnet/selftest/timer.c248
-rw-r--r--drivers/staging/lustre/lnet/selftest/timer.h53
-rw-r--r--drivers/staging/lustre/lustre/Kconfig60
-rw-r--r--drivers/staging/lustre/lustre/Makefile2
-rw-r--r--drivers/staging/lustre/lustre/fid/Makefile6
-rw-r--r--drivers/staging/lustre/lustre/fid/fid_internal.h56
-rw-r--r--drivers/staging/lustre/lustre/fid/fid_lib.c95
-rw-r--r--drivers/staging/lustre/lustre/fid/fid_request.c568
-rw-r--r--drivers/staging/lustre/lustre/fid/lproc_fid.c227
-rw-r--r--drivers/staging/lustre/lustre/fld/Makefile6
-rw-r--r--drivers/staging/lustre/lustre/fld/fld_cache.c547
-rw-r--r--drivers/staging/lustre/lustre/fld/fld_internal.h193
-rw-r--r--drivers/staging/lustre/lustre/fld/fld_request.c520
-rw-r--r--drivers/staging/lustre/lustre/fld/lproc_fld.c163
-rw-r--r--drivers/staging/lustre/lustre/include/cl_object.h3287
-rw-r--r--drivers/staging/lustre/lustre/include/dt_object.h1499
-rw-r--r--drivers/staging/lustre/lustre/include/interval_tree.h124
-rw-r--r--drivers/staging/lustre/lustre/include/lclient.h428
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lprocfs_status.h57
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_acl.h66
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_common.h22
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_compat25.h243
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_dlm.h46
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_fsfilt.h171
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_handles.h52
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_intent.h62
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_lib.h85
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_lite.h99
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_log.h57
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_net.h49
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_patchless_compat.h83
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_quota.h46
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lustre_user.h70
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lvfs.h134
-rw-r--r--drivers/staging/lustre/lustre/include/linux/lvfs_linux.h66
-rw-r--r--drivers/staging/lustre/lustre/include/linux/obd.h126
-rw-r--r--drivers/staging/lustre/lustre/include/linux/obd_class.h58
-rw-r--r--drivers/staging/lustre/lustre/include/linux/obd_support.h63
-rw-r--r--drivers/staging/lustre/lustre/include/lprocfs_status.h1005
-rw-r--r--drivers/staging/lustre/lustre/include/lu_object.h1340
-rw-r--r--drivers/staging/lustre/lustre/include/lu_ref.h182
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/libiam.h145
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/ll_fiemap.h121
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/lustre_build_version.h2
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/lustre_errno.h215
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/lustre_idl.h3790
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/lustre_lfsck_user.h95
-rw-r--r--drivers/staging/lustre/lustre/include/lustre/lustre_user.h1171
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_acl.h42
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_capa.h305
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_cfg.h293
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_debug.h56
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_disk.h548
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_dlm.h1482
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_dlm_flags.h476
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_eacl.h95
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_export.h406
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_fid.h767
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_fld.h161
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_fsfilt.h48
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_ha.h64
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_handles.h93
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_idmap.h104
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_import.h380
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_lib.h651
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_linkea.h57
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_lite.h151
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_log.h569
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_mdc.h191
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_mds.h81
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_net.h3247
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_param.h121
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_quota.h239
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_req_layout.h341
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_sec.h1150
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_update.h189
-rw-r--r--drivers/staging/lustre/lustre/include/lustre_ver.h24
-rw-r--r--drivers/staging/lustre/lustre/include/lvfs.h57
-rw-r--r--drivers/staging/lustre/lustre/include/md_object.h903
-rw-r--r--drivers/staging/lustre/lustre/include/obd.h1572
-rw-r--r--drivers/staging/lustre/lustre/include/obd_cache.h39
-rw-r--r--drivers/staging/lustre/lustre/include/obd_cksum.h176
-rw-r--r--drivers/staging/lustre/lustre/include/obd_class.h2191
-rw-r--r--drivers/staging/lustre/lustre/include/obd_ost.h96
-rw-r--r--drivers/staging/lustre/lustre/include/obd_support.h862
-rw-r--r--drivers/staging/lustre/lustre/lclient/glimpse.c269
-rw-r--r--drivers/staging/lustre/lustre/lclient/lcommon_cl.c1275
-rw-r--r--drivers/staging/lustre/lustre/lclient/lcommon_misc.c199
-rw-r--r--drivers/staging/lustre/lustre/ldlm/interval_tree.c746
-rw-r--r--drivers/staging/lustre/lustre/ldlm/l_lock.c76
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_extent.c240
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_flock.c859
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_inodebits.c75
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_internal.h309
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_lib.c851
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_lock.c2368
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_lockd.c1186
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_plain.c72
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_pool.c1434
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_request.c2285
-rw-r--r--drivers/staging/lustre/lustre/ldlm/ldlm_resource.c1425
-rw-r--r--drivers/staging/lustre/lustre/libcfs/Makefile21
-rw-r--r--drivers/staging/lustre/lustre/libcfs/debug.c469
-rw-r--r--drivers/staging/lustre/lustre/libcfs/fail.c137
-rw-r--r--drivers/staging/lustre/lustre/libcfs/hash.c2113
-rw-r--r--drivers/staging/lustre/lustre/libcfs/heap.c475
-rw-r--r--drivers/staging/lustre/lustre/libcfs/kernel_user_comm.c240
-rw-r--r--drivers/staging/lustre/lustre/libcfs/libcfs_cpu.c224
-rw-r--r--drivers/staging/lustre/lustre/libcfs/libcfs_lock.c189
-rw-r--r--drivers/staging/lustre/lustre/libcfs/libcfs_mem.c202
-rw-r--r--drivers/staging/lustre/lustre/libcfs/libcfs_string.c560
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-cpu.c1060
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-crypto-adler.c144
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-crypto.c291
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-curproc.c116
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-debug.c203
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-module.c183
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-prim.c217
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-proc.c544
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-tcpip.c613
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-tracefile.c275
-rw-r--r--drivers/staging/lustre/lustre/libcfs/linux/linux-tracefile.h48
-rw-r--r--drivers/staging/lustre/lustre/libcfs/module.c450
-rw-r--r--drivers/staging/lustre/lustre/libcfs/nidstrings.c860
-rw-r--r--drivers/staging/lustre/lustre/libcfs/prng.c139
-rw-r--r--drivers/staging/lustre/lustre/libcfs/tracefile.c1199
-rw-r--r--drivers/staging/lustre/lustre/libcfs/tracefile.h340
-rw-r--r--drivers/staging/lustre/lustre/libcfs/upcall_cache.c451
-rw-r--r--drivers/staging/lustre/lustre/libcfs/workitem.c478
-rw-r--r--drivers/staging/lustre/lustre/llite/Makefile14
-rw-r--r--drivers/staging/lustre/lustre/llite/dcache.c377
-rw-r--r--drivers/staging/lustre/lustre/llite/dir.c1985
-rw-r--r--drivers/staging/lustre/lustre/llite/file.c3557
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_capa.c655
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_close.c397
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_internal.h1538
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_lib.c2416
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_mmap.c487
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_nfs.c329
-rw-r--r--drivers/staging/lustre/lustre/llite/llite_rmtacl.c301
-rw-r--r--drivers/staging/lustre/lustre/llite/lloop.c863
-rw-r--r--drivers/staging/lustre/lustre/llite/lproc_llite.c1506
-rw-r--r--drivers/staging/lustre/lustre/llite/namei.c1252
-rw-r--r--drivers/staging/lustre/lustre/llite/remote_perm.c330
-rw-r--r--drivers/staging/lustre/lustre/llite/rw.c1290
-rw-r--r--drivers/staging/lustre/lustre/llite/rw26.c553
-rw-r--r--drivers/staging/lustre/lustre/llite/statahead.c1708
-rw-r--r--drivers/staging/lustre/lustre/llite/super25.c226
-rw-r--r--drivers/staging/lustre/lustre/llite/symlink.c167
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_dev.c545
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_internal.h62
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_io.c1208
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_lock.c84
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_object.c201
-rw-r--r--drivers/staging/lustre/lustre/llite/vvp_page.c552
-rw-r--r--drivers/staging/lustre/lustre/llite/xattr.c608
-rw-r--r--drivers/staging/lustre/lustre/llite/xattr_cache.c536
-rw-r--r--drivers/staging/lustre/lustre/lmv/Makefile5
-rw-r--r--drivers/staging/lustre/lustre/lmv/lmv_fld.c84
-rw-r--r--drivers/staging/lustre/lustre/lmv/lmv_intent.c320
-rw-r--r--drivers/staging/lustre/lustre/lmv/lmv_internal.h159
-rw-r--r--drivers/staging/lustre/lustre/lmv/lmv_obd.c2873
-rw-r--r--drivers/staging/lustre/lustre/lmv/lproc_lmv.c230
-rw-r--r--drivers/staging/lustre/lustre/lov/Makefile10
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_cl_internal.h839
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_dev.c528
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_ea.c358
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_internal.h352
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_io.c978
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_lock.c1184
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_merge.c214
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_obd.c2867
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_object.c990
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_offset.c265
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_pack.c677
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_page.c228
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_pool.c663
-rw-r--r--drivers/staging/lustre/lustre/lov/lov_request.c1517
-rw-r--r--drivers/staging/lustre/lustre/lov/lovsub_dev.c209
-rw-r--r--drivers/staging/lustre/lustre/lov/lovsub_io.c55
-rw-r--r--drivers/staging/lustre/lustre/lov/lovsub_lock.c466
-rw-r--r--drivers/staging/lustre/lustre/lov/lovsub_object.c164
-rw-r--r--drivers/staging/lustre/lustre/lov/lovsub_page.c71
-rw-r--r--drivers/staging/lustre/lustre/lov/lproc_lov.c299
-rw-r--r--drivers/staging/lustre/lustre/lvfs/Makefile7
-rw-r--r--drivers/staging/lustre/lustre/lvfs/fsfilt.c139
-rw-r--r--drivers/staging/lustre/lustre/lvfs/lvfs_lib.c170
-rw-r--r--drivers/staging/lustre/lustre/lvfs/lvfs_linux.c293
-rw-r--r--drivers/staging/lustre/lustre/mdc/Makefile6
-rw-r--r--drivers/staging/lustre/lustre/mdc/lproc_mdc.c214
-rw-r--r--drivers/staging/lustre/lustre/mdc/mdc_internal.h181
-rw-r--r--drivers/staging/lustre/lustre/mdc/mdc_lib.c590
-rw-r--r--drivers/staging/lustre/lustre/mdc/mdc_locks.c1301
-rw-r--r--drivers/staging/lustre/lustre/mdc/mdc_reint.c485
-rw-r--r--drivers/staging/lustre/lustre/mdc/mdc_request.c2758
-rw-r--r--drivers/staging/lustre/lustre/mgc/Makefile6
-rw-r--r--drivers/staging/lustre/lustre/mgc/libmgc.c158
-rw-r--r--drivers/staging/lustre/lustre/mgc/lproc_mgc.c80
-rw-r--r--drivers/staging/lustre/lustre/mgc/mgc_internal.h73
-rw-r--r--drivers/staging/lustre/lustre/mgc/mgc_request.c1985
-rw-r--r--drivers/staging/lustre/lustre/obdclass/Makefile13
-rw-r--r--drivers/staging/lustre/lustre/obdclass/acl.c539
-rw-r--r--drivers/staging/lustre/lustre/obdclass/capa.c417
-rw-r--r--drivers/staging/lustre/lustre/obdclass/cl_internal.h121
-rw-r--r--drivers/staging/lustre/lustre/obdclass/cl_io.c1669
-rw-r--r--drivers/staging/lustre/lustre/obdclass/cl_lock.c2240
-rw-r--r--drivers/staging/lustre/lustre/obdclass/cl_object.c1137
-rw-r--r--drivers/staging/lustre/lustre/obdclass/cl_page.c1553
-rw-r--r--drivers/staging/lustre/lustre/obdclass/class_obd.c678
-rw-r--r--drivers/staging/lustre/lustre/obdclass/debug.c112
-rw-r--r--drivers/staging/lustre/lustre/obdclass/dt_object.c1049
-rw-r--r--drivers/staging/lustre/lustre/obdclass/genops.c1827
-rw-r--r--drivers/staging/lustre/lustre/obdclass/idmap.c477
-rw-r--r--drivers/staging/lustre/lustre/obdclass/linkea.c194
-rw-r--r--drivers/staging/lustre/lustre/obdclass/linux/linux-module.c438
-rw-r--r--drivers/staging/lustre/lustre/obdclass/linux/linux-obdo.c222
-rw-r--r--drivers/staging/lustre/lustre/obdclass/linux/linux-sysctl.c426
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog.c990
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_cat.c815
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_internal.h98
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_ioctl.c418
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_lvfs.c847
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_obd.c287
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_osd.c1290
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_swab.c413
-rw-r--r--drivers/staging/lustre/lustre/obdclass/llog_test.c1072
-rw-r--r--drivers/staging/lustre/lustre/obdclass/local_storage.c894
-rw-r--r--drivers/staging/lustre/lustre/obdclass/local_storage.h91
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lprocfs_status.c1984
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lu_object.c2196
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lu_ref.c50
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lu_ucred.c107
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lustre_handles.c257
-rw-r--r--drivers/staging/lustre/lustre/obdclass/lustre_peer.c217
-rw-r--r--drivers/staging/lustre/lustre/obdclass/md_attrs.c199
-rw-r--r--drivers/staging/lustre/lustre/obdclass/mea.c112
-rw-r--r--drivers/staging/lustre/lustre/obdclass/obd_config.c1926
-rw-r--r--drivers/staging/lustre/lustre/obdclass/obd_mount.c1320
-rw-r--r--drivers/staging/lustre/lustre/obdclass/obdo.c362
-rw-r--r--drivers/staging/lustre/lustre/obdclass/statfs_pack.c75
-rw-r--r--drivers/staging/lustre/lustre/obdclass/uuid.c82
-rw-r--r--drivers/staging/lustre/lustre/obdecho/Makefile5
-rw-r--r--drivers/staging/lustre/lustre/obdecho/echo.c671
-rw-r--r--drivers/staging/lustre/lustre/obdecho/echo_client.c3170
-rw-r--r--drivers/staging/lustre/lustre/obdecho/echo_internal.h47
-rw-r--r--drivers/staging/lustre/lustre/obdecho/lproc_echo.c57
-rw-r--r--drivers/staging/lustre/lustre/osc/Makefile8
-rw-r--r--drivers/staging/lustre/lustre/osc/lproc_osc.c735
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_cache.c2881
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_cl_internal.h686
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_dev.c260
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_internal.h208
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_io.c816
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_lock.c1616
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_object.c274
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_page.c921
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_quota.c327
-rw-r--r--drivers/staging/lustre/lustre/osc/osc_request.c3661
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/Makefile25
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/client.c3114
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/connection.c240
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/errno.c380
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/events.c582
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/Makefile8
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_api.h179
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_asn1.h84
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_bulk.c505
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_cli_upcall.c446
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_err.h193
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_generic_token.c296
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_internal.h526
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_keyring.c1409
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_krb5.h163
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_krb5_mech.c1786
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_mech_switch.c358
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_pipefs.c1233
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_rawobj.c242
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/gss_svc_upcall.c1093
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/lproc_gss.c220
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c2889
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/import.c1621
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/layout.c2458
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/llog_client.c345
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/llog_net.c74
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/lproc_ptlrpc.c1342
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/niobuf.c729
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/nrs.c1759
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/nrs_crr.c40
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/nrs_fifo.c270
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/pack_generic.c2567
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/pers.c75
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/pinger.c683
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/ptlrpc_internal.h312
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/ptlrpc_module.c171
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/ptlrpcd.c812
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/recover.c381
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec.c2456
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_bulk.c888
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_config.c1228
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_gc.c250
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_lproc.c199
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_null.c464
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/sec_plain.c1001
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/service.c3114
-rw-r--r--drivers/staging/lustre/lustre/ptlrpc/wiretest.c4540
-rw-r--r--drivers/staging/media/Kconfig49
-rw-r--r--drivers/staging/media/Makefile15
-rw-r--r--drivers/staging/media/as102/Kconfig8
-rw-r--r--drivers/staging/media/as102/Makefile6
-rw-r--r--drivers/staging/media/as102/as102_drv.c277
-rw-r--r--drivers/staging/media/as102/as102_drv.h99
-rw-r--r--drivers/staging/media/as102/as102_fe.c571
-rw-r--r--drivers/staging/media/as102/as102_fw.c232
-rw-r--r--drivers/staging/media/as102/as102_fw.h38
-rw-r--r--drivers/staging/media/as102/as102_usb_drv.c472
-rw-r--r--drivers/staging/media/as102/as102_usb_drv.h61
-rw-r--r--drivers/staging/media/as102/as10x_cmd.c418
-rw-r--r--drivers/staging/media/as102/as10x_cmd.h529
-rw-r--r--drivers/staging/media/as102/as10x_cmd_cfg.c206
-rw-r--r--drivers/staging/media/as102/as10x_cmd_stream.c211
-rw-r--r--drivers/staging/media/as102/as10x_handle.h54
-rw-r--r--drivers/staging/media/as102/as10x_types.h194
-rw-r--r--drivers/staging/media/bcm2048/Kconfig13
-rw-r--r--drivers/staging/media/bcm2048/Makefile1
-rw-r--r--drivers/staging/media/bcm2048/TODO24
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.c2744
-rw-r--r--drivers/staging/media/bcm2048/radio-bcm2048.h30
-rw-r--r--drivers/staging/media/cxd2099/Kconfig12
-rw-r--r--drivers/staging/media/cxd2099/Makefile5
-rw-r--r--drivers/staging/media/cxd2099/TODO12
-rw-r--r--drivers/staging/media/cxd2099/cxd2099.c719
-rw-r--r--drivers/staging/media/cxd2099/cxd2099.h51
-rw-r--r--drivers/staging/media/davinci_vpfe/Kconfig9
-rw-r--r--drivers/staging/media/davinci_vpfe/Makefile3
-rw-r--r--drivers/staging/media/davinci_vpfe/TODO37
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci-vpfe-mc.txt154
-rw-r--r--drivers/staging/media/davinci_vpfe/davinci_vpfe_user.h1290
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.c1863
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe.h179
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.c1048
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipe_hw.h559
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.c1070
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif.h233
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_ipipeif_user.h93
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.c2105
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif.h203
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_isif_regs.h294
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.c1999
-rw-r--r--drivers/staging/media/davinci_vpfe/dm365_resizer.h244
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe.h86
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.c724
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_mc_capture.h95
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.c1637
-rw-r--r--drivers/staging/media/davinci_vpfe/vpfe_video.h153
-rw-r--r--drivers/staging/media/dt3155v4l/Kconfig28
-rw-r--r--drivers/staging/media/dt3155v4l/Makefile1
-rw-r--r--drivers/staging/media/dt3155v4l/dt3155v4l.c995
-rw-r--r--drivers/staging/media/dt3155v4l/dt3155v4l.h212
-rw-r--r--drivers/staging/media/go7007/Kconfig51
-rw-r--r--drivers/staging/media/go7007/Makefile15
-rw-r--r--drivers/staging/media/go7007/README137
-rw-r--r--drivers/staging/media/go7007/go7007-driver.c711
-rw-r--r--drivers/staging/media/go7007/go7007-fw.c1628
-rw-r--r--drivers/staging/media/go7007/go7007-i2c.c222
-rw-r--r--drivers/staging/media/go7007/go7007-loader.c145
-rw-r--r--drivers/staging/media/go7007/go7007-priv.h294
-rw-r--r--drivers/staging/media/go7007/go7007-usb.c1349
-rw-r--r--drivers/staging/media/go7007/go7007-v4l2.c1055
-rw-r--r--drivers/staging/media/go7007/go7007.h40
-rw-r--r--drivers/staging/media/go7007/go7007.txt478
-rw-r--r--drivers/staging/media/go7007/s2250-board.c631
-rw-r--r--drivers/staging/media/go7007/saa7134-go7007.c567
-rw-r--r--drivers/staging/media/go7007/snd-go7007.c302
-rw-r--r--drivers/staging/media/lirc/Kconfig72
-rw-r--r--drivers/staging/media/lirc/Makefile13
-rw-r--r--drivers/staging/media/lirc/TODO13
-rw-r--r--drivers/staging/media/lirc/TODO.lirc_zilog36
-rw-r--r--drivers/staging/media/lirc/lirc_bt829.c405
-rw-r--r--drivers/staging/media/lirc/lirc_igorplugusb.c514
-rw-r--r--drivers/staging/media/lirc/lirc_imon.c1042
-rw-r--r--drivers/staging/media/lirc/lirc_parallel.c746
-rw-r--r--drivers/staging/media/lirc/lirc_parallel.h26
-rw-r--r--drivers/staging/media/lirc/lirc_sasem.c934
-rw-r--r--drivers/staging/media/lirc/lirc_serial.c1240
-rw-r--r--drivers/staging/media/lirc/lirc_sir.c1317
-rw-r--r--drivers/staging/media/lirc/lirc_zilog.c1686
-rw-r--r--drivers/staging/media/msi3101/Kconfig10
-rw-r--r--drivers/staging/media/msi3101/Makefile2
-rw-r--r--drivers/staging/media/msi3101/msi001.c500
-rw-r--r--drivers/staging/media/msi3101/sdr-msi3101.c1518
-rw-r--r--drivers/staging/media/omap24xx/Kconfig35
-rw-r--r--drivers/staging/media/omap24xx/Makefile5
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam-dma.c601
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam.c1888
-rw-r--r--drivers/staging/media/omap24xx/omap24xxcam.h596
-rw-r--r--drivers/staging/media/omap24xx/tcm825x.c937
-rw-r--r--drivers/staging/media/omap24xx/tcm825x.h200
-rw-r--r--drivers/staging/media/omap24xx/v4l2-int-device.c164
-rw-r--r--drivers/staging/media/omap24xx/v4l2-int-device.h305
-rw-r--r--drivers/staging/media/omap4iss/Kconfig6
-rw-r--r--drivers/staging/media/omap4iss/Makefile6
-rw-r--r--drivers/staging/media/omap4iss/TODO4
-rw-r--r--drivers/staging/media/omap4iss/iss.c1557
-rw-r--r--drivers/staging/media/omap4iss/iss.h250
-rw-r--r--drivers/staging/media/omap4iss/iss_csi2.c1326
-rw-r--r--drivers/staging/media/omap4iss/iss_csi2.h158
-rw-r--r--drivers/staging/media/omap4iss/iss_csiphy.c279
-rw-r--r--drivers/staging/media/omap4iss/iss_csiphy.h51
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipe.c570
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipe.h67
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipeif.c849
-rw-r--r--drivers/staging/media/omap4iss/iss_ipipeif.h92
-rw-r--r--drivers/staging/media/omap4iss/iss_regs.h901
-rw-r--r--drivers/staging/media/omap4iss/iss_resizer.c893
-rw-r--r--drivers/staging/media/omap4iss/iss_resizer.h75
-rw-r--r--drivers/staging/media/omap4iss/iss_video.c1226
-rw-r--r--drivers/staging/media/omap4iss/iss_video.h204
-rw-r--r--drivers/staging/media/rtl2832u_sdr/Kconfig7
-rw-r--r--drivers/staging/media/rtl2832u_sdr/Makefile6
-rw-r--r--drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.c1497
-rw-r--r--drivers/staging/media/rtl2832u_sdr/rtl2832_sdr.h54
-rw-r--r--drivers/staging/media/sn9c102/Kconfig17
-rw-r--r--drivers/staging/media/sn9c102/Makefile15
-rw-r--r--drivers/staging/media/sn9c102/sn9c102.h214
-rw-r--r--drivers/staging/media/sn9c102/sn9c102.txt592
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_config.h86
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_core.c3465
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_devtable.h145
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_hv7131d.c269
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_hv7131r.c369
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mi0343.c352
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mi0360.c453
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_mt9v111.c260
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_ov7630.c634
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_ov7660.c546
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_pas106b.c308
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_pas202bcb.c340
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_sensor.h307
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5110c1b.c154
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5110d.c119
-rw-r--r--drivers/staging/media/sn9c102/sn9c102_tas5130d1b.c165
-rw-r--r--drivers/staging/media/solo6x10/Kconfig18
-rw-r--r--drivers/staging/media/solo6x10/Makefile5
-rw-r--r--drivers/staging/media/solo6x10/TODO15
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-core.c709
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-disp.c322
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-eeprom.c154
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-enc.c344
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-g723.c424
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-gpio.c109
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-i2c.c334
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-jpeg.h193
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-offsets.h87
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-p2m.c333
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-regs.h639
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-tw28.c874
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-tw28.h69
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-v4l2-enc.c1414
-rw-r--r--drivers/staging/media/solo6x10/solo6x10-v4l2.c734
-rw-r--r--drivers/staging/media/solo6x10/solo6x10.h426
-rw-r--r--drivers/staging/mt29f_spinand/Kconfig16
-rw-r--r--drivers/staging/mt29f_spinand/Makefile1
-rw-r--r--drivers/staging/mt29f_spinand/TODO13
-rw-r--r--drivers/staging/mt29f_spinand/mt29f_spinand.c950
-rw-r--r--drivers/staging/mt29f_spinand/mt29f_spinand.h107
-rw-r--r--drivers/staging/netlogic/Kconfig7
-rw-r--r--drivers/staging/netlogic/Makefile1
-rw-r--r--drivers/staging/netlogic/TODO12
-rw-r--r--drivers/staging/netlogic/platform_net.c223
-rw-r--r--drivers/staging/netlogic/platform_net.h46
-rw-r--r--drivers/staging/netlogic/xlr_net.c1129
-rw-r--r--drivers/staging/netlogic/xlr_net.h1099
-rw-r--r--drivers/staging/nokia_h4p/Kconfig9
-rw-r--r--drivers/staging/nokia_h4p/Makefile6
-rw-r--r--drivers/staging/nokia_h4p/TODO132
-rw-r--r--drivers/staging/nokia_h4p/hci_h4p.h222
-rw-r--r--drivers/staging/nokia_h4p/nokia_core.c1206
-rw-r--r--drivers/staging/nokia_h4p/nokia_fw-bcm.c149
-rw-r--r--drivers/staging/nokia_h4p/nokia_fw-csr.c149
-rw-r--r--drivers/staging/nokia_h4p/nokia_fw-ti1273.c110
-rw-r--r--drivers/staging/nokia_h4p/nokia_fw.c208
-rw-r--r--drivers/staging/nokia_h4p/nokia_uart.c199
-rw-r--r--drivers/staging/nvec/Kconfig35
-rw-r--r--drivers/staging/nvec/Makefile5
-rw-r--r--drivers/staging/nvec/README14
-rw-r--r--drivers/staging/nvec/TODO8
-rw-r--r--drivers/staging/nvec/nvec-keytable.h307
-rw-r--r--drivers/staging/nvec/nvec.c990
-rw-r--r--drivers/staging/nvec/nvec.h183
-rw-r--r--drivers/staging/nvec/nvec_kbd.c193
-rw-r--r--drivers/staging/nvec/nvec_paz00.c98
-rw-r--r--drivers/staging/nvec/nvec_power.c443
-rw-r--r--drivers/staging/nvec/nvec_ps2.c188
-rw-r--r--drivers/staging/octeon-usb/Kconfig10
-rw-r--r--drivers/staging/octeon-usb/Makefile1
-rw-r--r--drivers/staging/octeon-usb/TODO11
-rw-r--r--drivers/staging/octeon-usb/octeon-hcd.c3960
-rw-r--r--drivers/staging/octeon-usb/octeon-hcd.h1819
-rw-r--r--drivers/staging/octeon/Kconfig13
-rw-r--r--drivers/staging/octeon/Makefile23
-rw-r--r--drivers/staging/octeon/ethernet-defines.h102
-rw-r--r--drivers/staging/octeon/ethernet-mdio.c182
-rw-r--r--drivers/staging/octeon/ethernet-mdio.h44
-rw-r--r--drivers/staging/octeon/ethernet-mem.c173
-rw-r--r--drivers/staging/octeon/ethernet-mem.h29
-rw-r--r--drivers/staging/octeon/ethernet-rgmii.c433
-rw-r--r--drivers/staging/octeon/ethernet-rx.c558
-rw-r--r--drivers/staging/octeon/ethernet-rx.h52
-rw-r--r--drivers/staging/octeon/ethernet-sgmii.c127
-rw-r--r--drivers/staging/octeon/ethernet-spi.c292
-rw-r--r--drivers/staging/octeon/ethernet-tx.c749
-rw-r--r--drivers/staging/octeon/ethernet-tx.h34
-rw-r--r--drivers/staging/octeon/ethernet-util.h72
-rw-r--r--drivers/staging/octeon/ethernet-xaui.c125
-rw-r--r--drivers/staging/octeon/ethernet.c884
-rw-r--r--drivers/staging/octeon/octeon-ethernet.h101
-rw-r--r--drivers/staging/olpc_dcon/Kconfig35
-rw-r--r--drivers/staging/olpc_dcon/Makefile6
-rw-r--r--drivers/staging/olpc_dcon/TODO9
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon.c820
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon.h111
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon_xo_1.c205
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c161
-rw-r--r--drivers/staging/ozwpan/Kconfig9
-rw-r--r--drivers/staging/ozwpan/Makefile16
-rw-r--r--drivers/staging/ozwpan/README25
-rw-r--r--drivers/staging/ozwpan/TODO14
-rw-r--r--drivers/staging/ozwpan/ozappif.h36
-rw-r--r--drivers/staging/ozwpan/ozcdev.c550
-rw-r--r--drivers/staging/ozwpan/ozcdev.h17
-rw-r--r--drivers/staging/ozwpan/ozdbg.h54
-rw-r--r--drivers/staging/ozwpan/ozeltbuf.c338
-rw-r--r--drivers/staging/ozwpan/ozeltbuf.h70
-rw-r--r--drivers/staging/ozwpan/ozhcd.c2383
-rw-r--r--drivers/staging/ozwpan/ozhcd.h15
-rw-r--r--drivers/staging/ozwpan/ozmain.c59
-rw-r--r--drivers/staging/ozwpan/ozpd.c1016
-rw-r--r--drivers/staging/ozwpan/ozpd.h133
-rw-r--r--drivers/staging/ozwpan/ozproto.c800
-rw-r--r--drivers/staging/ozwpan/ozproto.h69
-rw-r--r--drivers/staging/ozwpan/ozprotocol.h377
-rw-r--r--drivers/staging/ozwpan/ozurbparanoia.c54
-rw-r--r--drivers/staging/ozwpan/ozurbparanoia.h19
-rw-r--r--drivers/staging/ozwpan/ozusbif.h43
-rw-r--r--drivers/staging/ozwpan/ozusbsvc.c261
-rw-r--r--drivers/staging/ozwpan/ozusbsvc.h32
-rw-r--r--drivers/staging/ozwpan/ozusbsvc1.c449
-rw-r--r--drivers/staging/panel/Kconfig278
-rw-r--r--drivers/staging/panel/Makefile1
-rw-r--r--drivers/staging/panel/TODO9
-rw-r--r--drivers/staging/panel/lcd-panel-cgram.txt24
-rw-r--r--drivers/staging/panel/panel.c2378
-rw-r--r--drivers/staging/phison/Kconfig5
-rw-r--r--drivers/staging/phison/Makefile1
-rw-r--r--drivers/staging/phison/phison.c94
-rw-r--r--drivers/staging/quickstart/Kconfig10
-rw-r--r--drivers/staging/quickstart/Makefile1
-rw-r--r--drivers/staging/quickstart/quickstart.c458
-rw-r--r--drivers/staging/rtl8188eu/Kconfig28
-rw-r--r--drivers/staging/rtl8188eu/Makefile66
-rw-r--r--drivers/staging/rtl8188eu/TODO19
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_ap.c1976
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_br_ext.c1191
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_cmd.c2196
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_debug.c947
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_efuse.c870
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_ieee80211.c1626
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_io.c301
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_ioctl_set.c1137
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_iol.c209
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_led.c481
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_mlme.c2277
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_mlme_ext.c8478
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_mp.c995
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_mp_ioctl.c1430
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_p2p.c2041
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_pwrctrl.c620
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_recv.c2214
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_rf.c89
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_security.c1706
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_sreset.c79
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_sta_mgt.c565
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_wlan_util.c1663
-rw-r--r--drivers/staging/rtl8188eu/core/rtw_xmit.c2359
-rw-r--r--drivers/staging/rtl8188eu/hal/Hal8188EPwrSeq.c86
-rw-r--r--drivers/staging/rtl8188eu/hal/Hal8188ERateAdaptive.c761
-rw-r--r--drivers/staging/rtl8188eu/hal/HalHWImg8188E_BB.c721
-rw-r--r--drivers/staging/rtl8188eu/hal/HalHWImg8188E_MAC.c231
-rw-r--r--drivers/staging/rtl8188eu/hal/HalHWImg8188E_RF.c269
-rw-r--r--drivers/staging/rtl8188eu/hal/HalPhyRf.c49
-rw-r--r--drivers/staging/rtl8188eu/hal/HalPhyRf_8188e.c1486
-rw-r--r--drivers/staging/rtl8188eu/hal/HalPwrSeqCmd.c132
-rw-r--r--drivers/staging/rtl8188eu/hal/hal_com.c381
-rw-r--r--drivers/staging/rtl8188eu/hal/hal_intf.c444
-rw-r--r--drivers/staging/rtl8188eu/hal/odm.c1386
-rw-r--r--drivers/staging/rtl8188eu/hal/odm_HWConfig.c469
-rw-r--r--drivers/staging/rtl8188eu/hal/odm_RTL8188E.c378
-rw-r--r--drivers/staging/rtl8188eu/hal/odm_RegConfig8188E.c138
-rw-r--r--drivers/staging/rtl8188eu/hal/odm_debug.c32
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_cmd.c767
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_dm.c247
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_hal_init.c2392
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_mp.c854
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_phycfg.c936
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_rf6052.c527
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_rxdesc.c205
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_sreset.c80
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188e_xmit.c91
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188eu_led.c92
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188eu_recv.c115
-rw-r--r--drivers/staging/rtl8188eu/hal/rtl8188eu_xmit.c704
-rw-r--r--drivers/staging/rtl8188eu/hal/usb_halinit.c2270
-rw-r--r--drivers/staging/rtl8188eu/hal/usb_ops_linux.c684
-rw-r--r--drivers/staging/rtl8188eu/include/Hal8188EPhyCfg.h275
-rw-r--r--drivers/staging/rtl8188eu/include/Hal8188EPhyReg.h1094
-rw-r--r--drivers/staging/rtl8188eu/include/Hal8188EPwrSeq.h176
-rw-r--r--drivers/staging/rtl8188eu/include/Hal8188ERateAdaptive.h75
-rw-r--r--drivers/staging/rtl8188eu/include/Hal8188EReg.h46
-rw-r--r--drivers/staging/rtl8188eu/include/HalHWImg8188E_BB.h44
-rw-r--r--drivers/staging/rtl8188eu/include/HalHWImg8188E_FW.h34
-rw-r--r--drivers/staging/rtl8188eu/include/HalHWImg8188E_MAC.h30
-rw-r--r--drivers/staging/rtl8188eu/include/HalHWImg8188E_RF.h30
-rw-r--r--drivers/staging/rtl8188eu/include/HalPhyRf.h30
-rw-r--r--drivers/staging/rtl8188eu/include/HalPhyRf_8188e.h61
-rw-r--r--drivers/staging/rtl8188eu/include/HalPwrSeqCmd.h128
-rw-r--r--drivers/staging/rtl8188eu/include/HalVerDef.h167
-rw-r--r--drivers/staging/rtl8188eu/include/basic_types.h184
-rw-r--r--drivers/staging/rtl8188eu/include/cmd_osdep.h32
-rw-r--r--drivers/staging/rtl8188eu/include/drv_types.h337
-rw-r--r--drivers/staging/rtl8188eu/include/hal_com.h173
-rw-r--r--drivers/staging/rtl8188eu/include/hal_intf.h389
-rw-r--r--drivers/staging/rtl8188eu/include/ieee80211.h1274
-rw-r--r--drivers/staging/rtl8188eu/include/ieee80211_ext.h290
-rw-r--r--drivers/staging/rtl8188eu/include/mlme_osdep.h35
-rw-r--r--drivers/staging/rtl8188eu/include/mp_custom_oid.h352
-rw-r--r--drivers/staging/rtl8188eu/include/odm.h1118
-rw-r--r--drivers/staging/rtl8188eu/include/odm_HWConfig.h132
-rw-r--r--drivers/staging/rtl8188eu/include/odm_RTL8188E.h54
-rw-r--r--drivers/staging/rtl8188eu/include/odm_RegConfig8188E.h43
-rw-r--r--drivers/staging/rtl8188eu/include/odm_RegDefine11AC.h54
-rw-r--r--drivers/staging/rtl8188eu/include/odm_RegDefine11N.h171
-rw-r--r--drivers/staging/rtl8188eu/include/odm_debug.h134
-rw-r--r--drivers/staging/rtl8188eu/include/odm_precomp.h91
-rw-r--r--drivers/staging/rtl8188eu/include/odm_reg.h119
-rw-r--r--drivers/staging/rtl8188eu/include/odm_types.h37
-rw-r--r--drivers/staging/rtl8188eu/include/osdep_intf.h83
-rw-r--r--drivers/staging/rtl8188eu/include/osdep_service.h368
-rw-r--r--drivers/staging/rtl8188eu/include/recv_osdep.h54
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_cmd.h122
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_dm.h61
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_hal.h475
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_led.h35
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_recv.h69
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_rf.h36
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_spec.h1439
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_sreset.h31
-rw-r--r--drivers/staging/rtl8188eu/include/rtl8188e_xmit.h178
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_android.h64
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_ap.h65
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_br_ext.h66
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_cmd.h989
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_debug.h266
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_eeprom.h130
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_efuse.h150
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_event.h115
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_ht.h44
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_io.h343
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_ioctl.h122
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_ioctl_rtl.h79
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_ioctl_set.h50
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_iol.h84
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_led.h124
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_mlme.h613
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_mlme_ext.h879
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_mp.h495
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_mp_ioctl.h340
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_mp_phy_regdef.h1084
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_p2p.h135
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_pwrctrl.h271
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_qos.h30
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_recv.h365
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_rf.h146
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_security.h383
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_sreset.h50
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_version.h1
-rw-r--r--drivers/staging/rtl8188eu/include/rtw_xmit.h379
-rw-r--r--drivers/staging/rtl8188eu/include/sta_info.h384
-rw-r--r--drivers/staging/rtl8188eu/include/usb_hal.h26
-rw-r--r--drivers/staging/rtl8188eu/include/usb_ops.h114
-rw-r--r--drivers/staging/rtl8188eu/include/usb_ops_linux.h55
-rw-r--r--drivers/staging/rtl8188eu/include/usb_osintf.h45
-rw-r--r--drivers/staging/rtl8188eu/include/usb_vendor_req.h52
-rw-r--r--drivers/staging/rtl8188eu/include/wifi.h1127
-rw-r--r--drivers/staging/rtl8188eu/include/wlan_bssdef.h347
-rw-r--r--drivers/staging/rtl8188eu/include/xmit_osdep.h61
-rw-r--r--drivers/staging/rtl8188eu/os_dep/ioctl_linux.c8211
-rw-r--r--drivers/staging/rtl8188eu/os_dep/mlme_linux.c239
-rw-r--r--drivers/staging/rtl8188eu/os_dep/os_intfs.c1250
-rw-r--r--drivers/staging/rtl8188eu/os_dep/osdep_service.c444
-rw-r--r--drivers/staging/rtl8188eu/os_dep/recv_linux.c219
-rw-r--r--drivers/staging/rtl8188eu/os_dep/rtw_android.c290
-rw-r--r--drivers/staging/rtl8188eu/os_dep/usb_intf.c756
-rw-r--r--drivers/staging/rtl8188eu/os_dep/usb_ops_linux.c276
-rw-r--r--drivers/staging/rtl8188eu/os_dep/xmit_linux.c267
-rw-r--r--drivers/staging/rtl8192e/Kconfig47
-rw-r--r--drivers/staging/rtl8192e/Makefile21
-rw-r--r--drivers/staging/rtl8192e/TODO2
-rw-r--r--drivers/staging/rtl8192e/dot11d.c215
-rw-r--r--drivers/staging/rtl8192e/dot11d.h108
-rw-r--r--drivers/staging/rtl8192e/license339
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/Kconfig9
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/Makefile20
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8190P_def.h410
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.c306
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.h31
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.c384
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.h159
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_dev.c2401
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_dev.h62
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_firmware.c320
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_firmware.h72
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_hw.h453
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.c565
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.h51
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_phy.c1634
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_phy.h120
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r8192E_phyreg.h852
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/r819xE_phyreg.h915
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_cam.c303
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_cam.h46
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_core.c3123
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_core.h1082
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_crypto.h382
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_dm.c2994
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_dm.h324
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.c139
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.h29
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_ethtool.c53
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_pci.c95
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_pci.h51
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_pm.c134
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_pm.h31
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_ps.c310
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_ps.h47
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_wx.c1340
-rw-r--r--drivers/staging/rtl8192e/rtl8192e/rtl_wx.h30
-rw-r--r--drivers/staging/rtl8192e/rtl819x_BA.h77
-rw-r--r--drivers/staging/rtl8192e/rtl819x_BAProc.c565
-rw-r--r--drivers/staging/rtl8192e/rtl819x_HT.h475
-rw-r--r--drivers/staging/rtl8192e/rtl819x_HTProc.c1076
-rw-r--r--drivers/staging/rtl8192e/rtl819x_Qos.h407
-rw-r--r--drivers/staging/rtl8192e/rtl819x_TS.h73
-rw-r--r--drivers/staging/rtl8192e/rtl819x_TSProc.c549
-rw-r--r--drivers/staging/rtl8192e/rtllib.h3053
-rw-r--r--drivers/staging/rtl8192e/rtllib_crypt.c251
-rw-r--r--drivers/staging/rtl8192e/rtllib_crypt.h35
-rw-r--r--drivers/staging/rtl8192e/rtllib_crypt_ccmp.c460
-rw-r--r--drivers/staging/rtl8192e/rtllib_crypt_tkip.c769
-rw-r--r--drivers/staging/rtl8192e/rtllib_crypt_wep.c287
-rw-r--r--drivers/staging/rtl8192e/rtllib_debug.h88
-rw-r--r--drivers/staging/rtl8192e/rtllib_endianfree.h122
-rw-r--r--drivers/staging/rtl8192e/rtllib_module.c272
-rw-r--r--drivers/staging/rtl8192e/rtllib_rx.c2729
-rw-r--r--drivers/staging/rtl8192e/rtllib_softmac.c3739
-rw-r--r--drivers/staging/rtl8192e/rtllib_softmac_wx.c661
-rw-r--r--drivers/staging/rtl8192e/rtllib_tx.c968
-rw-r--r--drivers/staging/rtl8192e/rtllib_wx.c879
-rw-r--r--drivers/staging/rtl8192ee/Kconfig15
-rw-r--r--drivers/staging/rtl8192ee/Makefile40
-rw-r--r--drivers/staging/rtl8192ee/TODO12
-rw-r--r--drivers/staging/rtl8192ee/base.c1852
-rw-r--r--drivers/staging/rtl8192ee/base.h163
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbt_precomp.h50
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8192e2ant.c4110
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8192e2ant.h161
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8723b1ant.c3146
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8723b1ant.h160
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8723b2ant.c3929
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8723b2ant.h145
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8821a1ant.c2780
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8821a1ant.h158
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8821a2ant.c3438
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtc8821a2ant.h179
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtcoutsrc.c1297
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/halbtcoutsrc.h537
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/rtl_btc.c194
-rw-r--r--drivers/staging/rtl8192ee/btcoexist/rtl_btc.h62
-rw-r--r--drivers/staging/rtl8192ee/cam.c337
-rw-r--r--drivers/staging/rtl8192ee/cam.h52
-rw-r--r--drivers/staging/rtl8192ee/compat.h70
-rw-r--r--drivers/staging/rtl8192ee/core.c1600
-rw-r--r--drivers/staging/rtl8192ee/core.h39
-rw-r--r--drivers/staging/rtl8192ee/debug.c978
-rw-r--r--drivers/staging/rtl8192ee/debug.h221
-rw-r--r--drivers/staging/rtl8192ee/efuse.c1233
-rw-r--r--drivers/staging/rtl8192ee/efuse.h127
-rw-r--r--drivers/staging/rtl8192ee/pci.c2397
-rw-r--r--drivers/staging/rtl8192ee/pci.h342
-rw-r--r--drivers/staging/rtl8192ee/ps.c983
-rw-r--r--drivers/staging/rtl8192ee/ps.h52
-rw-r--r--drivers/staging/rtl8192ee/rc.c288
-rw-r--r--drivers/staging/rtl8192ee/rc.h47
-rw-r--r--drivers/staging/rtl8192ee/regd.c448
-rw-r--r--drivers/staging/rtl8192ee/regd.h63
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/def.h106
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/dm.c1258
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/dm.h343
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/fw.c945
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/fw.h213
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/hw.c2544
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/hw.h67
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/led.c134
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/led.h37
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/phy.c3282
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/phy.h154
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/pwrseq.c108
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/pwrseq.h355
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/pwrseqcmd.c139
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/pwrseqcmd.h69
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/reg.h2240
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/rf.c150
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/rf.h39
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/sw.c428
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/sw.h39
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/table.c882
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/table.h48
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/trx.c1286
-rw-r--r--drivers/staging/rtl8192ee/rtl8192ee/trx.h877
-rw-r--r--drivers/staging/rtl8192ee/stats.c290
-rw-r--r--drivers/staging/rtl8192ee/stats.h43
-rw-r--r--drivers/staging/rtl8192ee/wifi.h2644
-rw-r--r--drivers/staging/rtl8192u/Kconfig8
-rw-r--r--drivers/staging/rtl8192u/Makefile32
-rw-r--r--drivers/staging/rtl8192u/authors1
-rw-r--r--drivers/staging/rtl8192u/changes4
-rw-r--r--drivers/staging/rtl8192u/copying340
-rw-r--r--drivers/staging/rtl8192u/ieee80211/Makefile27
-rw-r--r--drivers/staging/rtl8192u/ieee80211/dot11d.c175
-rw-r--r--drivers/staging/rtl8192u/ieee80211/dot11d.h100
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211.h2614
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_crypt.c241
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_crypt.h86
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_ccmp.c475
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_tkip.c778
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_wep.c295
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_module.c309
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_rx.c2643
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_softmac.c3219
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_softmac_wx.c605
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c916
-rw-r--r--drivers/staging/rtl8192u/ieee80211/ieee80211_wx.c852
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_BA.h67
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_BAProc.c740
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_HT.h480
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_HTProc.c1418
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_Qos.h565
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_TS.h55
-rw-r--r--drivers/staging/rtl8192u/ieee80211/rtl819x_TSProc.c623
-rw-r--r--drivers/staging/rtl8192u/r8180_93cx6.c154
-rw-r--r--drivers/staging/rtl8192u/r8180_93cx6.h40
-rw-r--r--drivers/staging/rtl8192u/r8190_rtl8256.c311
-rw-r--r--drivers/staging/rtl8192u/r8190_rtl8256.h23
-rw-r--r--drivers/staging/rtl8192u/r8192U.h1198
-rw-r--r--drivers/staging/rtl8192u/r8192U_core.c5210
-rw-r--r--drivers/staging/rtl8192u/r8192U_dm.c3570
-rw-r--r--drivers/staging/rtl8192u/r8192U_dm.h239
-rw-r--r--drivers/staging/rtl8192u/r8192U_hw.h412
-rw-r--r--drivers/staging/rtl8192u/r8192U_wx.c1013
-rw-r--r--drivers/staging/rtl8192u/r8192U_wx.h23
-rw-r--r--drivers/staging/rtl8192u/r819xU_cmdpkt.c571
-rw-r--r--drivers/staging/rtl8192u/r819xU_cmdpkt.h191
-rw-r--r--drivers/staging/rtl8192u/r819xU_firmware.c345
-rw-r--r--drivers/staging/rtl8192u/r819xU_firmware.h26
-rw-r--r--drivers/staging/rtl8192u/r819xU_firmware_img.c548
-rw-r--r--drivers/staging/rtl8192u/r819xU_firmware_img.h28
-rw-r--r--drivers/staging/rtl8192u/r819xU_phy.c1811
-rw-r--r--drivers/staging/rtl8192u/r819xU_phy.h92
-rw-r--r--drivers/staging/rtl8192u/r819xU_phyreg.h871
-rw-r--r--drivers/staging/rtl8712/Kconfig17
-rw-r--r--drivers/staging/rtl8712/Makefile34
-rw-r--r--drivers/staging/rtl8712/TODO13
-rw-r--r--drivers/staging/rtl8712/basic_types.h48
-rw-r--r--drivers/staging/rtl8712/drv_types.h194
-rw-r--r--drivers/staging/rtl8712/ethernet.h33
-rw-r--r--drivers/staging/rtl8712/hal_init.c404
-rw-r--r--drivers/staging/rtl8712/ieee80211.c421
-rw-r--r--drivers/staging/rtl8712/ieee80211.h796
-rw-r--r--drivers/staging/rtl8712/mlme_linux.c167
-rw-r--r--drivers/staging/rtl8712/mlme_osdep.h43
-rw-r--r--drivers/staging/rtl8712/mp_custom_oid.h299
-rw-r--r--drivers/staging/rtl8712/os_intfs.c479
-rw-r--r--drivers/staging/rtl8712/osdep_intf.h44
-rw-r--r--drivers/staging/rtl8712/osdep_service.h208
-rw-r--r--drivers/staging/rtl8712/recv_linux.c168
-rw-r--r--drivers/staging/rtl8712/recv_osdep.h52
-rw-r--r--drivers/staging/rtl8712/rtl8712_bitdef.h40
-rw-r--r--drivers/staging/rtl8712/rtl8712_cmd.c478
-rw-r--r--drivers/staging/rtl8712/rtl8712_cmd.h244
-rw-r--r--drivers/staging/rtl8712/rtl8712_cmdctrl_bitdef.h108
-rw-r--r--drivers/staging/rtl8712/rtl8712_cmdctrl_regdef.h34
-rw-r--r--drivers/staging/rtl8712/rtl8712_debugctrl_bitdef.h55
-rw-r--r--drivers/staging/rtl8712/rtl8712_debugctrl_regdef.h47
-rw-r--r--drivers/staging/rtl8712/rtl8712_edcasetting_bitdef.h77
-rw-r--r--drivers/staging/rtl8712/rtl8712_edcasetting_regdef.h37
-rw-r--r--drivers/staging/rtl8712/rtl8712_efuse.c575
-rw-r--r--drivers/staging/rtl8712/rtl8712_efuse.h43
-rw-r--r--drivers/staging/rtl8712/rtl8712_event.h99
-rw-r--r--drivers/staging/rtl8712/rtl8712_fifoctrl_bitdef.h145
-rw-r--r--drivers/staging/rtl8712/rtl8712_fifoctrl_regdef.h76
-rw-r--r--drivers/staging/rtl8712/rtl8712_gp_bitdef.h79
-rw-r--r--drivers/staging/rtl8712/rtl8712_gp_regdef.h42
-rw-r--r--drivers/staging/rtl8712/rtl8712_hal.h150
-rw-r--r--drivers/staging/rtl8712/rtl8712_interrupt_bitdef.h58
-rw-r--r--drivers/staging/rtl8712/rtl8712_io.c151
-rw-r--r--drivers/staging/rtl8712/rtl8712_led.c1811
-rw-r--r--drivers/staging/rtl8712/rtl8712_macsetting_bitdef.h47
-rw-r--r--drivers/staging/rtl8712/rtl8712_macsetting_regdef.h35
-rw-r--r--drivers/staging/rtl8712/rtl8712_powersave_bitdef.h52
-rw-r--r--drivers/staging/rtl8712/rtl8712_powersave_regdef.h39
-rw-r--r--drivers/staging/rtl8712/rtl8712_ratectrl_bitdef.h49
-rw-r--r--drivers/staging/rtl8712/rtl8712_ratectrl_regdef.h56
-rw-r--r--drivers/staging/rtl8712/rtl8712_recv.c1130
-rw-r--r--drivers/staging/rtl8712/rtl8712_recv.h157
-rw-r--r--drivers/staging/rtl8712/rtl8712_regdef.h44
-rw-r--r--drivers/staging/rtl8712/rtl8712_security_bitdef.h48
-rw-r--r--drivers/staging/rtl8712/rtl8712_spec.h135
-rw-r--r--drivers/staging/rtl8712/rtl8712_syscfg_bitdef.h170
-rw-r--r--drivers/staging/rtl8712/rtl8712_syscfg_regdef.h56
-rw-r--r--drivers/staging/rtl8712/rtl8712_timectrl_bitdef.h63
-rw-r--r--drivers/staging/rtl8712/rtl8712_timectrl_regdef.h39
-rw-r--r--drivers/staging/rtl8712/rtl8712_wmac_bitdef.h62
-rw-r--r--drivers/staging/rtl8712/rtl8712_wmac_regdef.h49
-rw-r--r--drivers/staging/rtl8712/rtl8712_xmit.c759
-rw-r--r--drivers/staging/rtl8712/rtl8712_xmit.h123
-rw-r--r--drivers/staging/rtl8712/rtl871x_cmd.c1048
-rw-r--r--drivers/staging/rtl8712/rtl871x_cmd.h790
-rw-r--r--drivers/staging/rtl8712/rtl871x_debug.h167
-rw-r--r--drivers/staging/rtl8712/rtl871x_eeprom.c233
-rw-r--r--drivers/staging/rtl8712/rtl871x_eeprom.h101
-rw-r--r--drivers/staging/rtl8712/rtl871x_event.h120
-rw-r--r--drivers/staging/rtl8712/rtl871x_ht.h44
-rw-r--r--drivers/staging/rtl8712/rtl871x_io.c161
-rw-r--r--drivers/staging/rtl8712/rtl871x_io.h257
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl.h97
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl_linux.c2372
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl_rtl.c536
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl_rtl.h121
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl_set.c376
-rw-r--r--drivers/staging/rtl8712/rtl871x_ioctl_set.h57
-rw-r--r--drivers/staging/rtl8712/rtl871x_led.h124
-rw-r--r--drivers/staging/rtl8712/rtl871x_mlme.c1819
-rw-r--r--drivers/staging/rtl8712/rtl871x_mlme.h232
-rw-r--r--drivers/staging/rtl8712/rtl871x_mp.c747
-rw-r--r--drivers/staging/rtl8712/rtl871x_mp.h286
-rw-r--r--drivers/staging/rtl8712/rtl871x_mp_ioctl.c1457
-rw-r--r--drivers/staging/rtl8712/rtl871x_mp_ioctl.h480
-rw-r--r--drivers/staging/rtl8712/rtl871x_mp_phy_regdef.h1025
-rw-r--r--drivers/staging/rtl8712/rtl871x_pwrctrl.c247
-rw-r--r--drivers/staging/rtl8712/rtl871x_pwrctrl.h131
-rw-r--r--drivers/staging/rtl8712/rtl871x_recv.c696
-rw-r--r--drivers/staging/rtl8712/rtl871x_recv.h219
-rw-r--r--drivers/staging/rtl8712/rtl871x_rf.h68
-rw-r--r--drivers/staging/rtl8712/rtl871x_security.c1407
-rw-r--r--drivers/staging/rtl8712/rtl871x_security.h222
-rw-r--r--drivers/staging/rtl8712/rtl871x_sta_mgt.c296
-rw-r--r--drivers/staging/rtl8712/rtl871x_wlan_sme.h47
-rw-r--r--drivers/staging/rtl8712/rtl871x_xmit.c1062
-rw-r--r--drivers/staging/rtl8712/rtl871x_xmit.h302
-rw-r--r--drivers/staging/rtl8712/sta_info.h146
-rw-r--r--drivers/staging/rtl8712/usb_halinit.c317
-rw-r--r--drivers/staging/rtl8712/usb_intf.c656
-rw-r--r--drivers/staging/rtl8712/usb_ops.c200
-rw-r--r--drivers/staging/rtl8712/usb_ops.h50
-rw-r--r--drivers/staging/rtl8712/usb_ops_linux.c525
-rw-r--r--drivers/staging/rtl8712/usb_osintf.h47
-rw-r--r--drivers/staging/rtl8712/wifi.h617
-rw-r--r--drivers/staging/rtl8712/wlan_bssdef.h267
-rw-r--r--drivers/staging/rtl8712/xmit_linux.c200
-rw-r--r--drivers/staging/rtl8712/xmit_osdep.h64
-rw-r--r--drivers/staging/rtl8723au/Kconfig30
-rw-r--r--drivers/staging/rtl8723au/Makefile56
-rw-r--r--drivers/staging/rtl8723au/TODO13
-rw-r--r--drivers/staging/rtl8723au/core/rtw_ap.c2028
-rw-r--r--drivers/staging/rtl8723au/core/rtw_cmd.c1578
-rw-r--r--drivers/staging/rtl8723au/core/rtw_efuse.c779
-rw-r--r--drivers/staging/rtl8723au/core/rtw_ieee80211.c1068
-rw-r--r--drivers/staging/rtl8723au/core/rtw_ioctl_set.c425
-rw-r--r--drivers/staging/rtl8723au/core/rtw_led.c1893
-rw-r--r--drivers/staging/rtl8723au/core/rtw_mlme.c2434
-rw-r--r--drivers/staging/rtl8723au/core/rtw_mlme_ext.c6591
-rw-r--r--drivers/staging/rtl8723au/core/rtw_pwrctrl.c649
-rw-r--r--drivers/staging/rtl8723au/core/rtw_recv.c2414
-rw-r--r--drivers/staging/rtl8723au/core/rtw_security.c1649
-rw-r--r--drivers/staging/rtl8723au/core/rtw_sreset.c262
-rw-r--r--drivers/staging/rtl8723au/core/rtw_sta_mgt.c442
-rw-r--r--drivers/staging/rtl8723au/core/rtw_wlan_util.c1705
-rw-r--r--drivers/staging/rtl8723au/core/rtw_xmit.c2437
-rw-r--r--drivers/staging/rtl8723au/hal/Hal8723PwrSeq.c80
-rw-r--r--drivers/staging/rtl8723au/hal/Hal8723UHWImg_CE.c136
-rw-r--r--drivers/staging/rtl8723au/hal/HalDMOutSrc8723A_CE.c1088
-rw-r--r--drivers/staging/rtl8723au/hal/HalHWImg8723A_BB.c726
-rw-r--r--drivers/staging/rtl8723au/hal/HalHWImg8723A_MAC.c188
-rw-r--r--drivers/staging/rtl8723au/hal/HalHWImg8723A_RF.c259
-rw-r--r--drivers/staging/rtl8723au/hal/HalPwrSeqCmd.c165
-rw-r--r--drivers/staging/rtl8723au/hal/hal_com.c927
-rw-r--r--drivers/staging/rtl8723au/hal/hal_intf.c42
-rw-r--r--drivers/staging/rtl8723au/hal/odm.c2009
-rw-r--r--drivers/staging/rtl8723au/hal/odm_HWConfig.c472
-rw-r--r--drivers/staging/rtl8723au/hal/odm_RegConfig8723A.c162
-rw-r--r--drivers/staging/rtl8723au/hal/odm_debug.c24
-rw-r--r--drivers/staging/rtl8723au/hal/odm_interface.c139
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_bt-coexist.c11381
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_cmd.c786
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_dm.c270
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_hal_init.c2571
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_phycfg.c1145
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_rf6052.c511
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_rxdesc.c69
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_sreset.c74
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723a_xmit.c52
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723au_led.c124
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723au_recv.c267
-rw-r--r--drivers/staging/rtl8723au/hal/rtl8723au_xmit.c529
-rw-r--r--drivers/staging/rtl8723au/hal/usb_halinit.c1649
-rw-r--r--drivers/staging/rtl8723au/hal/usb_ops_linux.c840
-rw-r--r--drivers/staging/rtl8723au/include/Hal8723APhyCfg.h230
-rw-r--r--drivers/staging/rtl8723au/include/Hal8723APhyReg.h1078
-rw-r--r--drivers/staging/rtl8723au/include/Hal8723PwrSeq.h150
-rw-r--r--drivers/staging/rtl8723au/include/Hal8723UHWImg_CE.h29
-rw-r--r--drivers/staging/rtl8723au/include/HalDMOutSrc8723A.h64
-rw-r--r--drivers/staging/rtl8723au/include/HalHWImg8723A_BB.h44
-rw-r--r--drivers/staging/rtl8723au/include/HalHWImg8723A_FW.h28
-rw-r--r--drivers/staging/rtl8723au/include/HalHWImg8723A_MAC.h26
-rw-r--r--drivers/staging/rtl8723au/include/HalHWImg8723A_RF.h25
-rw-r--r--drivers/staging/rtl8723au/include/HalPwrSeqCmd.h130
-rw-r--r--drivers/staging/rtl8723au/include/HalVerDef.h136
-rw-r--r--drivers/staging/rtl8723au/include/drv_types.h279
-rw-r--r--drivers/staging/rtl8723au/include/hal_com.h214
-rw-r--r--drivers/staging/rtl8723au/include/hal_intf.h123
-rw-r--r--drivers/staging/rtl8723au/include/ieee80211.h455
-rw-r--r--drivers/staging/rtl8723au/include/ioctl_cfg80211.h69
-rw-r--r--drivers/staging/rtl8723au/include/mlme_osdep.h24
-rw-r--r--drivers/staging/rtl8723au/include/odm.h1082
-rw-r--r--drivers/staging/rtl8723au/include/odm_HWConfig.h172
-rw-r--r--drivers/staging/rtl8723au/include/odm_RegConfig8723A.h34
-rw-r--r--drivers/staging/rtl8723au/include/odm_RegDefine11AC.h49
-rw-r--r--drivers/staging/rtl8723au/include/odm_RegDefine11N.h165
-rw-r--r--drivers/staging/rtl8723au/include/odm_debug.h139
-rw-r--r--drivers/staging/rtl8723au/include/odm_interface.h74
-rw-r--r--drivers/staging/rtl8723au/include/odm_precomp.h50
-rw-r--r--drivers/staging/rtl8723au/include/odm_reg.h111
-rw-r--r--drivers/staging/rtl8723au/include/osdep_intf.h48
-rw-r--r--drivers/staging/rtl8723au/include/osdep_service.h88
-rw-r--r--drivers/staging/rtl8723au/include/recv_osdep.h36
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_bt-coexist.h1614
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_bt_intf.h69
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_cmd.h158
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_dm.h142
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_hal.h557
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_led.h30
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_pg.h98
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_recv.h69
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_rf.h58
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_spec.h2158
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_sreset.h25
-rw-r--r--drivers/staging/rtl8723au/include/rtl8723a_xmit.h227
-rw-r--r--drivers/staging/rtl8723au/include/rtw_ap.h53
-rw-r--r--drivers/staging/rtl8723au/include/rtw_cmd.h821
-rw-r--r--drivers/staging/rtl8723au/include/rtw_debug.h192
-rw-r--r--drivers/staging/rtl8723au/include/rtw_eeprom.h135
-rw-r--r--drivers/staging/rtl8723au/include/rtw_efuse.h108
-rw-r--r--drivers/staging/rtl8723au/include/rtw_event.h74
-rw-r--r--drivers/staging/rtl8723au/include/rtw_ht.h43
-rw-r--r--drivers/staging/rtl8723au/include/rtw_io.h237
-rw-r--r--drivers/staging/rtl8723au/include/rtw_ioctl_set.h32
-rw-r--r--drivers/staging/rtl8723au/include/rtw_led.h181
-rw-r--r--drivers/staging/rtl8723au/include/rtw_mlme.h374
-rw-r--r--drivers/staging/rtl8723au/include/rtw_mlme_ext.h702
-rw-r--r--drivers/staging/rtl8723au/include/rtw_pwrctrl.h261
-rw-r--r--drivers/staging/rtl8723au/include/rtw_recv.h310
-rw-r--r--drivers/staging/rtl8723au/include/rtw_rf.h113
-rw-r--r--drivers/staging/rtl8723au/include/rtw_security.h334
-rw-r--r--drivers/staging/rtl8723au/include/rtw_sreset.h56
-rw-r--r--drivers/staging/rtl8723au/include/rtw_version.h1
-rw-r--r--drivers/staging/rtl8723au/include/rtw_xmit.h399
-rw-r--r--drivers/staging/rtl8723au/include/sta_info.h373
-rw-r--r--drivers/staging/rtl8723au/include/usb_ops.h68
-rw-r--r--drivers/staging/rtl8723au/include/usb_ops_linux.h41
-rw-r--r--drivers/staging/rtl8723au/include/wifi.h155
-rw-r--r--drivers/staging/rtl8723au/include/wlan_bssdef.h140
-rw-r--r--drivers/staging/rtl8723au/include/xmit_osdep.h38
-rw-r--r--drivers/staging/rtl8723au/os_dep/ioctl_cfg80211.c3454
-rw-r--r--drivers/staging/rtl8723au/os_dep/mlme_linux.c82
-rw-r--r--drivers/staging/rtl8723au/os_dep/os_intfs.c883
-rw-r--r--drivers/staging/rtl8723au/os_dep/recv_linux.c165
-rw-r--r--drivers/staging/rtl8723au/os_dep/usb_intf.c803
-rw-r--r--drivers/staging/rtl8723au/os_dep/usb_ops_linux.c264
-rw-r--r--drivers/staging/rtl8723au/os_dep/xmit_linux.c154
-rw-r--r--drivers/staging/rtl8821ae/Kconfig11
-rw-r--r--drivers/staging/rtl8821ae/Makefile35
-rw-r--r--drivers/staging/rtl8821ae/TODO10
-rw-r--r--drivers/staging/rtl8821ae/base.c1831
-rw-r--r--drivers/staging/rtl8821ae/base.h159
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/HalBtc8812a1Ant.c3976
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/HalBtc8812a1Ant.h205
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/habtc8723a1ant.c1614
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/habtc8723a1ant.h176
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbt_precomp.h99
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8192e1ant.c3891
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8192e1ant.h226
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8192e2ant.c4118
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8192e2ant.h160
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723a2ant.c3780
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723a2ant.h179
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723b1ant.c3892
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723b1ant.h175
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723b2ant.c4200
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtc8723b2ant.h145
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtcoutsrc.c1130
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/halbtcoutsrc.h549
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/rtl_btc.c236
-rw-r--r--drivers/staging/rtl8821ae/btcoexist/rtl_btc.h66
-rw-r--r--drivers/staging/rtl8821ae/cam.c352
-rw-r--r--drivers/staging/rtl8821ae/cam.h56
-rw-r--r--drivers/staging/rtl8821ae/compat.h68
-rw-r--r--drivers/staging/rtl8821ae/core.c1314
-rw-r--r--drivers/staging/rtl8821ae/core.h43
-rw-r--r--drivers/staging/rtl8821ae/debug.c983
-rw-r--r--drivers/staging/rtl8821ae/debug.h227
-rw-r--r--drivers/staging/rtl8821ae/efuse.c1283
-rw-r--r--drivers/staging/rtl8821ae/efuse.h130
-rw-r--r--drivers/staging/rtl8821ae/pci.c2429
-rw-r--r--drivers/staging/rtl8821ae/pci.h348
-rw-r--r--drivers/staging/rtl8821ae/ps.c999
-rw-r--r--drivers/staging/rtl8821ae/ps.h55
-rw-r--r--drivers/staging/rtl8821ae/rc.c290
-rw-r--r--drivers/staging/rtl8821ae/rc.h47
-rw-r--r--drivers/staging/rtl8821ae/regd.c451
-rw-r--r--drivers/staging/rtl8821ae/regd.h67
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/btc.h87
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/def.h442
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/dm.c3045
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/dm.h426
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/fw.c1349
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/fw.h321
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hal_bt_coexist.c519
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hal_bt_coexist.h169
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hal_btc.c2069
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hal_btc.h160
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hw.c3347
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/hw.h72
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/led.c239
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/led.h40
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/phy.c5525
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/phy.h258
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/pwrseq.c199
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/pwrseq.h413
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/pwrseqcmd.c140
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/pwrseqcmd.h71
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/reg.h2427
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/rf.c464
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/rf.h46
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/sw.c478
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/sw.h39
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/table.c4002
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/table.h62
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/trx.c1005
-rw-r--r--drivers/staging/rtl8821ae/rtl8821ae/trx.h630
-rw-r--r--drivers/staging/rtl8821ae/stats.c281
-rw-r--r--drivers/staging/rtl8821ae/stats.h46
-rw-r--r--drivers/staging/rtl8821ae/wifi.h2534
-rw-r--r--drivers/staging/rts5208/Kconfig15
-rw-r--r--drivers/staging/rts5208/Makefile6
-rw-r--r--drivers/staging/rts5208/TODO7
-rw-r--r--drivers/staging/rts5208/debug.h43
-rw-r--r--drivers/staging/rts5208/general.c35
-rw-r--r--drivers/staging/rts5208/general.h31
-rw-r--r--drivers/staging/rts5208/ms.c4208
-rw-r--r--drivers/staging/rts5208/ms.h227
-rw-r--r--drivers/staging/rts5208/rtsx.c1073
-rw-r--r--drivers/staging/rts5208/rtsx.h185
-rw-r--r--drivers/staging/rts5208/rtsx_card.c1149
-rw-r--r--drivers/staging/rts5208/rtsx_card.h1098
-rw-r--r--drivers/staging/rts5208/rtsx_chip.c1979
-rw-r--r--drivers/staging/rts5208/rtsx_chip.h1002
-rw-r--r--drivers/staging/rts5208/rtsx_scsi.c3370
-rw-r--r--drivers/staging/rts5208/rtsx_scsi.h143
-rw-r--r--drivers/staging/rts5208/rtsx_sys.h50
-rw-r--r--drivers/staging/rts5208/rtsx_transport.c769
-rw-r--r--drivers/staging/rts5208/rtsx_transport.h66
-rw-r--r--drivers/staging/rts5208/sd.c4525
-rw-r--r--drivers/staging/rts5208/sd.h301
-rw-r--r--drivers/staging/rts5208/spi.c877
-rw-r--r--drivers/staging/rts5208/spi.h65
-rw-r--r--drivers/staging/rts5208/trace.h93
-rw-r--r--drivers/staging/rts5208/xd.c2088
-rw-r--r--drivers/staging/rts5208/xd.h188
-rw-r--r--drivers/staging/sep/Kconfig11
-rw-r--r--drivers/staging/sep/Makefile3
-rw-r--r--drivers/staging/sep/TODO3
-rw-r--r--drivers/staging/sep/sep_crypto.c3962
-rw-r--r--drivers/staging/sep/sep_crypto.h359
-rw-r--r--drivers/staging/sep/sep_dev.h162
-rw-r--r--drivers/staging/sep/sep_driver_api.h402
-rw-r--r--drivers/staging/sep/sep_driver_config.h298
-rw-r--r--drivers/staging/sep/sep_driver_hw_defs.h56
-rw-r--r--drivers/staging/sep/sep_main.c4452
-rw-r--r--drivers/staging/sep/sep_trace_events.h193
-rw-r--r--drivers/staging/serqt_usb2/Kconfig9
-rw-r--r--drivers/staging/serqt_usb2/Makefile1
-rw-r--r--drivers/staging/serqt_usb2/serqt_usb2.c1528
-rw-r--r--drivers/staging/silicom/Kconfig45
-rw-r--r--drivers/staging/silicom/Makefile6
-rw-r--r--drivers/staging/silicom/README14
-rw-r--r--drivers/staging/silicom/TODO8
-rw-r--r--drivers/staging/silicom/bits.h56
-rw-r--r--drivers/staging/silicom/bp_ioctl.h140
-rw-r--r--drivers/staging/silicom/bp_mod.h711
-rw-r--r--drivers/staging/silicom/bpctl_mod.c7530
-rw-r--r--drivers/staging/silicom/bypass.h202
-rw-r--r--drivers/staging/silicom/bypasslib/Makefile6
-rw-r--r--drivers/staging/silicom/bypasslib/bp_ioctl.h198
-rw-r--r--drivers/staging/silicom/bypasslib/bplibk.h36
-rw-r--r--drivers/staging/silicom/bypasslib/bypass.c536
-rw-r--r--drivers/staging/silicom/bypasslib/libbp_sd.h532
-rw-r--r--drivers/staging/silicom/libbp_sd.h550
-rw-r--r--drivers/staging/skein/Kconfig32
-rw-r--r--drivers/staging/skein/Makefile9
-rw-r--r--drivers/staging/skein/TODO8
-rw-r--r--drivers/staging/skein/skein.c883
-rw-r--r--drivers/staging/skein/skein.h346
-rw-r--r--drivers/staging/skein/skein_api.c239
-rw-r--r--drivers/staging/skein/skein_api.h230
-rw-r--r--drivers/staging/skein/skein_block.c777
-rw-r--r--drivers/staging/skein/skein_block.h22
-rw-r--r--drivers/staging/skein/skein_iv.h186
-rw-r--r--drivers/staging/skein/threefish_api.c77
-rw-r--r--drivers/staging/skein/threefish_api.h170
-rw-r--r--drivers/staging/skein/threefish_block.c8258
-rw-r--r--drivers/staging/slicoss/Kconfig14
-rw-r--r--drivers/staging/slicoss/Makefile1
-rw-r--r--drivers/staging/slicoss/README7
-rw-r--r--drivers/staging/slicoss/TODO37
-rw-r--r--drivers/staging/slicoss/slic.h527
-rw-r--r--drivers/staging/slicoss/slichw.h827
-rw-r--r--drivers/staging/slicoss/slicoss.c3207
-rw-r--r--drivers/staging/speakup/DefaultKeyAssignments46
-rw-r--r--drivers/staging/speakup/Kconfig199
-rw-r--r--drivers/staging/speakup/Makefile30
-rw-r--r--drivers/staging/speakup/TODO47
-rw-r--r--drivers/staging/speakup/buffers.c106
-rw-r--r--drivers/staging/speakup/devsynth.c95
-rw-r--r--drivers/staging/speakup/fakekey.c99
-rw-r--r--drivers/staging/speakup/i18n.c629
-rw-r--r--drivers/staging/speakup/i18n.h228
-rw-r--r--drivers/staging/speakup/keyhelp.c219
-rw-r--r--drivers/staging/speakup/kobjects.c1038
-rw-r--r--drivers/staging/speakup/main.c2366
-rw-r--r--drivers/staging/speakup/selection.c185
-rw-r--r--drivers/staging/speakup/serialio.c218
-rw-r--r--drivers/staging/speakup/serialio.h39
-rw-r--r--drivers/staging/speakup/speakup.h121
-rw-r--r--drivers/staging/speakup/speakup_acnt.h16
-rw-r--r--drivers/staging/speakup/speakup_acntpc.c335
-rw-r--r--drivers/staging/speakup/speakup_acntsa.c163
-rw-r--r--drivers/staging/speakup/speakup_apollo.c227
-rw-r--r--drivers/staging/speakup/speakup_audptr.c195
-rw-r--r--drivers/staging/speakup/speakup_bns.c147
-rw-r--r--drivers/staging/speakup/speakup_decext.c253
-rw-r--r--drivers/staging/speakup/speakup_decpc.c504
-rw-r--r--drivers/staging/speakup/speakup_dectlk.c324
-rw-r--r--drivers/staging/speakup/speakup_dtlk.c402
-rw-r--r--drivers/staging/speakup/speakup_dtlk.h54
-rw-r--r--drivers/staging/speakup/speakup_dummy.c148
-rw-r--r--drivers/staging/speakup/speakup_keypc.c335
-rw-r--r--drivers/staging/speakup/speakup_ltlk.c194
-rw-r--r--drivers/staging/speakup/speakup_soft.c367
-rw-r--r--drivers/staging/speakup/speakup_spkout.c165
-rw-r--r--drivers/staging/speakup/speakup_txprt.c147
-rw-r--r--drivers/staging/speakup/speakupmap.h65
-rw-r--r--drivers/staging/speakup/speakupmap.map93
-rw-r--r--drivers/staging/speakup/spk_priv.h80
-rw-r--r--drivers/staging/speakup/spk_priv_keyinfo.h110
-rw-r--r--drivers/staging/speakup/spk_types.h193
-rw-r--r--drivers/staging/speakup/spkguide.txt1575
-rw-r--r--drivers/staging/speakup/synth.c478
-rw-r--r--drivers/staging/speakup/thread.c58
-rw-r--r--drivers/staging/speakup/varhandlers.c327
-rw-r--r--drivers/staging/staging.c19
-rw-r--r--drivers/staging/ste_rmi4/Kconfig9
-rw-r--r--drivers/staging/ste_rmi4/Makefile4
-rw-r--r--drivers/staging/ste_rmi4/TODO7
-rw-r--r--drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c1143
-rw-r--r--drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h46
-rw-r--r--drivers/staging/tidspbridge/Documentation/CONTRIBUTORS45
-rw-r--r--drivers/staging/tidspbridge/Documentation/README70
-rw-r--r--drivers/staging/tidspbridge/Documentation/error-codes157
-rw-r--r--drivers/staging/tidspbridge/Kconfig69
-rw-r--r--drivers/staging/tidspbridge/Makefile32
-rw-r--r--drivers/staging/tidspbridge/TODO18
-rw-r--r--drivers/staging/tidspbridge/core/_cmm.h45
-rw-r--r--drivers/staging/tidspbridge/core/_deh.h35
-rw-r--r--drivers/staging/tidspbridge/core/_msg_sm.h142
-rw-r--r--drivers/staging/tidspbridge/core/_tiomap.h382
-rw-r--r--drivers/staging/tidspbridge/core/_tiomap_pwr.h85
-rw-r--r--drivers/staging/tidspbridge/core/chnl_sm.c907
-rw-r--r--drivers/staging/tidspbridge/core/dsp-clock.c391
-rw-r--r--drivers/staging/tidspbridge/core/io_sm.c2245
-rw-r--r--drivers/staging/tidspbridge/core/msg_sm.c564
-rw-r--r--drivers/staging/tidspbridge/core/sync.c121
-rw-r--r--drivers/staging/tidspbridge/core/tiomap3430.c1813
-rw-r--r--drivers/staging/tidspbridge/core/tiomap3430_pwr.c556
-rw-r--r--drivers/staging/tidspbridge/core/tiomap_io.c438
-rw-r--r--drivers/staging/tidspbridge/core/tiomap_io.h104
-rw-r--r--drivers/staging/tidspbridge/core/ue_deh.c272
-rw-r--r--drivers/staging/tidspbridge/core/wdt.c143
-rw-r--r--drivers/staging/tidspbridge/dynload/cload.c1952
-rw-r--r--drivers/staging/tidspbridge/dynload/dload_internal.h344
-rw-r--r--drivers/staging/tidspbridge/dynload/doff.h354
-rw-r--r--drivers/staging/tidspbridge/dynload/getsection.c407
-rw-r--r--drivers/staging/tidspbridge/dynload/header.h49
-rw-r--r--drivers/staging/tidspbridge/dynload/module_list.h159
-rw-r--r--drivers/staging/tidspbridge/dynload/params.h226
-rw-r--r--drivers/staging/tidspbridge/dynload/reloc.c484
-rw-r--r--drivers/staging/tidspbridge/dynload/reloc_table.h102
-rw-r--r--drivers/staging/tidspbridge/dynload/reloc_table_c6000.c257
-rw-r--r--drivers/staging/tidspbridge/dynload/tramp.c1143
-rw-r--r--drivers/staging/tidspbridge/dynload/tramp_table_c6000.c164
-rw-r--r--drivers/staging/tidspbridge/gen/gh.c141
-rw-r--r--drivers/staging/tidspbridge/hw/EasiGlobal.h41
-rw-r--r--drivers/staging/tidspbridge/hw/MMUAccInt.h76
-rw-r--r--drivers/staging/tidspbridge/hw/MMURegAcM.h225
-rw-r--r--drivers/staging/tidspbridge/hw/hw_defs.h58
-rw-r--r--drivers/staging/tidspbridge/hw/hw_mmu.c487
-rw-r--r--drivers/staging/tidspbridge/hw/hw_mmu.h160
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/_chnl_sm.h177
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/brddefs.h37
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/cfgdefs.h61
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/chnl.h80
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/chnldefs.h63
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/chnlpriv.h85
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/clk.h101
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/cmm.h337
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/cmmdefs.h104
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/cod.h329
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dbdcd.h358
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dbdcddef.h78
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dbdefs.h488
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dbll.h56
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dblldefs.h431
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dev.h620
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/devdefs.h26
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/disp.h186
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dmm.h71
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/drv.h468
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspapi-ioctl.h467
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspapi.h167
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspchnl.h72
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspdefs.h1048
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspdeh.h43
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspdrv.h60
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspio.h41
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspioctl.h68
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dspmsg.h56
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/dynamic_loader.h490
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/getsection.h108
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/gh.h32
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/host_os.h57
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/io.h80
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/io_sm.h160
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/mbx_sh.h144
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/memdefs.h30
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/mgr.h205
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/mgrpriv.h45
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/msg.h59
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/msgdefs.h29
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/nldr.h55
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/nldrdefs.h259
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/node.h524
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/nodedefs.h28
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/nodepriv.h181
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/ntfy.h217
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/proc.h591
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/procpriv.h25
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/pwr.h113
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/resourcecleanup.h41
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/rmm.h156
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/rms_sh.h86
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/rmstypes.h24
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/strm.h306
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/strmdefs.h44
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/sync.h119
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/uuidutil.h24
-rw-r--r--drivers/staging/tidspbridge/include/dspbridge/wdt.h79
-rw-r--r--drivers/staging/tidspbridge/pmgr/chnl.c115
-rw-r--r--drivers/staging/tidspbridge/pmgr/chnlobj.h46
-rw-r--r--drivers/staging/tidspbridge/pmgr/cmm.c915
-rw-r--r--drivers/staging/tidspbridge/pmgr/cod.c537
-rw-r--r--drivers/staging/tidspbridge/pmgr/dbll.c1421
-rw-r--r--drivers/staging/tidspbridge/pmgr/dev.c969
-rw-r--r--drivers/staging/tidspbridge/pmgr/dmm.c487
-rw-r--r--drivers/staging/tidspbridge/pmgr/dspapi.c1843
-rw-r--r--drivers/staging/tidspbridge/pmgr/io.c93
-rw-r--r--drivers/staging/tidspbridge/pmgr/ioobj.h38
-rw-r--r--drivers/staging/tidspbridge/pmgr/msg.c91
-rw-r--r--drivers/staging/tidspbridge/pmgr/msgobj.h38
-rw-r--r--drivers/staging/tidspbridge/rmgr/dbdcd.c1483
-rw-r--r--drivers/staging/tidspbridge/rmgr/disp.c655
-rw-r--r--drivers/staging/tidspbridge/rmgr/drv.c816
-rw-r--r--drivers/staging/tidspbridge/rmgr/drv_interface.c648
-rw-r--r--drivers/staging/tidspbridge/rmgr/dspdrv.c139
-rw-r--r--drivers/staging/tidspbridge/rmgr/mgr.c352
-rw-r--r--drivers/staging/tidspbridge/rmgr/nldr.c1860
-rw-r--r--drivers/staging/tidspbridge/rmgr/node.c3029
-rw-r--r--drivers/staging/tidspbridge/rmgr/proc.c1833
-rw-r--r--drivers/staging/tidspbridge/rmgr/pwr.c176
-rw-r--r--drivers/staging/tidspbridge/rmgr/rmm.c456
-rw-r--r--drivers/staging/tidspbridge/rmgr/strm.c733
-rw-r--r--drivers/staging/unisys/Documentation/overview.txt174
-rw-r--r--drivers/staging/unisys/Documentation/proc-entries.txt93
-rw-r--r--drivers/staging/unisys/Kconfig20
-rw-r--r--drivers/staging/unisys/MAINTAINERS6
-rw-r--r--drivers/staging/unisys/Makefile10
-rw-r--r--drivers/staging/unisys/TODO21
-rw-r--r--drivers/staging/unisys/channels/Kconfig10
-rw-r--r--drivers/staging/unisys/channels/Makefile13
-rw-r--r--drivers/staging/unisys/channels/channel.c219
-rw-r--r--drivers/staging/unisys/channels/chanstub.c73
-rw-r--r--drivers/staging/unisys/channels/chanstub.h23
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/channel.h652
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/channel_guid.h64
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/controlframework.h77
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/controlvmchannel.h620
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/diagchannel.h427
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/iochannel.h935
-rw-r--r--drivers/staging/unisys/common-spar/include/channels/vbuschannel.h135
-rw-r--r--drivers/staging/unisys/common-spar/include/controlvmcompletionstatus.h92
-rw-r--r--drivers/staging/unisys/common-spar/include/diagnostics/appos_subsystems.h310
-rw-r--r--drivers/staging/unisys/common-spar/include/iovmcall_gnuc.h53
-rw-r--r--drivers/staging/unisys/common-spar/include/vbusdeviceinfo.h209
-rw-r--r--drivers/staging/unisys/common-spar/include/version.h46
-rw-r--r--drivers/staging/unisys/common-spar/include/vmcallinterface.h167
-rw-r--r--drivers/staging/unisys/include/commontypes.h155
-rw-r--r--drivers/staging/unisys/include/guestlinuxdebug.h182
-rw-r--r--drivers/staging/unisys/include/periodic_work.h40
-rw-r--r--drivers/staging/unisys/include/procobjecttree.h48
-rw-r--r--drivers/staging/unisys/include/sparstop.h30
-rw-r--r--drivers/staging/unisys/include/timskmod.h324
-rw-r--r--drivers/staging/unisys/include/timskmodutils.h77
-rw-r--r--drivers/staging/unisys/include/uisqueue.h441
-rw-r--r--drivers/staging/unisys/include/uisthread.h46
-rw-r--r--drivers/staging/unisys/include/uisutils.h356
-rw-r--r--drivers/staging/unisys/include/uniklog.h193
-rw-r--r--drivers/staging/unisys/include/vbushelper.h47
-rw-r--r--drivers/staging/unisys/uislib/Kconfig10
-rw-r--r--drivers/staging/unisys/uislib/Makefile17
-rw-r--r--drivers/staging/unisys/uislib/uislib.c1636
-rw-r--r--drivers/staging/unisys/uislib/uisqueue.c160
-rw-r--r--drivers/staging/unisys/uislib/uisthread.c85
-rw-r--r--drivers/staging/unisys/uislib/uisutils.c341
-rw-r--r--drivers/staging/unisys/virthba/Kconfig10
-rw-r--r--drivers/staging/unisys/virthba/Makefile16
-rw-r--r--drivers/staging/unisys/virthba/virthba.c1834
-rw-r--r--drivers/staging/unisys/virthba/virthba.h31
-rw-r--r--drivers/staging/unisys/virtpci/Kconfig10
-rw-r--r--drivers/staging/unisys/virtpci/Makefile13
-rw-r--r--drivers/staging/unisys/virtpci/virtpci.c1770
-rw-r--r--drivers/staging/unisys/virtpci/virtpci.h105
-rw-r--r--drivers/staging/unisys/visorchannel/Kconfig10
-rw-r--r--drivers/staging/unisys/visorchannel/Makefile14
-rw-r--r--drivers/staging/unisys/visorchannel/globals.h29
-rw-r--r--drivers/staging/unisys/visorchannel/visorchannel.h78
-rw-r--r--drivers/staging/unisys/visorchannel/visorchannel_funcs.c673
-rw-r--r--drivers/staging/unisys/visorchannel/visorchannel_main.c52
-rw-r--r--drivers/staging/unisys/visorchipset/Kconfig10
-rw-r--r--drivers/staging/unisys/visorchipset/Makefile18
-rw-r--r--drivers/staging/unisys/visorchipset/controlvm.h27
-rw-r--r--drivers/staging/unisys/visorchipset/controlvm_direct.c62
-rw-r--r--drivers/staging/unisys/visorchipset/file.c226
-rw-r--r--drivers/staging/unisys/visorchipset/file.h26
-rw-r--r--drivers/staging/unisys/visorchipset/globals.h45
-rw-r--r--drivers/staging/unisys/visorchipset/parser.c475
-rw-r--r--drivers/staging/unisys/visorchipset/parser.h47
-rw-r--r--drivers/staging/unisys/visorchipset/testing.h42
-rw-r--r--drivers/staging/unisys/visorchipset/visorchipset.h309
-rw-r--r--drivers/staging/unisys/visorchipset/visorchipset_main.c2955
-rw-r--r--drivers/staging/unisys/visorchipset/visorchipset_umode.h37
-rw-r--r--drivers/staging/unisys/visorutil/Kconfig10
-rw-r--r--drivers/staging/unisys/visorutil/Makefile11
-rw-r--r--drivers/staging/unisys/visorutil/charqueue.c143
-rw-r--r--drivers/staging/unisys/visorutil/charqueue.h37
-rw-r--r--drivers/staging/unisys/visorutil/easyproc.c377
-rw-r--r--drivers/staging/unisys/visorutil/easyproc.h92
-rw-r--r--drivers/staging/unisys/visorutil/memregion.h43
-rw-r--r--drivers/staging/unisys/visorutil/memregion_direct.c223
-rw-r--r--drivers/staging/unisys/visorutil/periodic_work.c237
-rw-r--r--drivers/staging/unisys/visorutil/procobjecttree.c354
-rw-r--r--drivers/staging/unisys/visorutil/visorkmodutils.c126
-rw-r--r--drivers/staging/usbip/Kconfig41
-rw-r--r--drivers/staging/usbip/Makefile10
-rw-r--r--drivers/staging/usbip/README7
-rw-r--r--drivers/staging/usbip/stub.h113
-rw-r--r--drivers/staging/usbip/stub_dev.c525
-rw-r--r--drivers/staging/usbip/stub_main.c339
-rw-r--r--drivers/staging/usbip/stub_rx.c595
-rw-r--r--drivers/staging/usbip/stub_tx.c398
-rw-r--r--drivers/staging/usbip/uapi/usbip.h26
-rw-r--r--drivers/staging/usbip/usbip_common.c776
-rw-r--r--drivers/staging/usbip/usbip_common.h335
-rw-r--r--drivers/staging/usbip/usbip_event.c128
-rw-r--r--drivers/staging/usbip/usbip_protocol.txt358
-rw-r--r--drivers/staging/usbip/userspace/.gitignore28
-rw-r--r--drivers/staging/usbip/userspace/AUTHORS3
-rw-r--r--drivers/staging/usbip/userspace/COPYING340
-rw-r--r--drivers/staging/usbip/userspace/INSTALL237
-rw-r--r--drivers/staging/usbip/userspace/Makefile.am6
-rw-r--r--drivers/staging/usbip/userspace/README202
-rwxr-xr-xdrivers/staging/usbip/userspace/autogen.sh9
-rwxr-xr-xdrivers/staging/usbip/userspace/cleanup.sh12
-rw-r--r--drivers/staging/usbip/userspace/configure.ac111
-rw-r--r--drivers/staging/usbip/userspace/doc/usbip.895
-rw-r--r--drivers/staging/usbip/userspace/doc/usbipd.891
-rw-r--r--drivers/staging/usbip/userspace/libsrc/Makefile.am8
-rw-r--r--drivers/staging/usbip/userspace/libsrc/list.h136
-rw-r--r--drivers/staging/usbip/userspace/libsrc/names.c504
-rw-r--r--drivers/staging/usbip/userspace/libsrc/names.h41
-rw-r--r--drivers/staging/usbip/userspace/libsrc/sysfs_utils.c31
-rw-r--r--drivers/staging/usbip/userspace/libsrc/sysfs_utils.h8
-rw-r--r--drivers/staging/usbip/userspace/libsrc/usbip_common.c285
-rw-r--r--drivers/staging/usbip/userspace/libsrc/usbip_common.h137
-rw-r--r--drivers/staging/usbip/userspace/libsrc/usbip_host_driver.c279
-rw-r--r--drivers/staging/usbip/userspace/libsrc/usbip_host_driver.h49
-rw-r--r--drivers/staging/usbip/userspace/libsrc/vhci_driver.c411
-rw-r--r--drivers/staging/usbip/userspace/libsrc/vhci_driver.h59
-rw-r--r--drivers/staging/usbip/userspace/src/Makefile.am11
-rw-r--r--drivers/staging/usbip/userspace/src/usbip.c201
-rw-r--r--drivers/staging/usbip/userspace/src/usbip.h40
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_attach.c241
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_bind.c214
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_detach.c110
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_list.c283
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_network.c303
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_network.h185
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_port.c57
-rw-r--r--drivers/staging/usbip/userspace/src/usbip_unbind.c141
-rw-r--r--drivers/staging/usbip/userspace/src/usbipd.c679
-rw-r--r--drivers/staging/usbip/userspace/src/utils.c52
-rw-r--r--drivers/staging/usbip/userspace/src/utils.h25
-rw-r--r--drivers/staging/usbip/vhci.h129
-rw-r--r--drivers/staging/usbip/vhci_hcd.c1172
-rw-r--r--drivers/staging/usbip/vhci_rx.c270
-rw-r--r--drivers/staging/usbip/vhci_sysfs.c252
-rw-r--r--drivers/staging/usbip/vhci_tx.c224
-rw-r--r--drivers/staging/vme/Makefile1
-rw-r--r--drivers/staging/vme/devices/Kconfig22
-rw-r--r--drivers/staging/vme/devices/Makefile8
-rw-r--r--drivers/staging/vme/devices/vme_pio2.h249
-rw-r--r--drivers/staging/vme/devices/vme_pio2_cntr.c71
-rw-r--r--drivers/staging/vme/devices/vme_pio2_core.c512
-rw-r--r--drivers/staging/vme/devices/vme_pio2_gpio.c229
-rw-r--r--drivers/staging/vme/devices/vme_user.c904
-rw-r--r--drivers/staging/vme/devices/vme_user.h58
-rw-r--r--drivers/staging/vt6655/80211hdr.h324
-rw-r--r--drivers/staging/vt6655/80211mgr.c1065
-rw-r--r--drivers/staging/vt6655/80211mgr.h768
-rw-r--r--drivers/staging/vt6655/IEEE11h.c316
-rw-r--r--drivers/staging/vt6655/IEEE11h.h52
-rw-r--r--drivers/staging/vt6655/Kconfig8
-rw-r--r--drivers/staging/vt6655/Makefile39
-rw-r--r--drivers/staging/vt6655/TODO21
-rw-r--r--drivers/staging/vt6655/aes_ccmp.c376
-rw-r--r--drivers/staging/vt6655/aes_ccmp.h46
-rw-r--r--drivers/staging/vt6655/baseband.c2878
-rw-r--r--drivers/staging/vt6655/baseband.h139
-rw-r--r--drivers/staging/vt6655/bssdb.c1557
-rw-r--r--drivers/staging/vt6655/bssdb.h348
-rw-r--r--drivers/staging/vt6655/card.c2095
-rw-r--r--drivers/staging/vt6655/card.h190
-rw-r--r--drivers/staging/vt6655/channel.c807
-rw-r--r--drivers/staging/vt6655/channel.h56
-rw-r--r--drivers/staging/vt6655/country.h162
-rw-r--r--drivers/staging/vt6655/datarate.c408
-rw-r--r--drivers/staging/vt6655/datarate.h88
-rw-r--r--drivers/staging/vt6655/desc.h601
-rw-r--r--drivers/staging/vt6655/device.h836
-rw-r--r--drivers/staging/vt6655/device_cfg.h95
-rw-r--r--drivers/staging/vt6655/device_main.c3253
-rw-r--r--drivers/staging/vt6655/dpc.c1333
-rw-r--r--drivers/staging/vt6655/dpc.h52
-rw-r--r--drivers/staging/vt6655/hostap.c774
-rw-r--r--drivers/staging/vt6655/hostap.h66
-rw-r--r--drivers/staging/vt6655/iocmd.h429
-rw-r--r--drivers/staging/vt6655/ioctl.c660
-rw-r--r--drivers/staging/vt6655/ioctl.h44
-rw-r--r--drivers/staging/vt6655/iowpa.h140
-rw-r--r--drivers/staging/vt6655/iwctl.c1915
-rw-r--r--drivers/staging/vt6655/iwctl.h216
-rw-r--r--drivers/staging/vt6655/key.c787
-rw-r--r--drivers/staging/vt6655/key.h179
-rw-r--r--drivers/staging/vt6655/mac.c1689
-rw-r--r--drivers/staging/vt6655/mac.h1050
-rw-r--r--drivers/staging/vt6655/mib.c538
-rw-r--r--drivers/staging/vt6655/mib.h356
-rw-r--r--drivers/staging/vt6655/michael.c148
-rw-r--r--drivers/staging/vt6655/michael.h58
-rw-r--r--drivers/staging/vt6655/power.c367
-rw-r--r--drivers/staging/vt6655/power.h78
-rw-r--r--drivers/staging/vt6655/rc4.c87
-rw-r--r--drivers/staging/vt6655/rc4.h47
-rw-r--r--drivers/staging/vt6655/rf.c1106
-rw-r--r--drivers/staging/vt6655/rf.h101
-rw-r--r--drivers/staging/vt6655/rxtx.c2845
-rw-r--r--drivers/staging/vt6655/rxtx.h72
-rw-r--r--drivers/staging/vt6655/srom.c405
-rw-r--r--drivers/staging/vt6655/srom.h155
-rw-r--r--drivers/staging/vt6655/tcrc.c191
-rw-r--r--drivers/staging/vt6655/tcrc.h50
-rw-r--r--drivers/staging/vt6655/test9
-rw-r--r--drivers/staging/vt6655/tether.c105
-rw-r--r--drivers/staging/vt6655/tether.h192
-rw-r--r--drivers/staging/vt6655/tkip.c268
-rw-r--r--drivers/staging/vt6655/tkip.h57
-rw-r--r--drivers/staging/vt6655/tmacro.h60
-rw-r--r--drivers/staging/vt6655/ttype.h69
-rw-r--r--drivers/staging/vt6655/upc.h157
-rw-r--r--drivers/staging/vt6655/vntconfiguration.dat1
-rw-r--r--drivers/staging/vt6655/vntwifi.c705
-rw-r--r--drivers/staging/vt6655/vntwifi.h273
-rw-r--r--drivers/staging/vt6655/wcmd.c1021
-rw-r--r--drivers/staging/vt6655/wcmd.h135
-rw-r--r--drivers/staging/vt6655/wctl.c231
-rw-r--r--drivers/staging/vt6655/wctl.h105
-rw-r--r--drivers/staging/vt6655/wmgr.c4650
-rw-r--r--drivers/staging/vt6655/wmgr.h424
-rw-r--r--drivers/staging/vt6655/wpa.c300
-rw-r--r--drivers/staging/vt6655/wpa.h83
-rw-r--r--drivers/staging/vt6655/wpa2.c357
-rw-r--r--drivers/staging/vt6655/wpa2.h77
-rw-r--r--drivers/staging/vt6655/wpactl.c892
-rw-r--r--drivers/staging/vt6655/wpactl.h64
-rw-r--r--drivers/staging/vt6655/wroute.c191
-rw-r--r--drivers/staging/vt6655/wroute.h44
-rw-r--r--drivers/staging/vt6656/80211hdr.h324
-rw-r--r--drivers/staging/vt6656/80211mgr.c861
-rw-r--r--drivers/staging/vt6656/80211mgr.h808
-rw-r--r--drivers/staging/vt6656/Kconfig9
-rw-r--r--drivers/staging/vt6656/Makefile33
-rw-r--r--drivers/staging/vt6656/TODO19
-rw-r--r--drivers/staging/vt6656/baseband.c1389
-rw-r--r--drivers/staging/vt6656/baseband.h105
-rw-r--r--drivers/staging/vt6656/bssdb.c1466
-rw-r--r--drivers/staging/vt6656/bssdb.h276
-rw-r--r--drivers/staging/vt6656/card.c877
-rw-r--r--drivers/staging/vt6656/card.h68
-rw-r--r--drivers/staging/vt6656/channel.c473
-rw-r--r--drivers/staging/vt6656/channel.h44
-rw-r--r--drivers/staging/vt6656/country.h162
-rw-r--r--drivers/staging/vt6656/datarate.c364
-rw-r--r--drivers/staging/vt6656/datarate.h76
-rw-r--r--drivers/staging/vt6656/desc.h107
-rw-r--r--drivers/staging/vt6656/device.h735
-rw-r--r--drivers/staging/vt6656/dpc.c971
-rw-r--r--drivers/staging/vt6656/dpc.h44
-rw-r--r--drivers/staging/vt6656/firmware.c144
-rw-r--r--drivers/staging/vt6656/firmware.h39
-rw-r--r--drivers/staging/vt6656/int.c187
-rw-r--r--drivers/staging/vt6656/int.h61
-rw-r--r--drivers/staging/vt6656/iocmd.h38
-rw-r--r--drivers/staging/vt6656/iowpa.h75
-rw-r--r--drivers/staging/vt6656/iwctl.c1802
-rw-r--r--drivers/staging/vt6656/iwctl.h138
-rw-r--r--drivers/staging/vt6656/key.c731
-rw-r--r--drivers/staging/vt6656/key.h112
-rw-r--r--drivers/staging/vt6656/mac.c255
-rw-r--r--drivers/staging/vt6656/mac.h433
-rw-r--r--drivers/staging/vt6656/main_usb.c1373
-rw-r--r--drivers/staging/vt6656/michael.c167
-rw-r--r--drivers/staging/vt6656/michael.h52
-rw-r--r--drivers/staging/vt6656/power.c338
-rw-r--r--drivers/staging/vt6656/power.h47
-rw-r--r--drivers/staging/vt6656/rc4.c87
-rw-r--r--drivers/staging/vt6656/rc4.h46
-rw-r--r--drivers/staging/vt6656/rf.c1116
-rw-r--r--drivers/staging/vt6656/rf.h66
-rw-r--r--drivers/staging/vt6656/rxtx.c2074
-rw-r--r--drivers/staging/vt6656/rxtx.h254
-rw-r--r--drivers/staging/vt6656/tether.c60
-rw-r--r--drivers/staging/vt6656/tether.h101
-rw-r--r--drivers/staging/vt6656/tkip.c246
-rw-r--r--drivers/staging/vt6656/tkip.h45
-rw-r--r--drivers/staging/vt6656/tmacro.h52
-rw-r--r--drivers/staging/vt6656/usbpipe.c440
-rw-r--r--drivers/staging/vt6656/usbpipe.h46
-rw-r--r--drivers/staging/vt6656/vntconfiguration.dat6
-rw-r--r--drivers/staging/vt6656/wcmd.c1094
-rw-r--r--drivers/staging/vt6656/wcmd.h112
-rw-r--r--drivers/staging/vt6656/wctl.c244
-rw-r--r--drivers/staging/vt6656/wctl.h94
-rw-r--r--drivers/staging/vt6656/wmgr.c4362
-rw-r--r--drivers/staging/vt6656/wmgr.h397
-rw-r--r--drivers/staging/vt6656/wpa.c314
-rw-r--r--drivers/staging/vt6656/wpa.h72
-rw-r--r--drivers/staging/vt6656/wpa2.c235
-rw-r--r--drivers/staging/vt6656/wpa2.h53
-rw-r--r--drivers/staging/vt6656/wpactl.c247
-rw-r--r--drivers/staging/vt6656/wpactl.h47
-rw-r--r--drivers/staging/winbond/Kconfig11
-rw-r--r--drivers/staging/winbond/Makefile15
-rw-r--r--drivers/staging/winbond/TODO12
-rw-r--r--drivers/staging/winbond/core.h61
-rw-r--r--drivers/staging/winbond/localpara.h311
-rw-r--r--drivers/staging/winbond/mac_structures.h71
-rw-r--r--drivers/staging/winbond/mds.c650
-rw-r--r--drivers/staging/winbond/mds_f.h22
-rw-r--r--drivers/staging/winbond/mds_s.h130
-rw-r--r--drivers/staging/winbond/mto.c167
-rw-r--r--drivers/staging/winbond/mto.h134
-rw-r--r--drivers/staging/winbond/phy_calibration.c1317
-rw-r--r--drivers/staging/winbond/phy_calibration.h85
-rw-r--r--drivers/staging/winbond/reg.c2328
-rw-r--r--drivers/staging/winbond/sme_api.h191
-rw-r--r--drivers/staging/winbond/wb35reg.c806
-rw-r--r--drivers/staging/winbond/wb35reg_f.h65
-rw-r--r--drivers/staging/winbond/wb35reg_s.h240
-rw-r--r--drivers/staging/winbond/wb35rx.c358
-rw-r--r--drivers/staging/winbond/wb35rx_f.h15
-rw-r--r--drivers/staging/winbond/wb35rx_s.h44
-rw-r--r--drivers/staging/winbond/wb35tx.c290
-rw-r--r--drivers/staging/winbond/wb35tx_f.h22
-rw-r--r--drivers/staging/winbond/wb35tx_s.h39
-rw-r--r--drivers/staging/winbond/wbhal.h513
-rw-r--r--drivers/staging/winbond/wbusb.c853
-rw-r--r--drivers/staging/wlags49_h2/Kconfig11
-rw-r--r--drivers/staging/wlags49_h2/Makefile53
-rw-r--r--drivers/staging/wlags49_h2/README.ubuntu180
-rw-r--r--drivers/staging/wlags49_h2/README.wlags49641
-rw-r--r--drivers/staging/wlags49_h2/TODO33
-rw-r--r--drivers/staging/wlags49_h2/WARNING.txt3
-rw-r--r--drivers/staging/wlags49_h2/ap_h2.c3337
-rw-r--r--drivers/staging/wlags49_h2/ap_h25.c4094
-rw-r--r--drivers/staging/wlags49_h2/debug.h199
-rw-r--r--drivers/staging/wlags49_h2/dhf.c380
-rw-r--r--drivers/staging/wlags49_h2/dhf.h225
-rw-r--r--drivers/staging/wlags49_h2/dhfcfg.h158
-rw-r--r--drivers/staging/wlags49_h2/hcf.c4748
-rw-r--r--drivers/staging/wlags49_h2/hcf.h394
-rw-r--r--drivers/staging/wlags49_h2/hcfcfg.h785
-rw-r--r--drivers/staging/wlags49_h2/hcfdef.h752
-rw-r--r--drivers/staging/wlags49_h2/man/wlags49.4734
-rw-r--r--drivers/staging/wlags49_h2/mdd.h1155
-rw-r--r--drivers/staging/wlags49_h2/mmd.c250
-rw-r--r--drivers/staging/wlags49_h2/mmd.h77
-rw-r--r--drivers/staging/wlags49_h2/sta_h2.c4480
-rw-r--r--drivers/staging/wlags49_h2/sta_h25.c5255
-rw-r--r--drivers/staging/wlags49_h2/wl_cs.c440
-rw-r--r--drivers/staging/wlags49_h2/wl_cs.h89
-rw-r--r--drivers/staging/wlags49_h2/wl_enc.c217
-rw-r--r--drivers/staging/wlags49_h2/wl_enc.h118
-rw-r--r--drivers/staging/wlags49_h2/wl_if.h221
-rw-r--r--drivers/staging/wlags49_h2/wl_internal.h1040
-rw-r--r--drivers/staging/wlags49_h2/wl_main.c3702
-rw-r--r--drivers/staging/wlags49_h2/wl_main.h138
-rw-r--r--drivers/staging/wlags49_h2/wl_netdev.c1951
-rw-r--r--drivers/staging/wlags49_h2/wl_netdev.h154
-rw-r--r--drivers/staging/wlags49_h2/wl_priv.c1928
-rw-r--r--drivers/staging/wlags49_h2/wl_priv.h127
-rw-r--r--drivers/staging/wlags49_h2/wl_profile.c995
-rw-r--r--drivers/staging/wlags49_h2/wl_profile.h87
-rw-r--r--drivers/staging/wlags49_h2/wl_util.c1367
-rw-r--r--drivers/staging/wlags49_h2/wl_util.h96
-rw-r--r--drivers/staging/wlags49_h2/wl_version.h146
-rw-r--r--drivers/staging/wlags49_h2/wl_wext.c3794
-rw-r--r--drivers/staging/wlags49_h2/wl_wext.h88
-rw-r--r--drivers/staging/wlags49_h25/Kconfig11
-rw-r--r--drivers/staging/wlags49_h25/Makefile52
-rw-r--r--drivers/staging/wlags49_h25/README.txt30
-rw-r--r--drivers/staging/wlags49_h25/TODO33
-rw-r--r--drivers/staging/wlags49_h25/ap_h25.c2
-rw-r--r--drivers/staging/wlags49_h25/debug.h2
-rw-r--r--drivers/staging/wlags49_h25/dhf.c2
-rw-r--r--drivers/staging/wlags49_h25/dhf.h2
-rw-r--r--drivers/staging/wlags49_h25/dhfcfg.h2
-rw-r--r--drivers/staging/wlags49_h25/hcf.c2
-rw-r--r--drivers/staging/wlags49_h25/hcf.h2
-rw-r--r--drivers/staging/wlags49_h25/hcfcfg.h2
-rw-r--r--drivers/staging/wlags49_h25/hcfdef.h2
-rw-r--r--drivers/staging/wlags49_h25/mdd.h2
-rw-r--r--drivers/staging/wlags49_h25/mmd.c2
-rw-r--r--drivers/staging/wlags49_h25/mmd.h2
-rw-r--r--drivers/staging/wlags49_h25/sta_h25.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_cs.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_cs.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_enc.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_enc.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_if.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_internal.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_main.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_main.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_netdev.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_netdev.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_priv.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_priv.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_profile.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_profile.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_util.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_util.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_version.h2
-rw-r--r--drivers/staging/wlags49_h25/wl_wext.c2
-rw-r--r--drivers/staging/wlags49_h25/wl_wext.h2
-rw-r--r--drivers/staging/wlan-ng/Kconfig12
-rw-r--r--drivers/staging/wlan-ng/Makefile7
-rw-r--r--drivers/staging/wlan-ng/README8
-rw-r--r--drivers/staging/wlan-ng/cfg80211.c793
-rw-r--r--drivers/staging/wlan-ng/hfa384x.h1427
-rw-r--r--drivers/staging/wlan-ng/hfa384x_usb.c4145
-rw-r--r--drivers/staging/wlan-ng/p80211conv.c662
-rw-r--r--drivers/staging/wlan-ng/p80211conv.h163
-rw-r--r--drivers/staging/wlan-ng/p80211hdr.h213
-rw-r--r--drivers/staging/wlan-ng/p80211ioctl.h89
-rw-r--r--drivers/staging/wlan-ng/p80211meta.h90
-rw-r--r--drivers/staging/wlan-ng/p80211metadef.h261
-rw-r--r--drivers/staging/wlan-ng/p80211metastruct.h271
-rw-r--r--drivers/staging/wlan-ng/p80211mgmt.h520
-rw-r--r--drivers/staging/wlan-ng/p80211msg.h59
-rw-r--r--drivers/staging/wlan-ng/p80211netdev.c1111
-rw-r--r--drivers/staging/wlan-ng/p80211netdev.h243
-rw-r--r--drivers/staging/wlan-ng/p80211req.c250
-rw-r--r--drivers/staging/wlan-ng/p80211req.h53
-rw-r--r--drivers/staging/wlan-ng/p80211types.h375
-rw-r--r--drivers/staging/wlan-ng/p80211wep.c301
-rw-r--r--drivers/staging/wlan-ng/prism2fw.c1221
-rw-r--r--drivers/staging/wlan-ng/prism2mgmt.c1331
-rw-r--r--drivers/staging/wlan-ng/prism2mgmt.h117
-rw-r--r--drivers/staging/wlan-ng/prism2mib.c834
-rw-r--r--drivers/staging/wlan-ng/prism2sta.c2044
-rw-r--r--drivers/staging/wlan-ng/prism2usb.c299
-rw-r--r--drivers/staging/xgifb/Kconfig11
-rw-r--r--drivers/staging/xgifb/Makefile4
-rw-r--r--drivers/staging/xgifb/TODO13
-rw-r--r--drivers/staging/xgifb/XGI_main.h377
-rw-r--r--drivers/staging/xgifb/XGI_main_26.c2129
-rw-r--r--drivers/staging/xgifb/XGIfb.h108
-rw-r--r--drivers/staging/xgifb/vb_def.h258
-rw-r--r--drivers/staging/xgifb/vb_init.c1375
-rw-r--r--drivers/staging/xgifb/vb_init.h6
-rw-r--r--drivers/staging/xgifb/vb_setmode.c5555
-rw-r--r--drivers/staging/xgifb/vb_setmode.h23
-rw-r--r--drivers/staging/xgifb/vb_struct.h166
-rw-r--r--drivers/staging/xgifb/vb_table.h2491
-rw-r--r--drivers/staging/xgifb/vb_util.c45
-rw-r--r--drivers/staging/xgifb/vb_util.h9
-rw-r--r--drivers/staging/xgifb/vgatypes.h49
-rw-r--r--drivers/staging/xillybus/Kconfig33
-rw-r--r--drivers/staging/xillybus/Makefile7
-rw-r--r--drivers/staging/xillybus/README403
-rw-r--r--drivers/staging/xillybus/TODO5
-rw-r--r--drivers/staging/xillybus/xillybus.h181
-rw-r--r--drivers/staging/xillybus/xillybus_core.c2338
-rw-r--r--drivers/staging/xillybus/xillybus_of.c194
-rw-r--r--drivers/staging/xillybus/xillybus_pcie.c230
2616 files changed, 1329853 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
new file mode 100644
index 00000000000..4f38fc000a3
--- /dev/null
+++ b/drivers/staging/Kconfig
@@ -0,0 +1,149 @@
+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/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/comedi/Kconfig"
+
+source "drivers/staging/olpc_dcon/Kconfig"
+
+source "drivers/staging/panel/Kconfig"
+
+source "drivers/staging/rtl8192u/Kconfig"
+
+source "drivers/staging/rtl8192e/Kconfig"
+
+source "drivers/staging/rtl8712/Kconfig"
+
+source "drivers/staging/rtl8188eu/Kconfig"
+
+source "drivers/staging/rtl8192ee/Kconfig"
+
+source "drivers/staging/rtl8723au/Kconfig"
+
+source "drivers/staging/rtl8821ae/Kconfig"
+
+source "drivers/staging/rts5208/Kconfig"
+
+source "drivers/staging/frontier/Kconfig"
+
+source "drivers/staging/phison/Kconfig"
+
+source "drivers/staging/line6/Kconfig"
+
+source "drivers/staging/octeon/Kconfig"
+
+source "drivers/staging/octeon-usb/Kconfig"
+
+source "drivers/staging/serqt_usb2/Kconfig"
+
+source "drivers/staging/vt6655/Kconfig"
+
+source "drivers/staging/vt6656/Kconfig"
+
+source "drivers/staging/sep/Kconfig"
+
+source "drivers/staging/iio/Kconfig"
+
+source "drivers/staging/wlags49_h2/Kconfig"
+
+source "drivers/staging/wlags49_h25/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/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/nvec/Kconfig"
+
+source "drivers/staging/media/Kconfig"
+
+source "drivers/staging/android/Kconfig"
+
+source "drivers/staging/ozwpan/Kconfig"
+
+source "drivers/staging/gdm72xx/Kconfig"
+
+source "drivers/staging/gdm724x/Kconfig"
+
+source "drivers/staging/silicom/Kconfig"
+
+source "drivers/staging/ced1401/Kconfig"
+
+source "drivers/staging/imx-drm/Kconfig"
+
+source "drivers/staging/dgrp/Kconfig"
+
+source "drivers/staging/fwserial/Kconfig"
+
+source "drivers/staging/goldfish/Kconfig"
+
+source "drivers/staging/netlogic/Kconfig"
+
+source "drivers/staging/mt29f_spinand/Kconfig"
+
+source "drivers/staging/lustre/Kconfig"
+
+source "drivers/staging/xillybus/Kconfig"
+
+source "drivers/staging/dgnc/Kconfig"
+
+source "drivers/staging/dgap/Kconfig"
+
+source "drivers/staging/gs_fpgaboot/Kconfig"
+
+source "drivers/staging/nokia_h4p/Kconfig"
+
+source "drivers/staging/skein/Kconfig"
+
+source "drivers/staging/unisys/Kconfig"
+
+endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
new file mode 100644
index 00000000000..1e97ad2177d
--- /dev/null
+++ b/drivers/staging/Makefile
@@ -0,0 +1,67 @@
+# Makefile for staging directory
+
+# fix for build system bug...
+obj-$(CONFIG_STAGING) += staging.o
+
+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_COMEDI) += comedi/
+obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/
+obj-$(CONFIG_PANEL) += panel/
+obj-$(CONFIG_RTL8192U) += rtl8192u/
+obj-$(CONFIG_RTL8192E) += rtl8192e/
+obj-$(CONFIG_R8712U) += rtl8712/
+obj-$(CONFIG_R8188EU) += rtl8188eu/
+obj-$(CONFIG_R8192EE) += rtl8192ee/
+obj-$(CONFIG_R8723AU) += rtl8723au/
+obj-$(CONFIG_R8821AE) += rtl8821ae/
+obj-$(CONFIG_RTS5208) += rts5208/
+obj-$(CONFIG_TRANZPORT) += frontier/
+obj-$(CONFIG_IDE_PHISON) += phison/
+obj-$(CONFIG_LINE6_USB) += line6/
+obj-$(CONFIG_NETLOGIC_XLR_NET) += netlogic/
+obj-$(CONFIG_USB_SERIAL_QUATECH2) += serqt_usb2/
+obj-$(CONFIG_OCTEON_ETHERNET) += octeon/
+obj-$(CONFIG_OCTEON_USB) += octeon-usb/
+obj-$(CONFIG_VT6655) += vt6655/
+obj-$(CONFIG_VT6656) += vt6656/
+obj-$(CONFIG_VME_BUS) += vme/
+obj-$(CONFIG_DX_SEP) += sep/
+obj-$(CONFIG_IIO) += iio/
+obj-$(CONFIG_WLAGS49_H2) += wlags49_h2/
+obj-$(CONFIG_WLAGS49_H25) += wlags49_h25/
+obj-$(CONFIG_CRYSTALHD) += crystalhd/
+obj-$(CONFIG_CXT1E1) += cxt1e1/
+obj-$(CONFIG_FB_XGI) += xgifb/
+obj-$(CONFIG_TIDSPBRIDGE) += tidspbridge/
+obj-$(CONFIG_ACPI_QUICKSTART) += quickstart/
+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_MFD_NVEC) += nvec/
+obj-$(CONFIG_ANDROID) += android/
+obj-$(CONFIG_USB_WPAN_HCD) += ozwpan/
+obj-$(CONFIG_WIMAX_GDM72XX) += gdm72xx/
+obj-$(CONFIG_LTE_GDM724X) += gdm724x/
+obj-$(CONFIG_NET_VENDOR_SILICOM) += silicom/
+obj-$(CONFIG_CED1401) += ced1401/
+obj-$(CONFIG_DRM_IMX) += imx-drm/
+obj-$(CONFIG_DGRP) += dgrp/
+obj-$(CONFIG_FIREWIRE_SERIAL) += fwserial/
+obj-$(CONFIG_GOLDFISH) += goldfish/
+obj-$(CONFIG_LUSTRE_FS) += lustre/
+obj-$(CONFIG_XILLYBUS) += xillybus/
+obj-$(CONFIG_DGNC) += dgnc/
+obj-$(CONFIG_DGAP) += dgap/
+obj-$(CONFIG_MTD_SPINAND_MT29F) += mt29f_spinand/
+obj-$(CONFIG_GS_FPGABOOT) += gs_fpgaboot/
+obj-$(CONFIG_BT_NOKIA_H4P) += nokia_h4p/
+obj-$(CONFIG_CRYPTO_SKEIN) += skein/
+obj-$(CONFIG_UNISYSSPAR) += unisys/
diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig
new file mode 100644
index 00000000000..99e484f845f
--- /dev/null
+++ b/drivers/staging/android/Kconfig
@@ -0,0 +1,118 @@
+menu "Android"
+
+config ANDROID
+ bool "Android Drivers"
+ ---help---
+ Enable support for various drivers needed on the Android platform
+
+if ANDROID
+
+config ANDROID_BINDER_IPC
+ bool "Android Binder IPC Driver"
+ depends on MMU
+ default n
+ ---help---
+ Binder is used in Android for both communication between processes,
+ and remote method invocation.
+
+ This means one Android process can call a method/routine in another
+ Android process, using Binder to identify, invoke and pass arguments
+ between said processes.
+
+config ANDROID_BINDER_IPC_32BIT
+ bool
+ depends on !64BIT && ANDROID_BINDER_IPC
+ default y
+ ---help---
+ The Binder API has been changed to support both 32 and 64bit
+ applications in a mixed environment.
+
+ Enable this to support an old 32-bit Android user-space (v4.4 and
+ earlier).
+
+ Note that enabling this will break newer Android user-space.
+
+config ASHMEM
+ bool "Enable the Anonymous Shared Memory Subsystem"
+ default n
+ depends on 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.
+
+ It is, in theory, a good memory allocator for low-memory devices,
+ because it can discard shared memory units when under memory pressure.
+
+config ANDROID_LOGGER
+ tristate "Android log driver"
+ default n
+ ---help---
+ This adds support for system-wide logging using four log buffers.
+
+ These are:
+
+ 1: main
+ 2: events
+ 3: radio
+ 4: system
+
+ Log reading and writing is performed via normal Linux reads and
+ optimized writes. This optimization avoids logging having too
+ much overhead in the system.
+
+config ANDROID_TIMED_OUTPUT
+ bool "Timed output class driver"
+ default y
+
+config ANDROID_TIMED_GPIO
+ tristate "Android timed gpio driver"
+ depends on GPIOLIB && ANDROID_TIMED_OUTPUT
+ default n
+
+config ANDROID_LOW_MEMORY_KILLER
+ bool "Android Low Memory Killer"
+ ---help---
+ Registers processes to be killed when memory is low
+
+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.
+
+config SYNC
+ bool "Synchronization framework"
+ default n
+ select ANON_INODES
+ ---help---
+ This option enables the framework for synchronization between multiple
+ drivers. Sync implementations can take advantage of hardware
+ synchronization built into devices like GPUs.
+
+config SW_SYNC
+ bool "Software synchronization objects"
+ default n
+ depends on SYNC
+ ---help---
+ A sync object driver that uses a 32bit counter to coordinate
+ syncrhronization. Useful when there is no hardware primitive backing
+ the synchronization.
+
+config SW_SYNC_USER
+ bool "Userspace API for SW_SYNC"
+ default n
+ depends on SW_SYNC
+ ---help---
+ Provides a user space API to the sw sync object.
+ *WARNING* improper use of this can result in deadlocking kernel
+ drivers from userspace.
+
+source "drivers/staging/android/ion/Kconfig"
+
+endif # if ANDROID
+
+endmenu
diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile
new file mode 100644
index 00000000000..0a01e191490
--- /dev/null
+++ b/drivers/staging/android/Makefile
@@ -0,0 +1,13 @@
+ccflags-y += -I$(src) # needed for trace events
+
+obj-y += ion/
+
+obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o
+obj-$(CONFIG_ASHMEM) += ashmem.o
+obj-$(CONFIG_ANDROID_LOGGER) += logger.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_INTF_ALARM_DEV) += alarm-dev.o
+obj-$(CONFIG_SYNC) += sync.o
+obj-$(CONFIG_SW_SYNC) += sw_sync.o
diff --git a/drivers/staging/android/TODO b/drivers/staging/android/TODO
new file mode 100644
index 00000000000..b15fb0d6b15
--- /dev/null
+++ b/drivers/staging/android/TODO
@@ -0,0 +1,10 @@
+TODO:
+ - checkpatch.pl cleanups
+ - sparse fixes
+ - rename files to be not so "generic"
+ - make sure things build as modules properly
+ - add proper arch dependencies as needed
+ - audit userspace interfaces to make sure they are sane
+
+Please send patches to Greg Kroah-Hartman <greg@kroah.com> and Cc:
+Brian Swetland <swetland@google.com>
diff --git a/drivers/staging/android/alarm-dev.c b/drivers/staging/android/alarm-dev.c
new file mode 100644
index 00000000000..f200e8a8432
--- /dev/null
+++ b/drivers/staging/android/alarm-dev.c
@@ -0,0 +1,446 @@
+/* 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 <linux/time.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/alarmtimer.h>
+#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 alarm_dbg(debug_level_mask, fmt, ...) \
+do { \
+ if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) \
+ pr_info(fmt, ##__VA_ARGS__); \
+} while (0)
+
+#define ANDROID_ALARM_WAKEUP_MASK ( \
+ ANDROID_ALARM_RTC_WAKEUP_MASK | \
+ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK)
+
+static int alarm_opened;
+static DEFINE_SPINLOCK(alarm_slock);
+static struct wakeup_source 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];
+
+/**
+ * is_wakeup() - Checks to see if this alarm can wake the device
+ * @type: The type of alarm being checked
+ *
+ * Return: 1 if this is a wakeup alarm, otherwise 0
+ */
+static int is_wakeup(enum android_alarm_type type)
+{
+ return type == ANDROID_ALARM_RTC_WAKEUP ||
+ type == ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP;
+}
+
+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)
+{
+ if (is_wakeup(alrm->type))
+ return alarm_try_to_cancel(&alrm->u.alrm);
+ return hrtimer_try_to_cancel(&alrm->u.hrt);
+}
+
+static void devalarm_cancel(struct devalarm *alrm)
+{
+ if (is_wakeup(alrm->type))
+ alarm_cancel(&alrm->u.alrm);
+ else
+ hrtimer_cancel(&alrm->u.hrt);
+}
+
+static void alarm_clear(enum android_alarm_type alarm_type)
+{
+ uint32_t alarm_type_mask = 1U << alarm_type;
+ unsigned long flags;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ alarm_dbg(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)
+ __pm_relax(&alarm_wake_lock);
+ }
+ alarm_enabled &= ~alarm_type_mask;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+}
+
+static void alarm_set(enum android_alarm_type alarm_type,
+ struct timespec *ts)
+{
+ uint32_t alarm_type_mask = 1U << alarm_type;
+ unsigned long flags;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ alarm_dbg(IO, "alarm %d set %ld.%09ld\n",
+ alarm_type, ts->tv_sec, ts->tv_nsec);
+ alarm_enabled |= alarm_type_mask;
+ devalarm_start(&alarms[alarm_type], timespec_to_ktime(*ts));
+ spin_unlock_irqrestore(&alarm_slock, flags);
+}
+
+static int alarm_wait(void)
+{
+ unsigned long flags;
+ int rv = 0;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ alarm_dbg(IO, "alarm wait\n");
+ if (!alarm_pending && wait_pending) {
+ __pm_relax(&alarm_wake_lock);
+ wait_pending = 0;
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+
+ rv = wait_event_interruptible(alarm_wait_queue, alarm_pending);
+ if (rv)
+ return rv;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ rv = alarm_pending;
+ wait_pending = 1;
+ alarm_pending = 0;
+ spin_unlock_irqrestore(&alarm_slock, flags);
+
+ return rv;
+}
+
+static int alarm_set_rtc(struct timespec *ts)
+{
+ struct rtc_time new_rtc_tm;
+ struct rtc_device *rtc_dev;
+ unsigned long flags;
+ int rv = 0;
+
+ rtc_time_to_tm(ts->tv_sec, &new_rtc_tm);
+ rtc_dev = alarmtimer_get_rtcdev();
+ rv = do_settimeofday(ts);
+ if (rv < 0)
+ return rv;
+ 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);
+
+ return rv;
+}
+
+static int alarm_get_time(enum android_alarm_type alarm_type,
+ struct timespec *ts)
+{
+ int rv = 0;
+
+ switch (alarm_type) {
+ case ANDROID_ALARM_RTC_WAKEUP:
+ case ANDROID_ALARM_RTC:
+ getnstimeofday(ts);
+ break;
+ case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP:
+ case ANDROID_ALARM_ELAPSED_REALTIME:
+ get_monotonic_boottime(ts);
+ break;
+ case ANDROID_ALARM_SYSTEMTIME:
+ ktime_get_ts(ts);
+ break;
+ default:
+ rv = -EINVAL;
+ }
+ return rv;
+}
+
+static long alarm_do_ioctl(struct file *file, unsigned int cmd,
+ struct timespec *ts)
+{
+ int rv = 0;
+ unsigned long flags;
+ enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd);
+
+ 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):
+ alarm_clear(alarm_type);
+ break;
+ case ANDROID_ALARM_SET(0):
+ alarm_set(alarm_type, ts);
+ break;
+ case ANDROID_ALARM_SET_AND_WAIT(0):
+ alarm_set(alarm_type, ts);
+ /* fall though */
+ case ANDROID_ALARM_WAIT:
+ rv = alarm_wait();
+ break;
+ case ANDROID_ALARM_SET_RTC:
+ rv = alarm_set_rtc(ts);
+ break;
+ case ANDROID_ALARM_GET_TIME(0):
+ rv = alarm_get_time(alarm_type, ts);
+ break;
+
+ default:
+ rv = -EINVAL;
+ }
+ return rv;
+}
+
+static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+
+ struct timespec ts;
+ int rv;
+
+ switch (ANDROID_ALARM_BASE_CMD(cmd)) {
+ case ANDROID_ALARM_SET_AND_WAIT(0):
+ case ANDROID_ALARM_SET(0):
+ case ANDROID_ALARM_SET_RTC:
+ if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
+ return -EFAULT;
+ break;
+ }
+
+ rv = alarm_do_ioctl(file, cmd, &ts);
+ if (rv)
+ return rv;
+
+ switch (ANDROID_ALARM_BASE_CMD(cmd)) {
+ case ANDROID_ALARM_GET_TIME(0):
+ if (copy_to_user((void __user *)arg, &ts, sizeof(ts)))
+ return -EFAULT;
+ break;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_COMPAT
+static long alarm_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+
+ struct timespec ts;
+ int rv;
+
+ switch (ANDROID_ALARM_BASE_CMD(cmd)) {
+ case ANDROID_ALARM_SET_AND_WAIT_COMPAT(0):
+ case ANDROID_ALARM_SET_COMPAT(0):
+ case ANDROID_ALARM_SET_RTC_COMPAT:
+ if (compat_get_timespec(&ts, (void __user *)arg))
+ return -EFAULT;
+ /* fall through */
+ case ANDROID_ALARM_GET_TIME_COMPAT(0):
+ cmd = ANDROID_ALARM_COMPAT_TO_NORM(cmd);
+ break;
+ }
+
+ rv = alarm_do_ioctl(file, cmd, &ts);
+ if (rv)
+ return rv;
+
+ switch (ANDROID_ALARM_BASE_CMD(cmd)) {
+ case ANDROID_ALARM_GET_TIME(0): /* NOTE: we modified cmd above */
+ if (compat_put_timespec(&ts, (void __user *)arg))
+ return -EFAULT;
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+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) {
+ for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) {
+ uint32_t alarm_type_mask = 1U << i;
+
+ if (alarm_enabled & alarm_type_mask) {
+ alarm_dbg(INFO,
+ "%s: clear alarm, pending %d\n",
+ __func__,
+ !!(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)
+ alarm_dbg(INFO, "%s: clear pending alarms %x\n",
+ __func__, alarm_pending);
+ __pm_relax(&alarm_wake_lock);
+ wait_pending = 0;
+ alarm_pending = 0;
+ }
+ alarm_opened = 0;
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+ return 0;
+}
+
+static void devalarm_triggered(struct devalarm *alarm)
+{
+ unsigned long flags;
+ uint32_t alarm_type_mask = 1U << alarm->type;
+
+ alarm_dbg(INT, "%s: type %d\n", __func__, alarm->type);
+ spin_lock_irqsave(&alarm_slock, flags);
+ if (alarm_enabled & alarm_type_mask) {
+ __pm_wakeup_event(&alarm_wake_lock, 5000); /* 5secs */
+ 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,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = alarm_compat_ioctl,
+#endif
+};
+
+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;
+ }
+
+ wakeup_source_init(&alarm_wake_lock, "alarm");
+ return 0;
+}
+
+static void __exit alarm_dev_exit(void)
+{
+ misc_deregister(&alarm_device);
+ wakeup_source_trash(&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 00000000000..495b20cf3bf
--- /dev/null
+++ b/drivers/staging/android/android_alarm.h
@@ -0,0 +1,41 @@
+/* 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 <linux/compat.h>
+#include <linux/ioctl.h>
+
+#include "uapi/android_alarm.h"
+
+#ifdef CONFIG_COMPAT
+#define ANDROID_ALARM_SET_COMPAT(type) ALARM_IOW(2, type, \
+ struct compat_timespec)
+#define ANDROID_ALARM_SET_AND_WAIT_COMPAT(type) ALARM_IOW(3, type, \
+ struct compat_timespec)
+#define ANDROID_ALARM_GET_TIME_COMPAT(type) ALARM_IOW(4, type, \
+ struct compat_timespec)
+#define ANDROID_ALARM_SET_RTC_COMPAT _IOW('a', 5, \
+ struct compat_timespec)
+#define ANDROID_ALARM_IOCTL_NR(cmd) (_IOC_NR(cmd) & ((1<<4)-1))
+#define ANDROID_ALARM_COMPAT_TO_NORM(cmd) \
+ ALARM_IOW(ANDROID_ALARM_IOCTL_NR(cmd), \
+ ANDROID_ALARM_IOCTL_TO_TYPE(cmd), \
+ struct timespec)
+
+#endif
+
+#endif
diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c
new file mode 100644
index 00000000000..713a9722678
--- /dev/null
+++ b/drivers/staging/android/ashmem.c
@@ -0,0 +1,886 @@
+/* mm/ashmem.c
+ *
+ * Anonymous Shared Memory Subsystem, ashmem
+ *
+ * Copyright (C) 2008 Google, Inc.
+ *
+ * Robert Love <rlove@google.com>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "ashmem: " fmt
+
+#include <linux/module.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/falloc.h>
+#include <linux/miscdevice.h>
+#include <linux/security.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/uaccess.h>
+#include <linux/personality.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <linux/shmem_fs.h>
+#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)
+
+/**
+ * struct ashmem_area - The anonymous shared memory area
+ * @name: The optional name in /proc/pid/maps
+ * @unpinned_list: The list of all ashmem areas
+ * @file: The shmem-based backing file
+ * @size: The size of the mapping, in bytes
+ * @prot_masks: The allowed protection bits, as vm_flags
+ *
+ * The lifecycle of this structure is from our parent file's open() until
+ * its release(). It is also protected by 'ashmem_mutex'
+ *
+ * Warning: Mappings do NOT pin this structure; It dies on close()
+ */
+struct ashmem_area {
+ char name[ASHMEM_FULL_NAME_LEN];
+ struct list_head unpinned_list;
+ struct file *file;
+ size_t size;
+ unsigned long prot_mask;
+};
+
+/**
+ * struct ashmem_range - A range of unpinned/evictable pages
+ * @lru: The entry in the LRU list
+ * @unpinned: The entry in its area's unpinned list
+ * @asma: The associated anonymous shared memory area.
+ * @pgstart: The starting page (inclusive)
+ * @pgend: The ending page (inclusive)
+ * @purged: The purge status (ASHMEM_NOT or ASHMEM_WAS_PURGED)
+ *
+ * The lifecycle of this structure is from unpin to pin.
+ * It is protected by 'ashmem_mutex'
+ */
+struct ashmem_range {
+ struct list_head lru;
+ struct list_head unpinned;
+ struct ashmem_area *asma;
+ size_t pgstart;
+ size_t pgend;
+ unsigned int purged;
+};
+
+/* LRU list of unpinned pages, protected by ashmem_mutex */
+static LIST_HEAD(ashmem_lru_list);
+
+/**
+ * long lru_count - The count of pages on our LRU list.
+ *
+ * This is 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)
+
+/**
+ * lru_add() - Adds a range of memory to the LRU list
+ * @range: The memory range being added.
+ *
+ * The range is first added to the end (tail) of the LRU list.
+ * After this, the size of the range is added to @lru_count
+ */
+static inline void lru_add(struct ashmem_range *range)
+{
+ list_add_tail(&range->lru, &ashmem_lru_list);
+ lru_count += range_size(range);
+}
+
+/**
+ * lru_del() - Removes a range of memory from the LRU list
+ * @range: The memory range being removed
+ *
+ * The range is first deleted from the LRU list.
+ * After this, the size of the range is removed from @lru_count
+ */
+static inline void lru_del(struct ashmem_range *range)
+{
+ list_del(&range->lru);
+ lru_count -= range_size(range);
+}
+
+/**
+ * range_alloc() - Allocates and initializes a new ashmem_range structure
+ * @asma: The associated ashmem_area
+ * @prev_range: The previous ashmem_range in the sorted asma->unpinned list
+ * @purged: Initial purge status (ASMEM_NOT_PURGED or ASHMEM_WAS_PURGED)
+ * @start: The starting page (inclusive)
+ * @end: The ending page (inclusive)
+ *
+ * This function is protected by ashmem_mutex.
+ *
+ * Return: 0 if successful, or -ENOMEM if there is an error
+ */
+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;
+}
+
+/**
+ * range_del() - Deletes and dealloctes an ashmem_range structure
+ * @range: The associated ashmem_range that has previously been allocated
+ */
+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 an ashmem_range
+ * @range: The associated ashmem_range being shrunk
+ * @start: The starting byte of the new range
+ * @end: The ending byte of the new range
+ *
+ * This does not modify the data inside the existing range in any way - It
+ * simply shrinks the boundaries of the range.
+ *
+ * Theoretically, with a little tweaking, this could eventually be changed
+ * to range_resize, and expand the lru_count if the new range is larger.
+ */
+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);
+}
+
+/**
+ * ashmem_open() - Opens an Anonymous Shared Memory structure
+ * @inode: The backing file's index node(?)
+ * @file: The backing file
+ *
+ * Please note that the ashmem_area is not returned by this function - It is
+ * instead written to "file->private_data".
+ *
+ * Return: 0 if successful, or another code if unsuccessful.
+ */
+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;
+}
+
+/**
+ * ashmem_release() - Releases an Anonymous Shared Memory structure
+ * @ignored: The backing file's Index Node(?) - It is ignored here.
+ * @file: The backing file
+ *
+ * Return: 0 if successful. If it is anything else, go have a coffee and
+ * try again.
+ */
+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;
+}
+
+/**
+ * ashmem_read() - Reads a set of bytes from an Ashmem-enabled file
+ * @file: The associated backing file.
+ * @buf: The buffer of data being written to
+ * @len: The number of bytes being read
+ * @pos: The position of the first byte to read.
+ *
+ * Return: 0 if successful, or another return code if not.
+ */
+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_unlock;
+
+ if (!asma->file) {
+ ret = -EBADF;
+ goto out_unlock;
+ }
+
+ mutex_unlock(&ashmem_mutex);
+
+ /*
+ * asma and asma->file are used outside the lock here. We assume
+ * once asma->file is set it will never be changed, and will not
+ * be destroyed until all references to the file are dropped and
+ * ashmem_release is called.
+ */
+ ret = asma->file->f_op->read(asma->file, buf, len, pos);
+ if (ret >= 0) {
+ /** Update backing file pos, since f_ops->read() doesn't */
+ asma->file->f_pos = *pos;
+ }
+ return ret;
+
+out_unlock:
+ 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 vm_flags_t 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);
+
+ /*
+ * XXX - Reworked to use shmem_zero_setup() instead of
+ * shmem_set_file while we're in staging. -jstultz
+ */
+ if (vma->vm_flags & VM_SHARED) {
+ ret = shmem_zero_setup(vma);
+ if (ret) {
+ fput(asma->file);
+ goto out;
+ }
+ }
+
+ if (vma->vm_file)
+ fput(vma->vm_file);
+ vma->vm_file = asma->file;
+
+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 to scan for freeing.
+ *
+ * 'gfp_mask' is the mask of the allocation that got us into this mess.
+ *
+ * Return value is the number of objects freed 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 unsigned long
+ashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
+{
+ struct ashmem_range *range, *next;
+ unsigned long freed = 0;
+
+ /* We might recurse into filesystem code, so bail out if necessary */
+ if (!(sc->gfp_mask & __GFP_FS))
+ return SHRINK_STOP;
+
+ mutex_lock(&ashmem_mutex);
+ list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) {
+ loff_t start = range->pgstart * PAGE_SIZE;
+ loff_t end = (range->pgend + 1) * PAGE_SIZE;
+
+ do_fallocate(range->asma->file,
+ FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ start, end - start);
+ range->purged = ASHMEM_WAS_PURGED;
+ lru_del(range);
+
+ freed += range_size(range);
+ if (--sc->nr_to_scan <= 0)
+ break;
+ }
+ mutex_unlock(&ashmem_mutex);
+ return freed;
+}
+
+static unsigned long
+ashmem_shrink_count(struct shrinker *shrink, struct shrink_control *sc)
+{
+ /*
+ * note that lru_count is count of pages on the lru, not a count of
+ * objects on the list. This means the scan function needs to return the
+ * number of pages freed, not the number of objects scanned.
+ */
+ return lru_count;
+}
+
+static struct shrinker ashmem_shrinker = {
+ .count_objects = ashmem_shrink_count,
+ .scan_objects = ashmem_shrink_scan,
+ /*
+ * XXX (dchinner): I wish people would comment on why they need on
+ * significant changes to the default value here
+ */
+ .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 len;
+ int ret = 0;
+ char local_name[ASHMEM_NAME_LEN];
+
+ /*
+ * Holding the ashmem_mutex while doing a copy_from_user might cause
+ * an data abort which would try to access mmap_sem. If another
+ * thread has invoked ashmem_mmap then it will be holding the
+ * semaphore and will be waiting for ashmem_mutex, there by leading to
+ * deadlock. We'll release the mutex and take the name to a local
+ * variable that does not need protection and later copy the local
+ * variable to the structure member with lock held.
+ */
+ len = strncpy_from_user(local_name, name, ASHMEM_NAME_LEN);
+ if (len < 0)
+ return len;
+ if (len == ASHMEM_NAME_LEN)
+ local_name[ASHMEM_NAME_LEN - 1] = '\0';
+ mutex_lock(&ashmem_mutex);
+ /* cannot change an existing mapping's name */
+ if (unlikely(asma->file))
+ ret = -EINVAL;
+ else
+ strcpy(asma->name + ASHMEM_NAME_PREFIX_LEN, local_name);
+
+ mutex_unlock(&ashmem_mutex);
+ return ret;
+}
+
+static int get_name(struct ashmem_area *asma, void __user *name)
+{
+ int ret = 0;
+ size_t len;
+ /*
+ * Have a local variable to which we'll copy the content
+ * from asma with the lock held. Later we can copy this to the user
+ * space safely without holding any locks. So even if we proceed to
+ * wait for mmap_sem, it won't lead to deadlock.
+ */
+ char local_name[ASHMEM_NAME_LEN];
+
+ mutex_lock(&ashmem_mutex);
+ if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') {
+
+ /*
+ * 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;
+ memcpy(local_name, asma->name + ASHMEM_NAME_PREFIX_LEN, len);
+ } else {
+ len = sizeof(ASHMEM_NAME_DEF);
+ memcpy(local_name, ASHMEM_NAME_DEF, len);
+ }
+ mutex_unlock(&ashmem_mutex);
+
+ /*
+ * Now we are just copying from the stack variable to userland
+ * No lock held
+ */
+ if (unlikely(copy_to_user(name, local_name, len)))
+ ret = -EFAULT;
+ 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 = LONG_MAX,
+ };
+ ret = ashmem_shrink_count(&ashmem_shrinker, &sc);
+ nodes_setall(sc.nodes_to_scan);
+ ashmem_shrink_scan(&ashmem_shrinker, &sc);
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* support of 32bit userspace on 64bit platforms */
+#ifdef CONFIG_COMPAT
+static long compat_ashmem_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+
+ switch (cmd) {
+ case COMPAT_ASHMEM_SET_SIZE:
+ cmd = ASHMEM_SET_SIZE;
+ break;
+ case COMPAT_ASHMEM_SET_PROT_MASK:
+ cmd = ASHMEM_SET_PROT_MASK;
+ break;
+ }
+ return ashmem_ioctl(file, cmd, arg);
+}
+#endif
+
+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,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = compat_ashmem_ioctl,
+#endif
+};
+
+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)) {
+ pr_err("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)) {
+ pr_err("failed to create slab cache\n");
+ return -ENOMEM;
+ }
+
+ ret = misc_register(&ashmem_misc);
+ if (unlikely(ret)) {
+ pr_err("failed to register misc device!\n");
+ return ret;
+ }
+
+ register_shrinker(&ashmem_shrinker);
+
+ pr_info("initialized\n");
+
+ return 0;
+}
+
+static void __exit ashmem_exit(void)
+{
+ int ret;
+
+ unregister_shrinker(&ashmem_shrinker);
+
+ ret = misc_deregister(&ashmem_misc);
+ if (unlikely(ret))
+ pr_err("failed to unregister misc device!\n");
+
+ kmem_cache_destroy(ashmem_range_cachep);
+ kmem_cache_destroy(ashmem_area_cachep);
+
+ pr_info("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 00000000000..5abcfd7aa70
--- /dev/null
+++ b/drivers/staging/android/ashmem.h
@@ -0,0 +1,27 @@
+/*
+ * 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 <linux/limits.h>
+#include <linux/ioctl.h>
+#include <linux/compat.h>
+
+#include "uapi/ashmem.h"
+
+/* support of 32bit userspace on 64bit platforms */
+#ifdef CONFIG_COMPAT
+#define COMPAT_ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, compat_size_t)
+#define COMPAT_ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned int)
+#endif
+
+#endif /* _LINUX_ASHMEM_H */
diff --git a/drivers/staging/android/binder.c b/drivers/staging/android/binder.c
new file mode 100644
index 00000000000..a741da77828
--- /dev/null
+++ b/drivers/staging/android/binder.c
@@ -0,0 +1,3637 @@
+/* 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <asm/cacheflush.h>
+#include <linux/fdtable.h>
+#include <linux/file.h>
+#include <linux/freezer.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nsproxy.h>
+#include <linux/poll.h>
+#include <linux/debugfs.h>
+#include <linux/rbtree.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/pid_namespace.h>
+
+#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 kuid_t binder_context_mgr_uid = INVALID_UID;
+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) \
+ pr_info(x); \
+ } while (0)
+
+#define binder_user_error(x...) \
+ do { \
+ if (binder_debug_mask & BINDER_DEBUG_USER_ERROR) \
+ pr_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;
+ binder_uintptr_t ptr;
+ binder_uintptr_t 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;
+ binder_uintptr_t 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;
+ kuid_t sender_euid;
+};
+
+static void
+binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer);
+
+static int task_get_unused_fd_flags(struct binder_proc *proc, int flags)
+{
+ struct files_struct *files = proc->files;
+ unsigned long rlim_cur;
+ unsigned long irqs;
+
+ if (files == NULL)
+ return -ESRCH;
+
+ if (!lock_task_sighand(proc->tsk, &irqs))
+ return -EMFILE;
+
+ rlim_cur = task_rlimit(proc->tsk, RLIMIT_NOFILE);
+ unlock_task_sighand(proc->tsk, &irqs);
+
+ return __alloc_fd(files, 0, rlim_cur, flags);
+}
+
+/*
+ * copied from fd_install
+ */
+static void task_fd_install(
+ struct binder_proc *proc, unsigned int fd, struct file *file)
+{
+ if (proc->files)
+ __fd_install(proc->files, fd, file);
+}
+
+/*
+ * copied from sys_close
+ */
+static long task_close_fd(struct binder_proc *proc, unsigned int fd)
+{
+ int retval;
+
+ if (proc->files == NULL)
+ return -ESRCH;
+
+ retval = __close_fd(proc->files, fd);
+ /* 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;
+}
+
+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 = rlimit_to_nice(current->signal->rlim[RLIMIT_NICE].rlim_cur);
+ binder_debug(BINDER_DEBUG_PRIORITY_CAP,
+ "%d: nice value %ld not allowed use %ld instead\n",
+ current->pid, nice, min_nice);
+ set_user_nice(current, min_nice);
+ if (min_nice <= MAX_NICE)
+ return;
+ binder_user_error("%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,
+ "%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,
+ uintptr_t user_ptr)
+{
+ struct rb_node *n = proc->allocated_buffers.rb_node;
+ struct binder_buffer *buffer;
+ struct binder_buffer *kern_ptr;
+
+ kern_ptr = (struct binder_buffer *)(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,
+ "%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("%d: vma mm and task mm mismatch\n",
+ proc->pid);
+ vma = NULL;
+ }
+ }
+
+ if (allocate == 0)
+ goto free_range;
+
+ if (vma == NULL) {
+ pr_err("%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_HIGHMEM | __GFP_ZERO);
+ if (*page == NULL) {
+ pr_err("%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) {
+ pr_err("%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) {
+ pr_err("%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) {
+ pr_err("%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("%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,
+ "%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) {
+ pr_err("%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,
+ "%d: binder_alloc_buf size %zd got buffer %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,
+ "%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,
+ "%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,
+ "%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,
+ "%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,
+ "%d: merge free, buffer %p do not share page%s%s 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,
+ "%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,
+ "%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,
+ binder_uintptr_t 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,
+ binder_uintptr_t ptr,
+ binder_uintptr_t 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,
+ "%d:%d node %d u%016llx c%016llx created\n",
+ proc->pid, current->pid, node->debug_id,
+ (u64)node->ptr, (u64)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)) {
+ pr_err("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) {
+ pr_err("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,
+ "refless node %d deleted\n",
+ node->debug_id);
+ } else {
+ hlist_del(&node->dead_node);
+ binder_debug(BINDER_DEBUG_INTERNAL_REFS,
+ "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,
+ "%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,
+ "%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,
+ "%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,
+ "%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("%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("%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,
+ "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 {
+ pr_err("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,
+ "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,
+ "reply failed, no target thread at root\n");
+ return;
+ }
+ t = next;
+ binder_debug(BINDER_DEBUG_DEAD_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,
+ binder_size_t *failed_at)
+{
+ binder_size_t *offp, *off_end;
+ int debug_id = buffer->debug_id;
+
+ binder_debug(BINDER_DEBUG_TRANSACTION,
+ "%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 = (binder_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(u32))) {
+ pr_err("transaction release %d bad offset %lld, size %zd\n",
+ debug_id, (u64)*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) {
+ pr_err("transaction release %d bad node %016llx\n",
+ debug_id, (u64)fp->binder);
+ break;
+ }
+ binder_debug(BINDER_DEBUG_TRANSACTION,
+ " node %d u%016llx\n",
+ node->debug_id, (u64)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) {
+ pr_err("transaction release %d bad handle %d\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 %d\n", fp->handle);
+ if (failed_at)
+ task_close_fd(proc, fp->handle);
+ break;
+
+ default:
+ pr_err("transaction release %d bad object type %x\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;
+ binder_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("%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("%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("%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("%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("%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,
+ "%d:%d BC_REPLY %d -> %d:%d, data %016llx-%016llx size %lld-%lld\n",
+ proc->pid, thread->pid, t->debug_id,
+ target_proc->pid, target_thread->pid,
+ (u64)tr->data.ptr.buffer,
+ (u64)tr->data.ptr.offsets,
+ (u64)tr->data_size, (u64)tr->offsets_size);
+ else
+ binder_debug(BINDER_DEBUG_TRANSACTION,
+ "%d:%d BC_TRANSACTION %d -> %d - node %d, data %016llx-%016llx size %lld-%lld\n",
+ proc->pid, thread->pid, t->debug_id,
+ target_proc->pid, target_node->debug_id,
+ (u64)tr->data.ptr.buffer,
+ (u64)tr->data.ptr.offsets,
+ (u64)tr->data_size, (u64)tr->offsets_size);
+
+ if (!reply && !(tr->flags & TF_ONE_WAY))
+ t->from = thread;
+ else
+ t->from = NULL;
+ t->sender_euid = task_euid(proc->tsk);
+ 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 = (binder_size_t *)(t->buffer->data +
+ ALIGN(tr->data_size, sizeof(void *)));
+
+ if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
+ tr->data.ptr.buffer, tr->data_size)) {
+ binder_user_error("%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, (const void __user *)(uintptr_t)
+ tr->data.ptr.offsets, tr->offsets_size)) {
+ binder_user_error("%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(binder_size_t))) {
+ binder_user_error("%d:%d got transaction with invalid offsets size, %lld\n",
+ proc->pid, thread->pid, (u64)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(u32))) {
+ binder_user_error("%d:%d got transaction with invalid offset, %lld\n",
+ proc->pid, thread->pid, (u64)*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("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
+ proc->pid, thread->pid,
+ (u64)fp->binder, node->debug_id,
+ (u64)fp->cookie, (u64)node->cookie);
+ return_error = BR_FAILED_REPLY;
+ 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%016llx -> ref %d desc %d\n",
+ node->debug_id, (u64)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("%d:%d got transaction with invalid handle, %d\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%016llx\n",
+ ref->debug_id, ref->desc, ref->node->debug_id,
+ (u64)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("%d:%d got reply with fd, %d, 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("%d:%d got transaction with fd, %d, 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("%d:%d got transaction with invalid fd, %d\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 %d -> %d\n", fp->handle, target_fd);
+ /* TODO: fput? */
+ fp->handle = target_fd;
+ } break;
+
+ default:
+ binder_user_error("%d:%d got transaction with invalid object type, %x\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,
+ "%d:%d transaction failed %d, size %lld-%lld\n",
+ proc->pid, thread->pid, return_error,
+ (u64)tr->data_size, (u64)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;
+}
+
+static int binder_thread_write(struct binder_proc *proc,
+ struct binder_thread *thread,
+ binder_uintptr_t binder_buffer, size_t size,
+ binder_size_t *consumed)
+{
+ uint32_t cmd;
+ void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
+ 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("%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("%d:%d refcount 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,
+ "%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: {
+ binder_uintptr_t node_ptr;
+ binder_uintptr_t cookie;
+ struct binder_node *node;
+
+ if (get_user(node_ptr, (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+ if (get_user(cookie, (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+ node = binder_get_node(proc, node_ptr);
+ if (node == NULL) {
+ binder_user_error("%d:%d %s u%016llx no match\n",
+ proc->pid, thread->pid,
+ cmd == BC_INCREFS_DONE ?
+ "BC_INCREFS_DONE" :
+ "BC_ACQUIRE_DONE",
+ (u64)node_ptr);
+ break;
+ }
+ if (cookie != node->cookie) {
+ binder_user_error("%d:%d %s u%016llx node %d cookie mismatch %016llx != %016llx\n",
+ proc->pid, thread->pid,
+ cmd == BC_INCREFS_DONE ?
+ "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
+ (u64)node_ptr, node->debug_id,
+ (u64)cookie, (u64)node->cookie);
+ break;
+ }
+ if (cmd == BC_ACQUIRE_DONE) {
+ if (node->pending_strong_ref == 0) {
+ binder_user_error("%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("%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,
+ "%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:
+ pr_err("BC_ATTEMPT_ACQUIRE not supported\n");
+ return -EINVAL;
+ case BC_ACQUIRE_RESULT:
+ pr_err("BC_ACQUIRE_RESULT not supported\n");
+ return -EINVAL;
+
+ case BC_FREE_BUFFER: {
+ binder_uintptr_t data_ptr;
+ struct binder_buffer *buffer;
+
+ if (get_user(data_ptr, (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+
+ buffer = binder_buffer_lookup(proc, data_ptr);
+ if (buffer == NULL) {
+ binder_user_error("%d:%d BC_FREE_BUFFER u%016llx no match\n",
+ proc->pid, thread->pid, (u64)data_ptr);
+ break;
+ }
+ if (!buffer->allow_user_free) {
+ binder_user_error("%d:%d BC_FREE_BUFFER u%016llx matched unreturned buffer\n",
+ proc->pid, thread->pid, (u64)data_ptr);
+ break;
+ }
+ binder_debug(BINDER_DEBUG_FREE_BUFFER,
+ "%d:%d BC_FREE_BUFFER u%016llx found buffer %d for %s transaction\n",
+ proc->pid, thread->pid, (u64)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,
+ "%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("%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("%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,
+ "%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("%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,
+ "%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;
+ binder_uintptr_t 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, (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+ ref = binder_get_ref(proc, target);
+ if (ref == NULL) {
+ binder_user_error("%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,
+ "%d:%d %s %016llx 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",
+ (u64)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("%d:%d BC_REQUEST_DEATH_NOTIFICATION death notification 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,
+ "%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("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification not active\n",
+ proc->pid, thread->pid);
+ break;
+ }
+ death = ref->death;
+ if (death->cookie != cookie) {
+ binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %016llx != %016llx\n",
+ proc->pid, thread->pid,
+ (u64)death->cookie,
+ (u64)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;
+ binder_uintptr_t cookie;
+ struct binder_ref_death *death = NULL;
+
+ if (get_user(cookie, (binder_uintptr_t __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,
+ "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n",
+ proc->pid, thread->pid, (u64)cookie,
+ death);
+ if (death == NULL) {
+ binder_user_error("%d:%d BC_DEAD_BINDER_DONE %016llx not found\n",
+ proc->pid, thread->pid, (u64)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:
+ pr_err("%d:%d unknown command %d\n",
+ proc->pid, thread->pid, cmd);
+ return -EINVAL;
+ }
+ *consumed = ptr - buffer;
+ }
+ return 0;
+}
+
+static 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,
+ binder_uintptr_t binder_buffer, size_t size,
+ binder_size_t *consumed, int non_block)
+{
+ void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
+ 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("%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_freezable_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_freezable(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,
+ "%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,
+ (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+ if (put_user(node->cookie,
+ (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+
+ binder_stat_br(proc, thread, cmd);
+ binder_debug(BINDER_DEBUG_USER_REFS,
+ "%d:%d %s %d u%016llx c%016llx\n",
+ proc->pid, thread->pid, cmd_name,
+ node->debug_id,
+ (u64)node->ptr, (u64)node->cookie);
+ } else {
+ list_del_init(&w->entry);
+ if (!weak && !strong) {
+ binder_debug(BINDER_DEBUG_INTERNAL_REFS,
+ "%d:%d node %d u%016llx c%016llx deleted\n",
+ proc->pid, thread->pid,
+ node->debug_id,
+ (u64)node->ptr,
+ (u64)node->cookie);
+ rb_erase(&node->rb_node, &proc->nodes);
+ kfree(node);
+ binder_stats_deleted(BINDER_STAT_NODE);
+ } else {
+ binder_debug(BINDER_DEBUG_INTERNAL_REFS,
+ "%d:%d node %d u%016llx c%016llx state unchanged\n",
+ proc->pid, thread->pid,
+ node->debug_id,
+ (u64)node->ptr,
+ (u64)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,
+ (binder_uintptr_t __user *)ptr))
+ return -EFAULT;
+ ptr += sizeof(binder_uintptr_t);
+ binder_stat_br(proc, thread, cmd);
+ binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
+ "%d:%d %s %016llx\n",
+ proc->pid, thread->pid,
+ cmd == BR_DEAD_BINDER ?
+ "BR_DEAD_BINDER" :
+ "BR_CLEAR_DEATH_NOTIFICATION_DONE",
+ (u64)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 = 0;
+ tr.cookie = 0;
+ cmd = BR_REPLY;
+ }
+ tr.code = t->code;
+ tr.flags = t->flags;
+ tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);
+
+ if (t->from) {
+ struct task_struct *sender = t->from->proc->tsk;
+
+ tr.sender_pid = task_tgid_nr_ns(sender,
+ task_active_pid_ns(current));
+ } else {
+ tr.sender_pid = 0;
+ }
+
+ tr.data_size = t->buffer->data_size;
+ tr.offsets_size = t->buffer->offsets_size;
+ tr.data.ptr.buffer = (binder_uintptr_t)(
+ (uintptr_t)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,
+ "%d:%d %s %d %d:%d, cmd %d size %zd-%zd ptr %016llx-%016llx\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,
+ (u64)tr.data.ptr.buffer, (u64)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,
+ "%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);
+ } else {
+ binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
+ "undelivered transaction %d\n",
+ t->debug_id);
+ t->buffer->transaction = NULL;
+ kfree(t);
+ binder_stats_deleted(BINDER_STAT_TRANSACTION);
+ }
+ } break;
+ case BINDER_WORK_TRANSACTION_COMPLETE: {
+ binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
+ "undelivered TRANSACTION_COMPLETE\n");
+ kfree(w);
+ binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
+ } break;
+ case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
+ case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
+ struct binder_ref_death *death;
+
+ death = container_of(w, struct binder_ref_death, work);
+ binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
+ "undelivered death notification, %016llx\n",
+ (u64)death->cookie);
+ kfree(death);
+ binder_stats_deleted(BINDER_STAT_DEATH);
+ } break;
+ default:
+ pr_err("unexpected work type, %d, not freed\n",
+ w->type);
+ 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,
+ "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;
+ kuid_t curr_euid = current_euid();
+
+ /*pr_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,
+ "%d:%d write %lld at %016llx, read %lld at %016llx\n",
+ proc->pid, thread->pid,
+ (u64)bwr.write_size, (u64)bwr.write_buffer,
+ (u64)bwr.read_size, (u64)bwr.read_buffer);
+
+ if (bwr.write_size > 0) {
+ ret = binder_thread_write(proc, thread,
+ 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, 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,
+ "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
+ proc->pid, thread->pid,
+ (u64)bwr.write_consumed, (u64)bwr.write_size,
+ (u64)bwr.read_consumed, (u64)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) {
+ pr_err("BINDER_SET_CONTEXT_MGR already set\n");
+ ret = -EBUSY;
+ goto err;
+ }
+ if (uid_valid(binder_context_mgr_uid)) {
+ if (!uid_eq(binder_context_mgr_uid, curr_euid)) {
+ pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
+ from_kuid(&init_user_ns, curr_euid),
+ from_kuid(&init_user_ns, binder_context_mgr_uid));
+ ret = -EPERM;
+ goto err;
+ }
+ } else {
+ binder_context_mgr_uid = curr_euid;
+ }
+ binder_context_mgr_node = binder_new_node(proc, 0, 0);
+ 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, "%d:%d exit\n",
+ proc->pid, thread->pid);
+ binder_free_thread(proc, thread);
+ thread = NULL;
+ break;
+ case BINDER_VERSION: {
+ struct binder_version __user *ver = ubuf;
+
+ if (size != sizeof(struct binder_version)) {
+ ret = -EINVAL;
+ goto err;
+ }
+ if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
+ &ver->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)
+ pr_info("%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,
+ "%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,
+ "%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 (proc->tsk != current)
+ return -EINVAL;
+
+ 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))) {
+ pr_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(current);
+ proc->vma = vma;
+ proc->vma_vm_mm = vma->vm_mm;
+
+ /*pr_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:
+ pr_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 int binder_node_release(struct binder_node *node, int refs)
+{
+ struct binder_ref *ref;
+ int death = 0;
+
+ list_del_init(&node->work.entry);
+ binder_release_work(&node->async_todo);
+
+ if (hlist_empty(&node->refs)) {
+ kfree(node);
+ binder_stats_deleted(BINDER_STAT_NODE);
+
+ return refs;
+ }
+
+ 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, &node->refs, node_entry) {
+ refs++;
+
+ if (!ref->death)
+ continue;
+
+ 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,
+ "node %d now dead, refs %d, death %d\n",
+ node->debug_id, refs, death);
+
+ return refs;
+}
+
+static void binder_deferred_release(struct binder_proc *proc)
+{
+ 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,
+ "%s: %d context_mgr_node gone\n",
+ __func__, proc->pid);
+ binder_context_mgr_node = NULL;
+ }
+
+ threads = 0;
+ active_transactions = 0;
+ while ((n = rb_first(&proc->threads))) {
+ struct binder_thread *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;
+
+ node = rb_entry(n, struct binder_node, rb_node);
+ nodes++;
+ rb_erase(&node->rb_node, &proc->nodes);
+ incoming_refs = binder_node_release(node, incoming_refs);
+ }
+
+ outgoing_refs = 0;
+ while ((n = rb_first(&proc->refs_by_desc))) {
+ struct binder_ref *ref;
+
+ ref = rb_entry(n, struct binder_ref, rb_node_desc);
+ outgoing_refs++;
+ binder_delete_ref(ref);
+ }
+
+ binder_release_work(&proc->todo);
+ binder_release_work(&proc->delivered_death);
+
+ buffers = 0;
+ while ((n = rb_first(&proc->allocated_buffers))) {
+ struct binder_buffer *buffer;
+
+ buffer = rb_entry(n, struct binder_buffer, rb_node);
+
+ t = buffer->transaction;
+ if (t) {
+ t->buffer = NULL;
+ buffer->transaction = NULL;
+ pr_err("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++) {
+ void *page_addr;
+
+ if (!proc->pages[i])
+ continue;
+
+ page_addr = proc->buffer + i * PAGE_SIZE;
+ binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
+ "%s: %d: page %d at %p not freed\n",
+ __func__, 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,
+ "%s: %d threads %d, nodes %d (ref %d), refs %d, active transactions %d, buffers %d, pages %d\n",
+ __func__, 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%016llx c%016llx\n",
+ prefix, node->debug_id,
+ (u64)node->ptr, (u64)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 binder_work *w;
+ int count;
+
+ count = 0;
+ hlist_for_each_entry(ref, &node->refs, node_entry)
+ count++;
+
+ seq_printf(m, " node %d: u%016llx c%016llx hs %d hw %d ls %d lw %d is %d iw %d",
+ node->debug_id, (u64)node->ptr, (u64)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, &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 * const 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 * const 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 * const 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 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, &binder_dead_nodes, dead_node)
+ print_binder_node(m, node);
+
+ hlist_for_each_entry(proc, &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;
+ 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, &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;
+ int do_lock = !binder_debug_no_lock;
+
+ if (do_lock)
+ binder_lock(__func__);
+
+ seq_puts(m, "binder transactions:\n");
+ hlist_for_each_entry(proc, &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,
+ .compat_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 00000000000..eb0834656df
--- /dev/null
+++ b/drivers/staging/android/binder.h
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+#ifdef CONFIG_ANDROID_BINDER_IPC_32BIT
+#define BINDER_IPC_32BIT 1
+#endif
+
+#include "uapi/binder.h"
+
+#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 00000000000..7f20f3dc836
--- /dev/null
+++ b/drivers/staging/android/binder_trace.h
@@ -0,0 +1,329 @@
+/*
+ * 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 <linux/tracepoint.h>
+
+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(binder_uintptr_t, 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%016llx ==> dest_ref=%d dest_desc=%d",
+ __entry->debug_id, __entry->node_debug_id,
+ (u64)__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(binder_uintptr_t, 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%016llx",
+ __entry->debug_id, __entry->node_debug_id,
+ __entry->ref_debug_id, __entry->ref_desc,
+ (u64)__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 <trace/define_trace.h>
diff --git a/drivers/staging/android/ion/Kconfig b/drivers/staging/android/ion/Kconfig
new file mode 100644
index 00000000000..0f8fec1f84e
--- /dev/null
+++ b/drivers/staging/android/ion/Kconfig
@@ -0,0 +1,35 @@
+menuconfig ION
+ bool "Ion Memory Manager"
+ depends on HAVE_MEMBLOCK
+ select GENERIC_ALLOCATOR
+ select DMA_SHARED_BUFFER
+ ---help---
+ Chose this option to enable the ION Memory Manager,
+ used by Android to efficiently allocate buffers
+ from userspace that can be shared between drivers.
+ If you're not using Android its probably safe to
+ say N here.
+
+config ION_TEST
+ tristate "Ion Test Device"
+ depends on ION
+ help
+ Choose this option to create a device that can be used to test the
+ kernel and device side ION functions.
+
+config ION_DUMMY
+ bool "Dummy Ion driver"
+ depends on ION
+ help
+ Provides a dummy ION driver that registers the
+ /dev/ion device and some basic heaps. This can
+ be used for testing the ION infrastructure if
+ one doesn't have access to hardware drivers that
+ use ION.
+
+config ION_TEGRA
+ tristate "Ion for Tegra"
+ depends on ARCH_TEGRA && ION
+ help
+ Choose this option if you wish to use ion on an nVidia Tegra.
+
diff --git a/drivers/staging/android/ion/Makefile b/drivers/staging/android/ion/Makefile
new file mode 100644
index 00000000000..b56fd2bf2b4
--- /dev/null
+++ b/drivers/staging/android/ion/Makefile
@@ -0,0 +1,10 @@
+obj-$(CONFIG_ION) += ion.o ion_heap.o ion_page_pool.o ion_system_heap.o \
+ ion_carveout_heap.o ion_chunk_heap.o ion_cma_heap.o
+obj-$(CONFIG_ION_TEST) += ion_test.o
+ifdef CONFIG_COMPAT
+obj-$(CONFIG_ION) += compat_ion.o
+endif
+
+obj-$(CONFIG_ION_DUMMY) += ion_dummy_driver.o
+obj-$(CONFIG_ION_TEGRA) += tegra/
+
diff --git a/drivers/staging/android/ion/compat_ion.c b/drivers/staging/android/ion/compat_ion.c
new file mode 100644
index 00000000000..ee3a7380e53
--- /dev/null
+++ b/drivers/staging/android/ion/compat_ion.c
@@ -0,0 +1,195 @@
+/*
+ * drivers/staging/android/ion/compat_ion.c
+ *
+ * Copyright (C) 2013 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 <linux/compat.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#include "ion.h"
+#include "compat_ion.h"
+
+/* See drivers/staging/android/uapi/ion.h for the definition of these structs */
+struct compat_ion_allocation_data {
+ compat_size_t len;
+ compat_size_t align;
+ compat_uint_t heap_id_mask;
+ compat_uint_t flags;
+ compat_int_t handle;
+};
+
+struct compat_ion_custom_data {
+ compat_uint_t cmd;
+ compat_ulong_t arg;
+};
+
+struct compat_ion_handle_data {
+ compat_int_t handle;
+};
+
+#define COMPAT_ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \
+ struct compat_ion_allocation_data)
+#define COMPAT_ION_IOC_FREE _IOWR(ION_IOC_MAGIC, 1, \
+ struct compat_ion_handle_data)
+#define COMPAT_ION_IOC_CUSTOM _IOWR(ION_IOC_MAGIC, 6, \
+ struct compat_ion_custom_data)
+
+static int compat_get_ion_allocation_data(
+ struct compat_ion_allocation_data __user *data32,
+ struct ion_allocation_data __user *data)
+{
+ compat_size_t s;
+ compat_uint_t u;
+ compat_int_t i;
+ int err;
+
+ err = get_user(s, &data32->len);
+ err |= put_user(s, &data->len);
+ err |= get_user(s, &data32->align);
+ err |= put_user(s, &data->align);
+ err |= get_user(u, &data32->heap_id_mask);
+ err |= put_user(u, &data->heap_id_mask);
+ err |= get_user(u, &data32->flags);
+ err |= put_user(u, &data->flags);
+ err |= get_user(i, &data32->handle);
+ err |= put_user(i, &data->handle);
+
+ return err;
+}
+
+static int compat_get_ion_handle_data(
+ struct compat_ion_handle_data __user *data32,
+ struct ion_handle_data __user *data)
+{
+ compat_int_t i;
+ int err;
+
+ err = get_user(i, &data32->handle);
+ err |= put_user(i, &data->handle);
+
+ return err;
+}
+
+static int compat_put_ion_allocation_data(
+ struct compat_ion_allocation_data __user *data32,
+ struct ion_allocation_data __user *data)
+{
+ compat_size_t s;
+ compat_uint_t u;
+ compat_int_t i;
+ int err;
+
+ err = get_user(s, &data->len);
+ err |= put_user(s, &data32->len);
+ err |= get_user(s, &data->align);
+ err |= put_user(s, &data32->align);
+ err |= get_user(u, &data->heap_id_mask);
+ err |= put_user(u, &data32->heap_id_mask);
+ err |= get_user(u, &data->flags);
+ err |= put_user(u, &data32->flags);
+ err |= get_user(i, &data->handle);
+ err |= put_user(i, &data32->handle);
+
+ return err;
+}
+
+static int compat_get_ion_custom_data(
+ struct compat_ion_custom_data __user *data32,
+ struct ion_custom_data __user *data)
+{
+ compat_uint_t cmd;
+ compat_ulong_t arg;
+ int err;
+
+ err = get_user(cmd, &data32->cmd);
+ err |= put_user(cmd, &data->cmd);
+ err |= get_user(arg, &data32->arg);
+ err |= put_user(arg, &data->arg);
+
+ return err;
+};
+
+long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long ret;
+
+ if (!filp->f_op || !filp->f_op->unlocked_ioctl)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case COMPAT_ION_IOC_ALLOC:
+ {
+ struct compat_ion_allocation_data __user *data32;
+ struct ion_allocation_data __user *data;
+ int err;
+
+ data32 = compat_ptr(arg);
+ data = compat_alloc_user_space(sizeof(*data));
+ if (data == NULL)
+ return -EFAULT;
+
+ err = compat_get_ion_allocation_data(data32, data);
+ if (err)
+ return err;
+ ret = filp->f_op->unlocked_ioctl(filp, ION_IOC_ALLOC,
+ (unsigned long)data);
+ err = compat_put_ion_allocation_data(data32, data);
+ return ret ? ret : err;
+ }
+ case COMPAT_ION_IOC_FREE:
+ {
+ struct compat_ion_handle_data __user *data32;
+ struct ion_handle_data __user *data;
+ int err;
+
+ data32 = compat_ptr(arg);
+ data = compat_alloc_user_space(sizeof(*data));
+ if (data == NULL)
+ return -EFAULT;
+
+ err = compat_get_ion_handle_data(data32, data);
+ if (err)
+ return err;
+
+ return filp->f_op->unlocked_ioctl(filp, ION_IOC_FREE,
+ (unsigned long)data);
+ }
+ case COMPAT_ION_IOC_CUSTOM: {
+ struct compat_ion_custom_data __user *data32;
+ struct ion_custom_data __user *data;
+ int err;
+
+ data32 = compat_ptr(arg);
+ data = compat_alloc_user_space(sizeof(*data));
+ if (data == NULL)
+ return -EFAULT;
+
+ err = compat_get_ion_custom_data(data32, data);
+ if (err)
+ return err;
+
+ return filp->f_op->unlocked_ioctl(filp, ION_IOC_CUSTOM,
+ (unsigned long)data);
+ }
+ case ION_IOC_SHARE:
+ case ION_IOC_MAP:
+ case ION_IOC_IMPORT:
+ case ION_IOC_SYNC:
+ return filp->f_op->unlocked_ioctl(filp, cmd,
+ (unsigned long)compat_ptr(arg));
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
diff --git a/drivers/staging/android/ion/compat_ion.h b/drivers/staging/android/ion/compat_ion.h
new file mode 100644
index 00000000000..c2ad5893dfd
--- /dev/null
+++ b/drivers/staging/android/ion/compat_ion.h
@@ -0,0 +1,30 @@
+/*
+
+ * drivers/staging/android/ion/compat_ion.h
+ *
+ * Copyright (C) 2013 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_COMPAT_ION_H
+#define _LINUX_COMPAT_ION_H
+
+#if IS_ENABLED(CONFIG_COMPAT)
+
+long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+
+#else
+
+#define compat_ion_ioctl NULL
+
+#endif /* CONFIG_COMPAT */
+#endif /* _LINUX_COMPAT_ION_H */
diff --git a/drivers/staging/android/ion/ion.c b/drivers/staging/android/ion/ion.c
new file mode 100644
index 00000000000..389b8f67a2e
--- /dev/null
+++ b/drivers/staging/android/ion/ion.c
@@ -0,0 +1,1645 @@
+/*
+
+ * drivers/staging/android/ion/ion.c
+ *
+ * Copyright (C) 2011 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 <linux/device.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/freezer.h>
+#include <linux/fs.h>
+#include <linux/anon_inodes.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/memblock.h>
+#include <linux/miscdevice.h>
+#include <linux/export.h>
+#include <linux/mm.h>
+#include <linux/mm_types.h>
+#include <linux/rbtree.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/idr.h>
+
+#include "ion.h"
+#include "ion_priv.h"
+#include "compat_ion.h"
+
+/**
+ * struct ion_device - the metadata of the ion device node
+ * @dev: the actual misc device
+ * @buffers: an rb tree of all the existing buffers
+ * @buffer_lock: lock protecting the tree of buffers
+ * @lock: rwsem protecting the tree of heaps and clients
+ * @heaps: list of all the heaps in the system
+ * @user_clients: list of all the clients created from userspace
+ */
+struct ion_device {
+ struct miscdevice dev;
+ struct rb_root buffers;
+ struct mutex buffer_lock;
+ struct rw_semaphore lock;
+ struct plist_head heaps;
+ long (*custom_ioctl)(struct ion_client *client, unsigned int cmd,
+ unsigned long arg);
+ struct rb_root clients;
+ struct dentry *debug_root;
+ struct dentry *heaps_debug_root;
+ struct dentry *clients_debug_root;
+};
+
+/**
+ * struct ion_client - a process/hw block local address space
+ * @node: node in the tree of all clients
+ * @dev: backpointer to ion device
+ * @handles: an rb tree of all the handles in this client
+ * @idr: an idr space for allocating handle ids
+ * @lock: lock protecting the tree of handles
+ * @name: used for debugging
+ * @display_name: used for debugging (unique version of @name)
+ * @display_serial: used for debugging (to make display_name unique)
+ * @task: used for debugging
+ *
+ * A client represents a list of buffers this client may access.
+ * The mutex stored here is used to protect both handles tree
+ * as well as the handles themselves, and should be held while modifying either.
+ */
+struct ion_client {
+ struct rb_node node;
+ struct ion_device *dev;
+ struct rb_root handles;
+ struct idr idr;
+ struct mutex lock;
+ const char *name;
+ char *display_name;
+ int display_serial;
+ struct task_struct *task;
+ pid_t pid;
+ struct dentry *debug_root;
+};
+
+/**
+ * ion_handle - a client local reference to a buffer
+ * @ref: reference count
+ * @client: back pointer to the client the buffer resides in
+ * @buffer: pointer to the buffer
+ * @node: node in the client's handle rbtree
+ * @kmap_cnt: count of times this client has mapped to kernel
+ * @id: client-unique id allocated by client->idr
+ *
+ * Modifications to node, map_cnt or mapping should be protected by the
+ * lock in the client. Other fields are never changed after initialization.
+ */
+struct ion_handle {
+ struct kref ref;
+ struct ion_client *client;
+ struct ion_buffer *buffer;
+ struct rb_node node;
+ unsigned int kmap_cnt;
+ int id;
+};
+
+bool ion_buffer_fault_user_mappings(struct ion_buffer *buffer)
+{
+ return (buffer->flags & ION_FLAG_CACHED) &&
+ !(buffer->flags & ION_FLAG_CACHED_NEEDS_SYNC);
+}
+
+bool ion_buffer_cached(struct ion_buffer *buffer)
+{
+ return !!(buffer->flags & ION_FLAG_CACHED);
+}
+
+static inline struct page *ion_buffer_page(struct page *page)
+{
+ return (struct page *)((unsigned long)page & ~(1UL));
+}
+
+static inline bool ion_buffer_page_is_dirty(struct page *page)
+{
+ return !!((unsigned long)page & 1UL);
+}
+
+static inline void ion_buffer_page_dirty(struct page **page)
+{
+ *page = (struct page *)((unsigned long)(*page) | 1UL);
+}
+
+static inline void ion_buffer_page_clean(struct page **page)
+{
+ *page = (struct page *)((unsigned long)(*page) & ~(1UL));
+}
+
+/* this function should only be called while dev->lock is held */
+static void ion_buffer_add(struct ion_device *dev,
+ struct ion_buffer *buffer)
+{
+ struct rb_node **p = &dev->buffers.rb_node;
+ struct rb_node *parent = NULL;
+ struct ion_buffer *entry;
+
+ while (*p) {
+ parent = *p;
+ entry = rb_entry(parent, struct ion_buffer, node);
+
+ if (buffer < entry) {
+ p = &(*p)->rb_left;
+ } else if (buffer > entry) {
+ p = &(*p)->rb_right;
+ } else {
+ pr_err("%s: buffer already found.", __func__);
+ BUG();
+ }
+ }
+
+ rb_link_node(&buffer->node, parent, p);
+ rb_insert_color(&buffer->node, &dev->buffers);
+}
+
+/* this function should only be called while dev->lock is held */
+static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
+ struct ion_device *dev,
+ unsigned long len,
+ unsigned long align,
+ unsigned long flags)
+{
+ struct ion_buffer *buffer;
+ struct sg_table *table;
+ struct scatterlist *sg;
+ int i, ret;
+
+ buffer = kzalloc(sizeof(struct ion_buffer), GFP_KERNEL);
+ if (!buffer)
+ return ERR_PTR(-ENOMEM);
+
+ buffer->heap = heap;
+ buffer->flags = flags;
+ kref_init(&buffer->ref);
+
+ ret = heap->ops->allocate(heap, buffer, len, align, flags);
+
+ if (ret) {
+ if (!(heap->flags & ION_HEAP_FLAG_DEFER_FREE))
+ goto err2;
+
+ ion_heap_freelist_drain(heap, 0);
+ ret = heap->ops->allocate(heap, buffer, len, align,
+ flags);
+ if (ret)
+ goto err2;
+ }
+
+ buffer->dev = dev;
+ buffer->size = len;
+
+ table = heap->ops->map_dma(heap, buffer);
+ if (WARN_ONCE(table == NULL,
+ "heap->ops->map_dma should return ERR_PTR on error"))
+ table = ERR_PTR(-EINVAL);
+ if (IS_ERR(table)) {
+ heap->ops->free(buffer);
+ kfree(buffer);
+ return ERR_CAST(table);
+ }
+ buffer->sg_table = table;
+ if (ion_buffer_fault_user_mappings(buffer)) {
+ int num_pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
+ struct scatterlist *sg;
+ int i, j, k = 0;
+
+ buffer->pages = vmalloc(sizeof(struct page *) * num_pages);
+ if (!buffer->pages) {
+ ret = -ENOMEM;
+ goto err1;
+ }
+
+ for_each_sg(table->sgl, sg, table->nents, i) {
+ struct page *page = sg_page(sg);
+
+ for (j = 0; j < sg->length / PAGE_SIZE; j++)
+ buffer->pages[k++] = page++;
+ }
+
+ if (ret)
+ goto err;
+ }
+
+ buffer->dev = dev;
+ buffer->size = len;
+ INIT_LIST_HEAD(&buffer->vmas);
+ mutex_init(&buffer->lock);
+ /* this will set up dma addresses for the sglist -- it is not
+ technically correct as per the dma api -- a specific
+ device isn't really taking ownership here. However, in practice on
+ our systems the only dma_address space is physical addresses.
+ Additionally, we can't afford the overhead of invalidating every
+ allocation via dma_map_sg. The implicit contract here is that
+ memory comming from the heaps is ready for dma, ie if it has a
+ cached mapping that mapping has been invalidated */
+ for_each_sg(buffer->sg_table->sgl, sg, buffer->sg_table->nents, i)
+ sg_dma_address(sg) = sg_phys(sg);
+ mutex_lock(&dev->buffer_lock);
+ ion_buffer_add(dev, buffer);
+ mutex_unlock(&dev->buffer_lock);
+ return buffer;
+
+err:
+ heap->ops->unmap_dma(heap, buffer);
+ heap->ops->free(buffer);
+err1:
+ if (buffer->pages)
+ vfree(buffer->pages);
+err2:
+ kfree(buffer);
+ return ERR_PTR(ret);
+}
+
+void ion_buffer_destroy(struct ion_buffer *buffer)
+{
+ if (WARN_ON(buffer->kmap_cnt > 0))
+ buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
+ buffer->heap->ops->unmap_dma(buffer->heap, buffer);
+ buffer->heap->ops->free(buffer);
+ if (buffer->pages)
+ vfree(buffer->pages);
+ kfree(buffer);
+}
+
+static void _ion_buffer_destroy(struct kref *kref)
+{
+ struct ion_buffer *buffer = container_of(kref, struct ion_buffer, ref);
+ struct ion_heap *heap = buffer->heap;
+ struct ion_device *dev = buffer->dev;
+
+ mutex_lock(&dev->buffer_lock);
+ rb_erase(&buffer->node, &dev->buffers);
+ mutex_unlock(&dev->buffer_lock);
+
+ if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
+ ion_heap_freelist_add(heap, buffer);
+ else
+ ion_buffer_destroy(buffer);
+}
+
+static void ion_buffer_get(struct ion_buffer *buffer)
+{
+ kref_get(&buffer->ref);
+}
+
+static int ion_buffer_put(struct ion_buffer *buffer)
+{
+ return kref_put(&buffer->ref, _ion_buffer_destroy);
+}
+
+static void ion_buffer_add_to_handle(struct ion_buffer *buffer)
+{
+ mutex_lock(&buffer->lock);
+ buffer->handle_count++;
+ mutex_unlock(&buffer->lock);
+}
+
+static void ion_buffer_remove_from_handle(struct ion_buffer *buffer)
+{
+ /*
+ * when a buffer is removed from a handle, if it is not in
+ * any other handles, copy the taskcomm and the pid of the
+ * process it's being removed from into the buffer. At this
+ * point there will be no way to track what processes this buffer is
+ * being used by, it only exists as a dma_buf file descriptor.
+ * The taskcomm and pid can provide a debug hint as to where this fd
+ * is in the system
+ */
+ mutex_lock(&buffer->lock);
+ buffer->handle_count--;
+ BUG_ON(buffer->handle_count < 0);
+ if (!buffer->handle_count) {
+ struct task_struct *task;
+
+ task = current->group_leader;
+ get_task_comm(buffer->task_comm, task);
+ buffer->pid = task_pid_nr(task);
+ }
+ mutex_unlock(&buffer->lock);
+}
+
+static struct ion_handle *ion_handle_create(struct ion_client *client,
+ struct ion_buffer *buffer)
+{
+ struct ion_handle *handle;
+
+ handle = kzalloc(sizeof(struct ion_handle), GFP_KERNEL);
+ if (!handle)
+ return ERR_PTR(-ENOMEM);
+ kref_init(&handle->ref);
+ RB_CLEAR_NODE(&handle->node);
+ handle->client = client;
+ ion_buffer_get(buffer);
+ ion_buffer_add_to_handle(buffer);
+ handle->buffer = buffer;
+
+ return handle;
+}
+
+static void ion_handle_kmap_put(struct ion_handle *);
+
+static void ion_handle_destroy(struct kref *kref)
+{
+ struct ion_handle *handle = container_of(kref, struct ion_handle, ref);
+ struct ion_client *client = handle->client;
+ struct ion_buffer *buffer = handle->buffer;
+
+ mutex_lock(&buffer->lock);
+ while (handle->kmap_cnt)
+ ion_handle_kmap_put(handle);
+ mutex_unlock(&buffer->lock);
+
+ idr_remove(&client->idr, handle->id);
+ if (!RB_EMPTY_NODE(&handle->node))
+ rb_erase(&handle->node, &client->handles);
+
+ ion_buffer_remove_from_handle(buffer);
+ ion_buffer_put(buffer);
+
+ kfree(handle);
+}
+
+struct ion_buffer *ion_handle_buffer(struct ion_handle *handle)
+{
+ return handle->buffer;
+}
+
+static void ion_handle_get(struct ion_handle *handle)
+{
+ kref_get(&handle->ref);
+}
+
+static int ion_handle_put(struct ion_handle *handle)
+{
+ struct ion_client *client = handle->client;
+ int ret;
+
+ mutex_lock(&client->lock);
+ ret = kref_put(&handle->ref, ion_handle_destroy);
+ mutex_unlock(&client->lock);
+
+ return ret;
+}
+
+static struct ion_handle *ion_handle_lookup(struct ion_client *client,
+ struct ion_buffer *buffer)
+{
+ struct rb_node *n = client->handles.rb_node;
+
+ while (n) {
+ struct ion_handle *entry = rb_entry(n, struct ion_handle, node);
+
+ if (buffer < entry->buffer)
+ n = n->rb_left;
+ else if (buffer > entry->buffer)
+ n = n->rb_right;
+ else
+ return entry;
+ }
+ return ERR_PTR(-EINVAL);
+}
+
+static struct ion_handle *ion_handle_get_by_id(struct ion_client *client,
+ int id)
+{
+ struct ion_handle *handle;
+
+ mutex_lock(&client->lock);
+ handle = idr_find(&client->idr, id);
+ if (handle)
+ ion_handle_get(handle);
+ mutex_unlock(&client->lock);
+
+ return handle ? handle : ERR_PTR(-EINVAL);
+}
+
+static bool ion_handle_validate(struct ion_client *client,
+ struct ion_handle *handle)
+{
+ WARN_ON(!mutex_is_locked(&client->lock));
+ return idr_find(&client->idr, handle->id) == handle;
+}
+
+static int ion_handle_add(struct ion_client *client, struct ion_handle *handle)
+{
+ int id;
+ struct rb_node **p = &client->handles.rb_node;
+ struct rb_node *parent = NULL;
+ struct ion_handle *entry;
+
+ id = idr_alloc(&client->idr, handle, 1, 0, GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ handle->id = id;
+
+ while (*p) {
+ parent = *p;
+ entry = rb_entry(parent, struct ion_handle, node);
+
+ if (handle->buffer < entry->buffer)
+ p = &(*p)->rb_left;
+ else if (handle->buffer > entry->buffer)
+ p = &(*p)->rb_right;
+ else
+ WARN(1, "%s: buffer already found.", __func__);
+ }
+
+ rb_link_node(&handle->node, parent, p);
+ rb_insert_color(&handle->node, &client->handles);
+
+ return 0;
+}
+
+struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
+ size_t align, unsigned int heap_id_mask,
+ unsigned int flags)
+{
+ struct ion_handle *handle;
+ struct ion_device *dev = client->dev;
+ struct ion_buffer *buffer = NULL;
+ struct ion_heap *heap;
+ int ret;
+
+ pr_debug("%s: len %zu align %zu heap_id_mask %u flags %x\n", __func__,
+ len, align, heap_id_mask, flags);
+ /*
+ * traverse the list of heaps available in this system in priority
+ * order. If the heap type is supported by the client, and matches the
+ * request of the caller allocate from it. Repeat until allocate has
+ * succeeded or all heaps have been tried
+ */
+ len = PAGE_ALIGN(len);
+
+ if (!len)
+ return ERR_PTR(-EINVAL);
+
+ down_read(&dev->lock);
+ plist_for_each_entry(heap, &dev->heaps, node) {
+ /* if the caller didn't specify this heap id */
+ if (!((1 << heap->id) & heap_id_mask))
+ continue;
+ buffer = ion_buffer_create(heap, dev, len, align, flags);
+ if (!IS_ERR(buffer))
+ break;
+ }
+ up_read(&dev->lock);
+
+ if (buffer == NULL)
+ return ERR_PTR(-ENODEV);
+
+ if (IS_ERR(buffer))
+ return ERR_CAST(buffer);
+
+ handle = ion_handle_create(client, buffer);
+
+ /*
+ * ion_buffer_create will create a buffer with a ref_cnt of 1,
+ * and ion_handle_create will take a second reference, drop one here
+ */
+ ion_buffer_put(buffer);
+
+ if (IS_ERR(handle))
+ return handle;
+
+ mutex_lock(&client->lock);
+ ret = ion_handle_add(client, handle);
+ mutex_unlock(&client->lock);
+ if (ret) {
+ ion_handle_put(handle);
+ handle = ERR_PTR(ret);
+ }
+
+ return handle;
+}
+EXPORT_SYMBOL(ion_alloc);
+
+void ion_free(struct ion_client *client, struct ion_handle *handle)
+{
+ bool valid_handle;
+
+ BUG_ON(client != handle->client);
+
+ mutex_lock(&client->lock);
+ valid_handle = ion_handle_validate(client, handle);
+
+ if (!valid_handle) {
+ WARN(1, "%s: invalid handle passed to free.\n", __func__);
+ mutex_unlock(&client->lock);
+ return;
+ }
+ mutex_unlock(&client->lock);
+ ion_handle_put(handle);
+}
+EXPORT_SYMBOL(ion_free);
+
+int ion_phys(struct ion_client *client, struct ion_handle *handle,
+ ion_phys_addr_t *addr, size_t *len)
+{
+ struct ion_buffer *buffer;
+ int ret;
+
+ mutex_lock(&client->lock);
+ if (!ion_handle_validate(client, handle)) {
+ mutex_unlock(&client->lock);
+ return -EINVAL;
+ }
+
+ buffer = handle->buffer;
+
+ if (!buffer->heap->ops->phys) {
+ pr_err("%s: ion_phys is not implemented by this heap.\n",
+ __func__);
+ mutex_unlock(&client->lock);
+ return -ENODEV;
+ }
+ mutex_unlock(&client->lock);
+ ret = buffer->heap->ops->phys(buffer->heap, buffer, addr, len);
+ return ret;
+}
+EXPORT_SYMBOL(ion_phys);
+
+static void *ion_buffer_kmap_get(struct ion_buffer *buffer)
+{
+ void *vaddr;
+
+ if (buffer->kmap_cnt) {
+ buffer->kmap_cnt++;
+ return buffer->vaddr;
+ }
+ vaddr = buffer->heap->ops->map_kernel(buffer->heap, buffer);
+ if (WARN_ONCE(vaddr == NULL,
+ "heap->ops->map_kernel should return ERR_PTR on error"))
+ return ERR_PTR(-EINVAL);
+ if (IS_ERR(vaddr))
+ return vaddr;
+ buffer->vaddr = vaddr;
+ buffer->kmap_cnt++;
+ return vaddr;
+}
+
+static void *ion_handle_kmap_get(struct ion_handle *handle)
+{
+ struct ion_buffer *buffer = handle->buffer;
+ void *vaddr;
+
+ if (handle->kmap_cnt) {
+ handle->kmap_cnt++;
+ return buffer->vaddr;
+ }
+ vaddr = ion_buffer_kmap_get(buffer);
+ if (IS_ERR(vaddr))
+ return vaddr;
+ handle->kmap_cnt++;
+ return vaddr;
+}
+
+static void ion_buffer_kmap_put(struct ion_buffer *buffer)
+{
+ buffer->kmap_cnt--;
+ if (!buffer->kmap_cnt) {
+ buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
+ buffer->vaddr = NULL;
+ }
+}
+
+static void ion_handle_kmap_put(struct ion_handle *handle)
+{
+ struct ion_buffer *buffer = handle->buffer;
+
+ if (!handle->kmap_cnt) {
+ WARN(1, "%s: Double unmap detected! bailing...\n", __func__);
+ return;
+ }
+ handle->kmap_cnt--;
+ if (!handle->kmap_cnt)
+ ion_buffer_kmap_put(buffer);
+}
+
+void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle)
+{
+ struct ion_buffer *buffer;
+ void *vaddr;
+
+ mutex_lock(&client->lock);
+ if (!ion_handle_validate(client, handle)) {
+ pr_err("%s: invalid handle passed to map_kernel.\n",
+ __func__);
+ mutex_unlock(&client->lock);
+ return ERR_PTR(-EINVAL);
+ }
+
+ buffer = handle->buffer;
+
+ if (!handle->buffer->heap->ops->map_kernel) {
+ pr_err("%s: map_kernel is not implemented by this heap.\n",
+ __func__);
+ mutex_unlock(&client->lock);
+ return ERR_PTR(-ENODEV);
+ }
+
+ mutex_lock(&buffer->lock);
+ vaddr = ion_handle_kmap_get(handle);
+ mutex_unlock(&buffer->lock);
+ mutex_unlock(&client->lock);
+ return vaddr;
+}
+EXPORT_SYMBOL(ion_map_kernel);
+
+void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle)
+{
+ struct ion_buffer *buffer;
+
+ mutex_lock(&client->lock);
+ buffer = handle->buffer;
+ mutex_lock(&buffer->lock);
+ ion_handle_kmap_put(handle);
+ mutex_unlock(&buffer->lock);
+ mutex_unlock(&client->lock);
+}
+EXPORT_SYMBOL(ion_unmap_kernel);
+
+static int ion_debug_client_show(struct seq_file *s, void *unused)
+{
+ struct ion_client *client = s->private;
+ struct rb_node *n;
+ size_t sizes[ION_NUM_HEAP_IDS] = {0};
+ const char *names[ION_NUM_HEAP_IDS] = {NULL};
+ int i;
+
+ mutex_lock(&client->lock);
+ for (n = rb_first(&client->handles); n; n = rb_next(n)) {
+ struct ion_handle *handle = rb_entry(n, struct ion_handle,
+ node);
+ unsigned int id = handle->buffer->heap->id;
+
+ if (!names[id])
+ names[id] = handle->buffer->heap->name;
+ sizes[id] += handle->buffer->size;
+ }
+ mutex_unlock(&client->lock);
+
+ seq_printf(s, "%16.16s: %16.16s\n", "heap_name", "size_in_bytes");
+ for (i = 0; i < ION_NUM_HEAP_IDS; i++) {
+ if (!names[i])
+ continue;
+ seq_printf(s, "%16.16s: %16zu\n", names[i], sizes[i]);
+ }
+ return 0;
+}
+
+static int ion_debug_client_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ion_debug_client_show, inode->i_private);
+}
+
+static const struct file_operations debug_client_fops = {
+ .open = ion_debug_client_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int ion_get_client_serial(const struct rb_root *root,
+ const unsigned char *name)
+{
+ int serial = -1;
+ struct rb_node *node;
+
+ for (node = rb_first(root); node; node = rb_next(node)) {
+ struct ion_client *client = rb_entry(node, struct ion_client,
+ node);
+
+ if (strcmp(client->name, name))
+ continue;
+ serial = max(serial, client->display_serial);
+ }
+ return serial + 1;
+}
+
+struct ion_client *ion_client_create(struct ion_device *dev,
+ const char *name)
+{
+ struct ion_client *client;
+ struct task_struct *task;
+ struct rb_node **p;
+ struct rb_node *parent = NULL;
+ struct ion_client *entry;
+ pid_t pid;
+
+ if (!name) {
+ pr_err("%s: Name cannot be null\n", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+
+ get_task_struct(current->group_leader);
+ task_lock(current->group_leader);
+ pid = task_pid_nr(current->group_leader);
+ /* don't bother to store task struct for kernel threads,
+ they can't be killed anyway */
+ if (current->group_leader->flags & PF_KTHREAD) {
+ put_task_struct(current->group_leader);
+ task = NULL;
+ } else {
+ task = current->group_leader;
+ }
+ task_unlock(current->group_leader);
+
+ client = kzalloc(sizeof(struct ion_client), GFP_KERNEL);
+ if (!client)
+ goto err_put_task_struct;
+
+ client->dev = dev;
+ client->handles = RB_ROOT;
+ idr_init(&client->idr);
+ mutex_init(&client->lock);
+ client->task = task;
+ client->pid = pid;
+ client->name = kstrdup(name, GFP_KERNEL);
+ if (!client->name)
+ goto err_free_client;
+
+ down_write(&dev->lock);
+ client->display_serial = ion_get_client_serial(&dev->clients, name);
+ client->display_name = kasprintf(
+ GFP_KERNEL, "%s-%d", name, client->display_serial);
+ if (!client->display_name) {
+ up_write(&dev->lock);
+ goto err_free_client_name;
+ }
+ p = &dev->clients.rb_node;
+ while (*p) {
+ parent = *p;
+ entry = rb_entry(parent, struct ion_client, node);
+
+ if (client < entry)
+ p = &(*p)->rb_left;
+ else if (client > entry)
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&client->node, parent, p);
+ rb_insert_color(&client->node, &dev->clients);
+
+ client->debug_root = debugfs_create_file(client->display_name, 0664,
+ dev->clients_debug_root,
+ client, &debug_client_fops);
+ if (!client->debug_root) {
+ char buf[256], *path;
+ path = dentry_path(dev->clients_debug_root, buf, 256);
+ pr_err("Failed to create client debugfs at %s/%s\n",
+ path, client->display_name);
+ }
+
+ up_write(&dev->lock);
+
+ return client;
+
+err_free_client_name:
+ kfree(client->name);
+err_free_client:
+ kfree(client);
+err_put_task_struct:
+ if (task)
+ put_task_struct(current->group_leader);
+ return ERR_PTR(-ENOMEM);
+}
+EXPORT_SYMBOL(ion_client_create);
+
+void ion_client_destroy(struct ion_client *client)
+{
+ struct ion_device *dev = client->dev;
+ struct rb_node *n;
+
+ pr_debug("%s: %d\n", __func__, __LINE__);
+ while ((n = rb_first(&client->handles))) {
+ struct ion_handle *handle = rb_entry(n, struct ion_handle,
+ node);
+ ion_handle_destroy(&handle->ref);
+ }
+
+ idr_destroy(&client->idr);
+
+ down_write(&dev->lock);
+ if (client->task)
+ put_task_struct(client->task);
+ rb_erase(&client->node, &dev->clients);
+ debugfs_remove_recursive(client->debug_root);
+ up_write(&dev->lock);
+
+ kfree(client->display_name);
+ kfree(client->name);
+ kfree(client);
+}
+EXPORT_SYMBOL(ion_client_destroy);
+
+struct sg_table *ion_sg_table(struct ion_client *client,
+ struct ion_handle *handle)
+{
+ struct ion_buffer *buffer;
+ struct sg_table *table;
+
+ mutex_lock(&client->lock);
+ if (!ion_handle_validate(client, handle)) {
+ pr_err("%s: invalid handle passed to map_dma.\n",
+ __func__);
+ mutex_unlock(&client->lock);
+ return ERR_PTR(-EINVAL);
+ }
+ buffer = handle->buffer;
+ table = buffer->sg_table;
+ mutex_unlock(&client->lock);
+ return table;
+}
+EXPORT_SYMBOL(ion_sg_table);
+
+static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
+ struct device *dev,
+ enum dma_data_direction direction);
+
+static struct sg_table *ion_map_dma_buf(struct dma_buf_attachment *attachment,
+ enum dma_data_direction direction)
+{
+ struct dma_buf *dmabuf = attachment->dmabuf;
+ struct ion_buffer *buffer = dmabuf->priv;
+
+ ion_buffer_sync_for_device(buffer, attachment->dev, direction);
+ return buffer->sg_table;
+}
+
+static void ion_unmap_dma_buf(struct dma_buf_attachment *attachment,
+ struct sg_table *table,
+ enum dma_data_direction direction)
+{
+}
+
+void ion_pages_sync_for_device(struct device *dev, struct page *page,
+ size_t size, enum dma_data_direction dir)
+{
+ struct scatterlist sg;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, page, size, 0);
+ /*
+ * This is not correct - sg_dma_address needs a dma_addr_t that is valid
+ * for the the targeted device, but this works on the currently targeted
+ * hardware.
+ */
+ sg_dma_address(&sg) = page_to_phys(page);
+ dma_sync_sg_for_device(dev, &sg, 1, dir);
+}
+
+struct ion_vma_list {
+ struct list_head list;
+ struct vm_area_struct *vma;
+};
+
+static void ion_buffer_sync_for_device(struct ion_buffer *buffer,
+ struct device *dev,
+ enum dma_data_direction dir)
+{
+ struct ion_vma_list *vma_list;
+ int pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
+ int i;
+
+ pr_debug("%s: syncing for device %s\n", __func__,
+ dev ? dev_name(dev) : "null");
+
+ if (!ion_buffer_fault_user_mappings(buffer))
+ return;
+
+ mutex_lock(&buffer->lock);
+ for (i = 0; i < pages; i++) {
+ struct page *page = buffer->pages[i];
+
+ if (ion_buffer_page_is_dirty(page))
+ ion_pages_sync_for_device(dev, ion_buffer_page(page),
+ PAGE_SIZE, dir);
+
+ ion_buffer_page_clean(buffer->pages + i);
+ }
+ list_for_each_entry(vma_list, &buffer->vmas, list) {
+ struct vm_area_struct *vma = vma_list->vma;
+
+ zap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start,
+ NULL);
+ }
+ mutex_unlock(&buffer->lock);
+}
+
+static int ion_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct ion_buffer *buffer = vma->vm_private_data;
+ unsigned long pfn;
+ int ret;
+
+ mutex_lock(&buffer->lock);
+ ion_buffer_page_dirty(buffer->pages + vmf->pgoff);
+ BUG_ON(!buffer->pages || !buffer->pages[vmf->pgoff]);
+
+ pfn = page_to_pfn(ion_buffer_page(buffer->pages[vmf->pgoff]));
+ ret = vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn);
+ mutex_unlock(&buffer->lock);
+ if (ret)
+ return VM_FAULT_ERROR;
+
+ return VM_FAULT_NOPAGE;
+}
+
+static void ion_vm_open(struct vm_area_struct *vma)
+{
+ struct ion_buffer *buffer = vma->vm_private_data;
+ struct ion_vma_list *vma_list;
+
+ vma_list = kmalloc(sizeof(struct ion_vma_list), GFP_KERNEL);
+ if (!vma_list)
+ return;
+ vma_list->vma = vma;
+ mutex_lock(&buffer->lock);
+ list_add(&vma_list->list, &buffer->vmas);
+ mutex_unlock(&buffer->lock);
+ pr_debug("%s: adding %p\n", __func__, vma);
+}
+
+static void ion_vm_close(struct vm_area_struct *vma)
+{
+ struct ion_buffer *buffer = vma->vm_private_data;
+ struct ion_vma_list *vma_list, *tmp;
+
+ pr_debug("%s\n", __func__);
+ mutex_lock(&buffer->lock);
+ list_for_each_entry_safe(vma_list, tmp, &buffer->vmas, list) {
+ if (vma_list->vma != vma)
+ continue;
+ list_del(&vma_list->list);
+ kfree(vma_list);
+ pr_debug("%s: deleting %p\n", __func__, vma);
+ break;
+ }
+ mutex_unlock(&buffer->lock);
+}
+
+static struct vm_operations_struct ion_vma_ops = {
+ .open = ion_vm_open,
+ .close = ion_vm_close,
+ .fault = ion_vm_fault,
+};
+
+static int ion_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
+{
+ struct ion_buffer *buffer = dmabuf->priv;
+ int ret = 0;
+
+ if (!buffer->heap->ops->map_user) {
+ pr_err("%s: this heap does not define a method for mapping to userspace\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (ion_buffer_fault_user_mappings(buffer)) {
+ vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND |
+ VM_DONTDUMP;
+ vma->vm_private_data = buffer;
+ vma->vm_ops = &ion_vma_ops;
+ ion_vm_open(vma);
+ return 0;
+ }
+
+ if (!(buffer->flags & ION_FLAG_CACHED))
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ mutex_lock(&buffer->lock);
+ /* now map it to userspace */
+ ret = buffer->heap->ops->map_user(buffer->heap, buffer, vma);
+ mutex_unlock(&buffer->lock);
+
+ if (ret)
+ pr_err("%s: failure mapping buffer to userspace\n",
+ __func__);
+
+ return ret;
+}
+
+static void ion_dma_buf_release(struct dma_buf *dmabuf)
+{
+ struct ion_buffer *buffer = dmabuf->priv;
+
+ ion_buffer_put(buffer);
+}
+
+static void *ion_dma_buf_kmap(struct dma_buf *dmabuf, unsigned long offset)
+{
+ struct ion_buffer *buffer = dmabuf->priv;
+
+ return buffer->vaddr + offset * PAGE_SIZE;
+}
+
+static void ion_dma_buf_kunmap(struct dma_buf *dmabuf, unsigned long offset,
+ void *ptr)
+{
+ return;
+}
+
+static int ion_dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start,
+ size_t len,
+ enum dma_data_direction direction)
+{
+ struct ion_buffer *buffer = dmabuf->priv;
+ void *vaddr;
+
+ if (!buffer->heap->ops->map_kernel) {
+ pr_err("%s: map kernel is not implemented by this heap.\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ mutex_lock(&buffer->lock);
+ vaddr = ion_buffer_kmap_get(buffer);
+ mutex_unlock(&buffer->lock);
+ return PTR_ERR_OR_ZERO(vaddr);
+}
+
+static void ion_dma_buf_end_cpu_access(struct dma_buf *dmabuf, size_t start,
+ size_t len,
+ enum dma_data_direction direction)
+{
+ struct ion_buffer *buffer = dmabuf->priv;
+
+ mutex_lock(&buffer->lock);
+ ion_buffer_kmap_put(buffer);
+ mutex_unlock(&buffer->lock);
+}
+
+static struct dma_buf_ops dma_buf_ops = {
+ .map_dma_buf = ion_map_dma_buf,
+ .unmap_dma_buf = ion_unmap_dma_buf,
+ .mmap = ion_mmap,
+ .release = ion_dma_buf_release,
+ .begin_cpu_access = ion_dma_buf_begin_cpu_access,
+ .end_cpu_access = ion_dma_buf_end_cpu_access,
+ .kmap_atomic = ion_dma_buf_kmap,
+ .kunmap_atomic = ion_dma_buf_kunmap,
+ .kmap = ion_dma_buf_kmap,
+ .kunmap = ion_dma_buf_kunmap,
+};
+
+struct dma_buf *ion_share_dma_buf(struct ion_client *client,
+ struct ion_handle *handle)
+{
+ struct ion_buffer *buffer;
+ struct dma_buf *dmabuf;
+ bool valid_handle;
+
+ mutex_lock(&client->lock);
+ valid_handle = ion_handle_validate(client, handle);
+ if (!valid_handle) {
+ WARN(1, "%s: invalid handle passed to share.\n", __func__);
+ mutex_unlock(&client->lock);
+ return ERR_PTR(-EINVAL);
+ }
+ buffer = handle->buffer;
+ ion_buffer_get(buffer);
+ mutex_unlock(&client->lock);
+
+ dmabuf = dma_buf_export(buffer, &dma_buf_ops, buffer->size, O_RDWR);
+ if (IS_ERR(dmabuf)) {
+ ion_buffer_put(buffer);
+ return dmabuf;
+ }
+
+ return dmabuf;
+}
+EXPORT_SYMBOL(ion_share_dma_buf);
+
+int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle)
+{
+ struct dma_buf *dmabuf;
+ int fd;
+
+ dmabuf = ion_share_dma_buf(client, handle);
+ if (IS_ERR(dmabuf))
+ return PTR_ERR(dmabuf);
+
+ fd = dma_buf_fd(dmabuf, O_CLOEXEC);
+ if (fd < 0)
+ dma_buf_put(dmabuf);
+
+ return fd;
+}
+EXPORT_SYMBOL(ion_share_dma_buf_fd);
+
+struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd)
+{
+ struct dma_buf *dmabuf;
+ struct ion_buffer *buffer;
+ struct ion_handle *handle;
+ int ret;
+
+ dmabuf = dma_buf_get(fd);
+ if (IS_ERR(dmabuf))
+ return ERR_CAST(dmabuf);
+ /* if this memory came from ion */
+
+ if (dmabuf->ops != &dma_buf_ops) {
+ pr_err("%s: can not import dmabuf from another exporter\n",
+ __func__);
+ dma_buf_put(dmabuf);
+ return ERR_PTR(-EINVAL);
+ }
+ buffer = dmabuf->priv;
+
+ mutex_lock(&client->lock);
+ /* if a handle exists for this buffer just take a reference to it */
+ handle = ion_handle_lookup(client, buffer);
+ if (!IS_ERR(handle)) {
+ ion_handle_get(handle);
+ mutex_unlock(&client->lock);
+ goto end;
+ }
+ mutex_unlock(&client->lock);
+
+ handle = ion_handle_create(client, buffer);
+ if (IS_ERR(handle))
+ goto end;
+
+ mutex_lock(&client->lock);
+ ret = ion_handle_add(client, handle);
+ mutex_unlock(&client->lock);
+ if (ret) {
+ ion_handle_put(handle);
+ handle = ERR_PTR(ret);
+ }
+
+end:
+ dma_buf_put(dmabuf);
+ return handle;
+}
+EXPORT_SYMBOL(ion_import_dma_buf);
+
+static int ion_sync_for_device(struct ion_client *client, int fd)
+{
+ struct dma_buf *dmabuf;
+ struct ion_buffer *buffer;
+
+ dmabuf = dma_buf_get(fd);
+ if (IS_ERR(dmabuf))
+ return PTR_ERR(dmabuf);
+
+ /* if this memory came from ion */
+ if (dmabuf->ops != &dma_buf_ops) {
+ pr_err("%s: can not sync dmabuf from another exporter\n",
+ __func__);
+ dma_buf_put(dmabuf);
+ return -EINVAL;
+ }
+ buffer = dmabuf->priv;
+
+ dma_sync_sg_for_device(NULL, buffer->sg_table->sgl,
+ buffer->sg_table->nents, DMA_BIDIRECTIONAL);
+ dma_buf_put(dmabuf);
+ return 0;
+}
+
+/* fix up the cases where the ioctl direction bits are incorrect */
+static unsigned int ion_ioctl_dir(unsigned int cmd)
+{
+ switch (cmd) {
+ case ION_IOC_SYNC:
+ case ION_IOC_FREE:
+ case ION_IOC_CUSTOM:
+ return _IOC_WRITE;
+ default:
+ return _IOC_DIR(cmd);
+ }
+}
+
+static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct ion_client *client = filp->private_data;
+ struct ion_device *dev = client->dev;
+ struct ion_handle *cleanup_handle = NULL;
+ int ret = 0;
+ unsigned int dir;
+
+ union {
+ struct ion_fd_data fd;
+ struct ion_allocation_data allocation;
+ struct ion_handle_data handle;
+ struct ion_custom_data custom;
+ } data;
+
+ dir = ion_ioctl_dir(cmd);
+
+ if (_IOC_SIZE(cmd) > sizeof(data))
+ return -EINVAL;
+
+ if (dir & _IOC_WRITE)
+ if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case ION_IOC_ALLOC:
+ {
+ struct ion_handle *handle;
+
+ handle = ion_alloc(client, data.allocation.len,
+ data.allocation.align,
+ data.allocation.heap_id_mask,
+ data.allocation.flags);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+
+ data.allocation.handle = handle->id;
+
+ cleanup_handle = handle;
+ break;
+ }
+ case ION_IOC_FREE:
+ {
+ struct ion_handle *handle;
+
+ handle = ion_handle_get_by_id(client, data.handle.handle);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+ ion_free(client, handle);
+ ion_handle_put(handle);
+ break;
+ }
+ case ION_IOC_SHARE:
+ case ION_IOC_MAP:
+ {
+ struct ion_handle *handle;
+
+ handle = ion_handle_get_by_id(client, data.handle.handle);
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+ data.fd.fd = ion_share_dma_buf_fd(client, handle);
+ ion_handle_put(handle);
+ if (data.fd.fd < 0)
+ ret = data.fd.fd;
+ break;
+ }
+ case ION_IOC_IMPORT:
+ {
+ struct ion_handle *handle;
+
+ handle = ion_import_dma_buf(client, data.fd.fd);
+ if (IS_ERR(handle))
+ ret = PTR_ERR(handle);
+ else
+ data.handle.handle = handle->id;
+ break;
+ }
+ case ION_IOC_SYNC:
+ {
+ ret = ion_sync_for_device(client, data.fd.fd);
+ break;
+ }
+ case ION_IOC_CUSTOM:
+ {
+ if (!dev->custom_ioctl)
+ return -ENOTTY;
+ ret = dev->custom_ioctl(client, data.custom.cmd,
+ data.custom.arg);
+ break;
+ }
+ default:
+ return -ENOTTY;
+ }
+
+ if (dir & _IOC_READ) {
+ if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd))) {
+ if (cleanup_handle)
+ ion_free(client, cleanup_handle);
+ return -EFAULT;
+ }
+ }
+ return ret;
+}
+
+static int ion_release(struct inode *inode, struct file *file)
+{
+ struct ion_client *client = file->private_data;
+
+ pr_debug("%s: %d\n", __func__, __LINE__);
+ ion_client_destroy(client);
+ return 0;
+}
+
+static int ion_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *miscdev = file->private_data;
+ struct ion_device *dev = container_of(miscdev, struct ion_device, dev);
+ struct ion_client *client;
+ char debug_name[64];
+
+ pr_debug("%s: %d\n", __func__, __LINE__);
+ snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader));
+ client = ion_client_create(dev, debug_name);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+ file->private_data = client;
+
+ return 0;
+}
+
+static const struct file_operations ion_fops = {
+ .owner = THIS_MODULE,
+ .open = ion_open,
+ .release = ion_release,
+ .unlocked_ioctl = ion_ioctl,
+ .compat_ioctl = compat_ion_ioctl,
+};
+
+static size_t ion_debug_heap_total(struct ion_client *client,
+ unsigned int id)
+{
+ size_t size = 0;
+ struct rb_node *n;
+
+ mutex_lock(&client->lock);
+ for (n = rb_first(&client->handles); n; n = rb_next(n)) {
+ struct ion_handle *handle = rb_entry(n,
+ struct ion_handle,
+ node);
+ if (handle->buffer->heap->id == id)
+ size += handle->buffer->size;
+ }
+ mutex_unlock(&client->lock);
+ return size;
+}
+
+static int ion_debug_heap_show(struct seq_file *s, void *unused)
+{
+ struct ion_heap *heap = s->private;
+ struct ion_device *dev = heap->dev;
+ struct rb_node *n;
+ size_t total_size = 0;
+ size_t total_orphaned_size = 0;
+
+ seq_printf(s, "%16.s %16.s %16.s\n", "client", "pid", "size");
+ seq_puts(s, "----------------------------------------------------\n");
+
+ for (n = rb_first(&dev->clients); n; n = rb_next(n)) {
+ struct ion_client *client = rb_entry(n, struct ion_client,
+ node);
+ size_t size = ion_debug_heap_total(client, heap->id);
+
+ if (!size)
+ continue;
+ if (client->task) {
+ char task_comm[TASK_COMM_LEN];
+
+ get_task_comm(task_comm, client->task);
+ seq_printf(s, "%16.s %16u %16zu\n", task_comm,
+ client->pid, size);
+ } else {
+ seq_printf(s, "%16.s %16u %16zu\n", client->name,
+ client->pid, size);
+ }
+ }
+ seq_puts(s, "----------------------------------------------------\n");
+ seq_puts(s, "orphaned allocations (info is from last known client):\n");
+ mutex_lock(&dev->buffer_lock);
+ for (n = rb_first(&dev->buffers); n; n = rb_next(n)) {
+ struct ion_buffer *buffer = rb_entry(n, struct ion_buffer,
+ node);
+ if (buffer->heap->id != heap->id)
+ continue;
+ total_size += buffer->size;
+ if (!buffer->handle_count) {
+ seq_printf(s, "%16.s %16u %16zu %d %d\n",
+ buffer->task_comm, buffer->pid,
+ buffer->size, buffer->kmap_cnt,
+ atomic_read(&buffer->ref.refcount));
+ total_orphaned_size += buffer->size;
+ }
+ }
+ mutex_unlock(&dev->buffer_lock);
+ seq_puts(s, "----------------------------------------------------\n");
+ seq_printf(s, "%16.s %16zu\n", "total orphaned",
+ total_orphaned_size);
+ seq_printf(s, "%16.s %16zu\n", "total ", total_size);
+ if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
+ seq_printf(s, "%16.s %16zu\n", "deferred free",
+ heap->free_list_size);
+ seq_puts(s, "----------------------------------------------------\n");
+
+ if (heap->debug_show)
+ heap->debug_show(heap, s, unused);
+
+ return 0;
+}
+
+static int ion_debug_heap_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ion_debug_heap_show, inode->i_private);
+}
+
+static const struct file_operations debug_heap_fops = {
+ .open = ion_debug_heap_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#ifdef DEBUG_HEAP_SHRINKER
+static int debug_shrink_set(void *data, u64 val)
+{
+ struct ion_heap *heap = data;
+ struct shrink_control sc;
+ int objs;
+
+ sc.gfp_mask = -1;
+ sc.nr_to_scan = 0;
+
+ if (!val)
+ return 0;
+
+ objs = heap->shrinker.shrink(&heap->shrinker, &sc);
+ sc.nr_to_scan = objs;
+
+ heap->shrinker.shrink(&heap->shrinker, &sc);
+ return 0;
+}
+
+static int debug_shrink_get(void *data, u64 *val)
+{
+ struct ion_heap *heap = data;
+ struct shrink_control sc;
+ int objs;
+
+ sc.gfp_mask = -1;
+ sc.nr_to_scan = 0;
+
+ objs = heap->shrinker.shrink(&heap->shrinker, &sc);
+ *val = objs;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(debug_shrink_fops, debug_shrink_get,
+ debug_shrink_set, "%llu\n");
+#endif
+
+void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap)
+{
+ struct dentry *debug_file;
+
+ if (!heap->ops->allocate || !heap->ops->free || !heap->ops->map_dma ||
+ !heap->ops->unmap_dma)
+ pr_err("%s: can not add heap with invalid ops struct.\n",
+ __func__);
+
+ if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
+ ion_heap_init_deferred_free(heap);
+
+ if ((heap->flags & ION_HEAP_FLAG_DEFER_FREE) || heap->ops->shrink)
+ ion_heap_init_shrinker(heap);
+
+ heap->dev = dev;
+ down_write(&dev->lock);
+ /* use negative heap->id to reverse the priority -- when traversing
+ the list later attempt higher id numbers first */
+ plist_node_init(&heap->node, -heap->id);
+ plist_add(&heap->node, &dev->heaps);
+ debug_file = debugfs_create_file(heap->name, 0664,
+ dev->heaps_debug_root, heap,
+ &debug_heap_fops);
+
+ if (!debug_file) {
+ char buf[256], *path;
+
+ path = dentry_path(dev->heaps_debug_root, buf, 256);
+ pr_err("Failed to create heap debugfs at %s/%s\n",
+ path, heap->name);
+ }
+
+#ifdef DEBUG_HEAP_SHRINKER
+ if (heap->shrinker.shrink) {
+ char debug_name[64];
+
+ snprintf(debug_name, 64, "%s_shrink", heap->name);
+ debug_file = debugfs_create_file(
+ debug_name, 0644, dev->heaps_debug_root, heap,
+ &debug_shrink_fops);
+ if (!debug_file) {
+ char buf[256], *path;
+
+ path = dentry_path(dev->heaps_debug_root, buf, 256);
+ pr_err("Failed to create heap shrinker debugfs at %s/%s\n",
+ path, debug_name);
+ }
+ }
+#endif
+ up_write(&dev->lock);
+}
+
+struct ion_device *ion_device_create(long (*custom_ioctl)
+ (struct ion_client *client,
+ unsigned int cmd,
+ unsigned long arg))
+{
+ struct ion_device *idev;
+ int ret;
+
+ idev = kzalloc(sizeof(struct ion_device), GFP_KERNEL);
+ if (!idev)
+ return ERR_PTR(-ENOMEM);
+
+ idev->dev.minor = MISC_DYNAMIC_MINOR;
+ idev->dev.name = "ion";
+ idev->dev.fops = &ion_fops;
+ idev->dev.parent = NULL;
+ ret = misc_register(&idev->dev);
+ if (ret) {
+ pr_err("ion: failed to register misc device.\n");
+ return ERR_PTR(ret);
+ }
+
+ idev->debug_root = debugfs_create_dir("ion", NULL);
+ if (!idev->debug_root) {
+ pr_err("ion: failed to create debugfs root directory.\n");
+ goto debugfs_done;
+ }
+ idev->heaps_debug_root = debugfs_create_dir("heaps", idev->debug_root);
+ if (!idev->heaps_debug_root) {
+ pr_err("ion: failed to create debugfs heaps directory.\n");
+ goto debugfs_done;
+ }
+ idev->clients_debug_root = debugfs_create_dir("clients",
+ idev->debug_root);
+ if (!idev->clients_debug_root)
+ pr_err("ion: failed to create debugfs clients directory.\n");
+
+debugfs_done:
+
+ idev->custom_ioctl = custom_ioctl;
+ idev->buffers = RB_ROOT;
+ mutex_init(&idev->buffer_lock);
+ init_rwsem(&idev->lock);
+ plist_head_init(&idev->heaps);
+ idev->clients = RB_ROOT;
+ return idev;
+}
+
+void ion_device_destroy(struct ion_device *dev)
+{
+ misc_deregister(&dev->dev);
+ debugfs_remove_recursive(dev->debug_root);
+ /* XXX need to free the heaps and clients ? */
+ kfree(dev);
+}
+
+void __init ion_reserve(struct ion_platform_data *data)
+{
+ int i;
+
+ for (i = 0; i < data->nr; i++) {
+ if (data->heaps[i].size == 0)
+ continue;
+
+ if (data->heaps[i].base == 0) {
+ phys_addr_t paddr;
+
+ paddr = memblock_alloc_base(data->heaps[i].size,
+ data->heaps[i].align,
+ MEMBLOCK_ALLOC_ANYWHERE);
+ if (!paddr) {
+ pr_err("%s: error allocating memblock for heap %d\n",
+ __func__, i);
+ continue;
+ }
+ data->heaps[i].base = paddr;
+ } else {
+ int ret = memblock_reserve(data->heaps[i].base,
+ data->heaps[i].size);
+ if (ret)
+ pr_err("memblock reserve of %zx@%lx failed\n",
+ data->heaps[i].size,
+ data->heaps[i].base);
+ }
+ pr_info("%s: %s reserved base %lx size %zu\n", __func__,
+ data->heaps[i].name,
+ data->heaps[i].base,
+ data->heaps[i].size);
+ }
+}
diff --git a/drivers/staging/android/ion/ion.h b/drivers/staging/android/ion/ion.h
new file mode 100644
index 00000000000..dcd2a0cdb19
--- /dev/null
+++ b/drivers/staging/android/ion/ion.h
@@ -0,0 +1,204 @@
+/*
+ * drivers/staging/android/ion/ion.h
+ *
+ * Copyright (C) 2011 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_ION_H
+#define _LINUX_ION_H
+
+#include <linux/types.h>
+
+#include "../uapi/ion.h"
+
+struct ion_handle;
+struct ion_device;
+struct ion_heap;
+struct ion_mapper;
+struct ion_client;
+struct ion_buffer;
+
+/* This should be removed some day when phys_addr_t's are fully
+ plumbed in the kernel, and all instances of ion_phys_addr_t should
+ be converted to phys_addr_t. For the time being many kernel interfaces
+ do not accept phys_addr_t's that would have to */
+#define ion_phys_addr_t unsigned long
+
+/**
+ * struct ion_platform_heap - defines a heap in the given platform
+ * @type: type of the heap from ion_heap_type enum
+ * @id: unique identifier for heap. When allocating higher numbers
+ * will be allocated from first. At allocation these are passed
+ * as a bit mask and therefore can not exceed ION_NUM_HEAP_IDS.
+ * @name: used for debug purposes
+ * @base: base address of heap in physical memory if applicable
+ * @size: size of the heap in bytes if applicable
+ * @align: required alignment in physical memory if applicable
+ * @priv: private info passed from the board file
+ *
+ * Provided by the board file.
+ */
+struct ion_platform_heap {
+ enum ion_heap_type type;
+ unsigned int id;
+ const char *name;
+ ion_phys_addr_t base;
+ size_t size;
+ ion_phys_addr_t align;
+ void *priv;
+};
+
+/**
+ * struct ion_platform_data - array of platform heaps passed from board file
+ * @nr: number of structures in the array
+ * @heaps: array of platform_heap structions
+ *
+ * Provided by the board file in the form of platform data to a platform device.
+ */
+struct ion_platform_data {
+ int nr;
+ struct ion_platform_heap *heaps;
+};
+
+/**
+ * ion_reserve() - reserve memory for ion heaps if applicable
+ * @data: platform data specifying starting physical address and
+ * size
+ *
+ * Calls memblock reserve to set aside memory for heaps that are
+ * located at specific memory addresses or of specfic sizes not
+ * managed by the kernel
+ */
+void ion_reserve(struct ion_platform_data *data);
+
+/**
+ * ion_client_create() - allocate a client and returns it
+ * @dev: the global ion device
+ * @heap_type_mask: mask of heaps this client can allocate from
+ * @name: used for debugging
+ */
+struct ion_client *ion_client_create(struct ion_device *dev,
+ const char *name);
+
+/**
+ * ion_client_destroy() - free's a client and all it's handles
+ * @client: the client
+ *
+ * Free the provided client and all it's resources including
+ * any handles it is holding.
+ */
+void ion_client_destroy(struct ion_client *client);
+
+/**
+ * ion_alloc - allocate ion memory
+ * @client: the client
+ * @len: size of the allocation
+ * @align: requested allocation alignment, lots of hardware blocks
+ * have alignment requirements of some kind
+ * @heap_id_mask: mask of heaps to allocate from, if multiple bits are set
+ * heaps will be tried in order from highest to lowest
+ * id
+ * @flags: heap flags, the low 16 bits are consumed by ion, the
+ * high 16 bits are passed on to the respective heap and
+ * can be heap custom
+ *
+ * Allocate memory in one of the heaps provided in heap mask and return
+ * an opaque handle to it.
+ */
+struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
+ size_t align, unsigned int heap_id_mask,
+ unsigned int flags);
+
+/**
+ * ion_free - free a handle
+ * @client: the client
+ * @handle: the handle to free
+ *
+ * Free the provided handle.
+ */
+void ion_free(struct ion_client *client, struct ion_handle *handle);
+
+/**
+ * ion_phys - returns the physical address and len of a handle
+ * @client: the client
+ * @handle: the handle
+ * @addr: a pointer to put the address in
+ * @len: a pointer to put the length in
+ *
+ * This function queries the heap for a particular handle to get the
+ * handle's physical address. It't output is only correct if
+ * a heap returns physically contiguous memory -- in other cases
+ * this api should not be implemented -- ion_sg_table should be used
+ * instead. Returns -EINVAL if the handle is invalid. This has
+ * no implications on the reference counting of the handle --
+ * the returned value may not be valid if the caller is not
+ * holding a reference.
+ */
+int ion_phys(struct ion_client *client, struct ion_handle *handle,
+ ion_phys_addr_t *addr, size_t *len);
+
+/**
+ * ion_map_dma - return an sg_table describing a handle
+ * @client: the client
+ * @handle: the handle
+ *
+ * This function returns the sg_table describing
+ * a particular ion handle.
+ */
+struct sg_table *ion_sg_table(struct ion_client *client,
+ struct ion_handle *handle);
+
+/**
+ * ion_map_kernel - create mapping for the given handle
+ * @client: the client
+ * @handle: handle to map
+ *
+ * Map the given handle into the kernel and return a kernel address that
+ * can be used to access this address.
+ */
+void *ion_map_kernel(struct ion_client *client, struct ion_handle *handle);
+
+/**
+ * ion_unmap_kernel() - destroy a kernel mapping for a handle
+ * @client: the client
+ * @handle: handle to unmap
+ */
+void ion_unmap_kernel(struct ion_client *client, struct ion_handle *handle);
+
+/**
+ * ion_share_dma_buf() - share buffer as dma-buf
+ * @client: the client
+ * @handle: the handle
+ */
+struct dma_buf *ion_share_dma_buf(struct ion_client *client,
+ struct ion_handle *handle);
+
+/**
+ * ion_share_dma_buf_fd() - given an ion client, create a dma-buf fd
+ * @client: the client
+ * @handle: the handle
+ */
+int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle);
+
+/**
+ * ion_import_dma_buf() - given an dma-buf fd from the ion exporter get handle
+ * @client: the client
+ * @fd: the dma-buf fd
+ *
+ * Given an dma-buf fd that was allocated through ion via ion_share_dma_buf,
+ * import that fd and return a handle representing it. If a dma-buf from
+ * another exporter is passed in this function will return ERR_PTR(-EINVAL)
+ */
+struct ion_handle *ion_import_dma_buf(struct ion_client *client, int fd);
+
+#endif /* _LINUX_ION_H */
diff --git a/drivers/staging/android/ion/ion_carveout_heap.c b/drivers/staging/android/ion/ion_carveout_heap.c
new file mode 100644
index 00000000000..dcb6f2196c8
--- /dev/null
+++ b/drivers/staging/android/ion/ion_carveout_heap.c
@@ -0,0 +1,194 @@
+/*
+ * drivers/staging/android/ion/ion_carveout_heap.c
+ *
+ * Copyright (C) 2011 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 <linux/spinlock.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/genalloc.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include "ion.h"
+#include "ion_priv.h"
+
+struct ion_carveout_heap {
+ struct ion_heap heap;
+ struct gen_pool *pool;
+ ion_phys_addr_t base;
+};
+
+ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap,
+ unsigned long size,
+ unsigned long align)
+{
+ struct ion_carveout_heap *carveout_heap =
+ container_of(heap, struct ion_carveout_heap, heap);
+ unsigned long offset = gen_pool_alloc(carveout_heap->pool, size);
+
+ if (!offset)
+ return ION_CARVEOUT_ALLOCATE_FAIL;
+
+ return offset;
+}
+
+void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr,
+ unsigned long size)
+{
+ struct ion_carveout_heap *carveout_heap =
+ container_of(heap, struct ion_carveout_heap, heap);
+
+ if (addr == ION_CARVEOUT_ALLOCATE_FAIL)
+ return;
+ gen_pool_free(carveout_heap->pool, addr, size);
+}
+
+static int ion_carveout_heap_phys(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ ion_phys_addr_t *addr, size_t *len)
+{
+ struct sg_table *table = buffer->priv_virt;
+ struct page *page = sg_page(table->sgl);
+ ion_phys_addr_t paddr = PFN_PHYS(page_to_pfn(page));
+
+ *addr = paddr;
+ *len = buffer->size;
+ return 0;
+}
+
+static int ion_carveout_heap_allocate(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long size, unsigned long align,
+ unsigned long flags)
+{
+ struct sg_table *table;
+ ion_phys_addr_t paddr;
+ int ret;
+
+ if (align > PAGE_SIZE)
+ return -EINVAL;
+
+ table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+ ret = sg_alloc_table(table, 1, GFP_KERNEL);
+ if (ret)
+ goto err_free;
+
+ paddr = ion_carveout_allocate(heap, size, align);
+ if (paddr == ION_CARVEOUT_ALLOCATE_FAIL) {
+ ret = -ENOMEM;
+ goto err_free_table;
+ }
+
+ sg_set_page(table->sgl, pfn_to_page(PFN_DOWN(paddr)), size, 0);
+ buffer->priv_virt = table;
+
+ return 0;
+
+err_free_table:
+ sg_free_table(table);
+err_free:
+ kfree(table);
+ return ret;
+}
+
+static void ion_carveout_heap_free(struct ion_buffer *buffer)
+{
+ struct ion_heap *heap = buffer->heap;
+ struct sg_table *table = buffer->priv_virt;
+ struct page *page = sg_page(table->sgl);
+ ion_phys_addr_t paddr = PFN_PHYS(page_to_pfn(page));
+
+ ion_heap_buffer_zero(buffer);
+
+ if (ion_buffer_cached(buffer))
+ dma_sync_sg_for_device(NULL, table->sgl, table->nents,
+ DMA_BIDIRECTIONAL);
+
+ ion_carveout_free(heap, paddr, buffer->size);
+ sg_free_table(table);
+ kfree(table);
+}
+
+static struct sg_table *ion_carveout_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return buffer->priv_virt;
+}
+
+static void ion_carveout_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return;
+}
+
+static struct ion_heap_ops carveout_heap_ops = {
+ .allocate = ion_carveout_heap_allocate,
+ .free = ion_carveout_heap_free,
+ .phys = ion_carveout_heap_phys,
+ .map_dma = ion_carveout_heap_map_dma,
+ .unmap_dma = ion_carveout_heap_unmap_dma,
+ .map_user = ion_heap_map_user,
+ .map_kernel = ion_heap_map_kernel,
+ .unmap_kernel = ion_heap_unmap_kernel,
+};
+
+struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data)
+{
+ struct ion_carveout_heap *carveout_heap;
+ int ret;
+
+ struct page *page;
+ size_t size;
+
+ page = pfn_to_page(PFN_DOWN(heap_data->base));
+ size = heap_data->size;
+
+ ion_pages_sync_for_device(NULL, page, size, DMA_BIDIRECTIONAL);
+
+ ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL));
+ if (ret)
+ return ERR_PTR(ret);
+
+ carveout_heap = kzalloc(sizeof(struct ion_carveout_heap), GFP_KERNEL);
+ if (!carveout_heap)
+ return ERR_PTR(-ENOMEM);
+
+ carveout_heap->pool = gen_pool_create(12, -1);
+ if (!carveout_heap->pool) {
+ kfree(carveout_heap);
+ return ERR_PTR(-ENOMEM);
+ }
+ carveout_heap->base = heap_data->base;
+ gen_pool_add(carveout_heap->pool, carveout_heap->base, heap_data->size,
+ -1);
+ carveout_heap->heap.ops = &carveout_heap_ops;
+ carveout_heap->heap.type = ION_HEAP_TYPE_CARVEOUT;
+ carveout_heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
+
+ return &carveout_heap->heap;
+}
+
+void ion_carveout_heap_destroy(struct ion_heap *heap)
+{
+ struct ion_carveout_heap *carveout_heap =
+ container_of(heap, struct ion_carveout_heap, heap);
+
+ gen_pool_destroy(carveout_heap->pool);
+ kfree(carveout_heap);
+ carveout_heap = NULL;
+}
diff --git a/drivers/staging/android/ion/ion_chunk_heap.c b/drivers/staging/android/ion/ion_chunk_heap.c
new file mode 100644
index 00000000000..3f2c12ba4d1
--- /dev/null
+++ b/drivers/staging/android/ion/ion_chunk_heap.c
@@ -0,0 +1,195 @@
+/*
+ * drivers/staging/android/ion/ion_chunk_heap.c
+ *
+ * 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 <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/genalloc.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include "ion.h"
+#include "ion_priv.h"
+
+struct ion_chunk_heap {
+ struct ion_heap heap;
+ struct gen_pool *pool;
+ ion_phys_addr_t base;
+ unsigned long chunk_size;
+ unsigned long size;
+ unsigned long allocated;
+};
+
+static int ion_chunk_heap_allocate(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long size, unsigned long align,
+ unsigned long flags)
+{
+ struct ion_chunk_heap *chunk_heap =
+ container_of(heap, struct ion_chunk_heap, heap);
+ struct sg_table *table;
+ struct scatterlist *sg;
+ int ret, i;
+ unsigned long num_chunks;
+ unsigned long allocated_size;
+
+ if (align > chunk_heap->chunk_size)
+ return -EINVAL;
+
+ allocated_size = ALIGN(size, chunk_heap->chunk_size);
+ num_chunks = allocated_size / chunk_heap->chunk_size;
+
+ if (allocated_size > chunk_heap->size - chunk_heap->allocated)
+ return -ENOMEM;
+
+ table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!table)
+ return -ENOMEM;
+ ret = sg_alloc_table(table, num_chunks, GFP_KERNEL);
+ if (ret) {
+ kfree(table);
+ return ret;
+ }
+
+ sg = table->sgl;
+ for (i = 0; i < num_chunks; i++) {
+ unsigned long paddr = gen_pool_alloc(chunk_heap->pool,
+ chunk_heap->chunk_size);
+ if (!paddr)
+ goto err;
+ sg_set_page(sg, pfn_to_page(PFN_DOWN(paddr)),
+ chunk_heap->chunk_size, 0);
+ sg = sg_next(sg);
+ }
+
+ buffer->priv_virt = table;
+ chunk_heap->allocated += allocated_size;
+ return 0;
+err:
+ sg = table->sgl;
+ for (i -= 1; i >= 0; i--) {
+ gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)),
+ sg->length);
+ sg = sg_next(sg);
+ }
+ sg_free_table(table);
+ kfree(table);
+ return -ENOMEM;
+}
+
+static void ion_chunk_heap_free(struct ion_buffer *buffer)
+{
+ struct ion_heap *heap = buffer->heap;
+ struct ion_chunk_heap *chunk_heap =
+ container_of(heap, struct ion_chunk_heap, heap);
+ struct sg_table *table = buffer->priv_virt;
+ struct scatterlist *sg;
+ int i;
+ unsigned long allocated_size;
+
+ allocated_size = ALIGN(buffer->size, chunk_heap->chunk_size);
+
+ ion_heap_buffer_zero(buffer);
+
+ if (ion_buffer_cached(buffer))
+ dma_sync_sg_for_device(NULL, table->sgl, table->nents,
+ DMA_BIDIRECTIONAL);
+
+ for_each_sg(table->sgl, sg, table->nents, i) {
+ gen_pool_free(chunk_heap->pool, page_to_phys(sg_page(sg)),
+ sg->length);
+ }
+ chunk_heap->allocated -= allocated_size;
+ sg_free_table(table);
+ kfree(table);
+}
+
+static struct sg_table *ion_chunk_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return buffer->priv_virt;
+}
+
+static void ion_chunk_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return;
+}
+
+static struct ion_heap_ops chunk_heap_ops = {
+ .allocate = ion_chunk_heap_allocate,
+ .free = ion_chunk_heap_free,
+ .map_dma = ion_chunk_heap_map_dma,
+ .unmap_dma = ion_chunk_heap_unmap_dma,
+ .map_user = ion_heap_map_user,
+ .map_kernel = ion_heap_map_kernel,
+ .unmap_kernel = ion_heap_unmap_kernel,
+};
+
+struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *heap_data)
+{
+ struct ion_chunk_heap *chunk_heap;
+ int ret;
+ struct page *page;
+ size_t size;
+
+ page = pfn_to_page(PFN_DOWN(heap_data->base));
+ size = heap_data->size;
+
+ ion_pages_sync_for_device(NULL, page, size, DMA_BIDIRECTIONAL);
+
+ ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL));
+ if (ret)
+ return ERR_PTR(ret);
+
+ chunk_heap = kzalloc(sizeof(struct ion_chunk_heap), GFP_KERNEL);
+ if (!chunk_heap)
+ return ERR_PTR(-ENOMEM);
+
+ chunk_heap->chunk_size = (unsigned long)heap_data->priv;
+ chunk_heap->pool = gen_pool_create(get_order(chunk_heap->chunk_size) +
+ PAGE_SHIFT, -1);
+ if (!chunk_heap->pool) {
+ ret = -ENOMEM;
+ goto error_gen_pool_create;
+ }
+ chunk_heap->base = heap_data->base;
+ chunk_heap->size = heap_data->size;
+ chunk_heap->allocated = 0;
+
+ gen_pool_add(chunk_heap->pool, chunk_heap->base, heap_data->size, -1);
+ chunk_heap->heap.ops = &chunk_heap_ops;
+ chunk_heap->heap.type = ION_HEAP_TYPE_CHUNK;
+ chunk_heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
+ pr_info("%s: base %lu size %zu align %ld\n", __func__, chunk_heap->base,
+ heap_data->size, heap_data->align);
+
+ return &chunk_heap->heap;
+
+error_gen_pool_create:
+ kfree(chunk_heap);
+ return ERR_PTR(ret);
+}
+
+void ion_chunk_heap_destroy(struct ion_heap *heap)
+{
+ struct ion_chunk_heap *chunk_heap =
+ container_of(heap, struct ion_chunk_heap, heap);
+
+ gen_pool_destroy(chunk_heap->pool);
+ kfree(chunk_heap);
+ chunk_heap = NULL;
+}
diff --git a/drivers/staging/android/ion/ion_cma_heap.c b/drivers/staging/android/ion/ion_cma_heap.c
new file mode 100644
index 00000000000..ce68ecfed31
--- /dev/null
+++ b/drivers/staging/android/ion/ion_cma_heap.c
@@ -0,0 +1,218 @@
+/*
+ * drivers/staging/android/ion/ion_cma_heap.c
+ *
+ * Copyright (C) Linaro 2012
+ * Author: <benjamin.gaignard@linaro.org> for ST-Ericsson.
+ *
+ * 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 <linux/device.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/dma-mapping.h>
+
+#include "ion.h"
+#include "ion_priv.h"
+
+#define ION_CMA_ALLOCATE_FAILED -1
+
+struct ion_cma_heap {
+ struct ion_heap heap;
+ struct device *dev;
+};
+
+#define to_cma_heap(x) container_of(x, struct ion_cma_heap, heap)
+
+struct ion_cma_buffer_info {
+ void *cpu_addr;
+ dma_addr_t handle;
+ struct sg_table *table;
+};
+
+/*
+ * Create scatter-list for the already allocated DMA buffer.
+ * This function could be replaced by dma_common_get_sgtable
+ * as soon as it will avalaible.
+ */
+static int ion_cma_get_sgtable(struct device *dev, struct sg_table *sgt,
+ void *cpu_addr, dma_addr_t handle, size_t size)
+{
+ struct page *page = virt_to_page(cpu_addr);
+ int ret;
+
+ ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+ if (unlikely(ret))
+ return ret;
+
+ sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
+ return 0;
+}
+
+/* ION CMA heap operations functions */
+static int ion_cma_allocate(struct ion_heap *heap, struct ion_buffer *buffer,
+ unsigned long len, unsigned long align,
+ unsigned long flags)
+{
+ struct ion_cma_heap *cma_heap = to_cma_heap(heap);
+ struct device *dev = cma_heap->dev;
+ struct ion_cma_buffer_info *info;
+
+ dev_dbg(dev, "Request buffer allocation len %ld\n", len);
+
+ if (buffer->flags & ION_FLAG_CACHED)
+ return -EINVAL;
+
+ if (align > PAGE_SIZE)
+ return -EINVAL;
+
+ info = kzalloc(sizeof(struct ion_cma_buffer_info), GFP_KERNEL);
+ if (!info) {
+ dev_err(dev, "Can't allocate buffer info\n");
+ return ION_CMA_ALLOCATE_FAILED;
+ }
+
+ info->cpu_addr = dma_alloc_coherent(dev, len, &(info->handle),
+ GFP_HIGHUSER | __GFP_ZERO);
+
+ if (!info->cpu_addr) {
+ dev_err(dev, "Fail to allocate buffer\n");
+ goto err;
+ }
+
+ info->table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!info->table) {
+ dev_err(dev, "Fail to allocate sg table\n");
+ goto free_mem;
+ }
+
+ if (ion_cma_get_sgtable
+ (dev, info->table, info->cpu_addr, info->handle, len))
+ goto free_table;
+ /* keep this for memory release */
+ buffer->priv_virt = info;
+ dev_dbg(dev, "Allocate buffer %p\n", buffer);
+ return 0;
+
+free_table:
+ kfree(info->table);
+free_mem:
+ dma_free_coherent(dev, len, info->cpu_addr, info->handle);
+err:
+ kfree(info);
+ return ION_CMA_ALLOCATE_FAILED;
+}
+
+static void ion_cma_free(struct ion_buffer *buffer)
+{
+ struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
+ struct device *dev = cma_heap->dev;
+ struct ion_cma_buffer_info *info = buffer->priv_virt;
+
+ dev_dbg(dev, "Release buffer %p\n", buffer);
+ /* release memory */
+ dma_free_coherent(dev, buffer->size, info->cpu_addr, info->handle);
+ /* release sg table */
+ sg_free_table(info->table);
+ kfree(info->table);
+ kfree(info);
+}
+
+/* return physical address in addr */
+static int ion_cma_phys(struct ion_heap *heap, struct ion_buffer *buffer,
+ ion_phys_addr_t *addr, size_t *len)
+{
+ struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
+ struct device *dev = cma_heap->dev;
+ struct ion_cma_buffer_info *info = buffer->priv_virt;
+
+ dev_dbg(dev, "Return buffer %p physical address %pa\n", buffer,
+ &info->handle);
+
+ *addr = info->handle;
+ *len = buffer->size;
+
+ return 0;
+}
+
+static struct sg_table *ion_cma_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ struct ion_cma_buffer_info *info = buffer->priv_virt;
+
+ return info->table;
+}
+
+static void ion_cma_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return;
+}
+
+static int ion_cma_mmap(struct ion_heap *mapper, struct ion_buffer *buffer,
+ struct vm_area_struct *vma)
+{
+ struct ion_cma_heap *cma_heap = to_cma_heap(buffer->heap);
+ struct device *dev = cma_heap->dev;
+ struct ion_cma_buffer_info *info = buffer->priv_virt;
+
+ return dma_mmap_coherent(dev, vma, info->cpu_addr, info->handle,
+ buffer->size);
+}
+
+static void *ion_cma_map_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ struct ion_cma_buffer_info *info = buffer->priv_virt;
+ /* kernel memory mapping has been done at allocation time */
+ return info->cpu_addr;
+}
+
+static void ion_cma_unmap_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+}
+
+static struct ion_heap_ops ion_cma_ops = {
+ .allocate = ion_cma_allocate,
+ .free = ion_cma_free,
+ .map_dma = ion_cma_heap_map_dma,
+ .unmap_dma = ion_cma_heap_unmap_dma,
+ .phys = ion_cma_phys,
+ .map_user = ion_cma_mmap,
+ .map_kernel = ion_cma_map_kernel,
+ .unmap_kernel = ion_cma_unmap_kernel,
+};
+
+struct ion_heap *ion_cma_heap_create(struct ion_platform_heap *data)
+{
+ struct ion_cma_heap *cma_heap;
+
+ cma_heap = kzalloc(sizeof(struct ion_cma_heap), GFP_KERNEL);
+
+ if (!cma_heap)
+ return ERR_PTR(-ENOMEM);
+
+ cma_heap->heap.ops = &ion_cma_ops;
+ /* get device from private heaps data, later it will be
+ * used to make the link with reserved CMA memory */
+ cma_heap->dev = data->priv;
+ cma_heap->heap.type = ION_HEAP_TYPE_DMA;
+ return &cma_heap->heap;
+}
+
+void ion_cma_heap_destroy(struct ion_heap *heap)
+{
+ struct ion_cma_heap *cma_heap = to_cma_heap(heap);
+
+ kfree(cma_heap);
+}
diff --git a/drivers/staging/android/ion/ion_dummy_driver.c b/drivers/staging/android/ion/ion_dummy_driver.c
new file mode 100644
index 00000000000..3a45e79fe44
--- /dev/null
+++ b/drivers/staging/android/ion/ion_dummy_driver.c
@@ -0,0 +1,158 @@
+/*
+ * drivers/gpu/ion/ion_dummy_driver.c
+ *
+ * Copyright (C) 2013 Linaro, 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 <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/bootmem.h>
+#include <linux/memblock.h>
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include "ion.h"
+#include "ion_priv.h"
+
+static struct ion_device *idev;
+static struct ion_heap **heaps;
+
+static void *carveout_ptr;
+static void *chunk_ptr;
+
+static struct ion_platform_heap dummy_heaps[] = {
+ {
+ .id = ION_HEAP_TYPE_SYSTEM,
+ .type = ION_HEAP_TYPE_SYSTEM,
+ .name = "system",
+ },
+ {
+ .id = ION_HEAP_TYPE_SYSTEM_CONTIG,
+ .type = ION_HEAP_TYPE_SYSTEM_CONTIG,
+ .name = "system contig",
+ },
+ {
+ .id = ION_HEAP_TYPE_CARVEOUT,
+ .type = ION_HEAP_TYPE_CARVEOUT,
+ .name = "carveout",
+ .size = SZ_4M,
+ },
+ {
+ .id = ION_HEAP_TYPE_CHUNK,
+ .type = ION_HEAP_TYPE_CHUNK,
+ .name = "chunk",
+ .size = SZ_4M,
+ .align = SZ_16K,
+ .priv = (void *)(SZ_16K),
+ },
+};
+
+static struct ion_platform_data dummy_ion_pdata = {
+ .nr = ARRAY_SIZE(dummy_heaps),
+ .heaps = dummy_heaps,
+};
+
+static int __init ion_dummy_init(void)
+{
+ int i, err;
+
+ idev = ion_device_create(NULL);
+ heaps = kzalloc(sizeof(struct ion_heap *) * dummy_ion_pdata.nr,
+ GFP_KERNEL);
+ if (!heaps)
+ return -ENOMEM;
+
+
+ /* Allocate a dummy carveout heap */
+ carveout_ptr = alloc_pages_exact(
+ dummy_heaps[ION_HEAP_TYPE_CARVEOUT].size,
+ GFP_KERNEL);
+ if (carveout_ptr)
+ dummy_heaps[ION_HEAP_TYPE_CARVEOUT].base =
+ virt_to_phys(carveout_ptr);
+ else
+ pr_err("ion_dummy: Could not allocate carveout\n");
+
+ /* Allocate a dummy chunk heap */
+ chunk_ptr = alloc_pages_exact(
+ dummy_heaps[ION_HEAP_TYPE_CHUNK].size,
+ GFP_KERNEL);
+ if (chunk_ptr)
+ dummy_heaps[ION_HEAP_TYPE_CHUNK].base = virt_to_phys(chunk_ptr);
+ else
+ pr_err("ion_dummy: Could not allocate chunk\n");
+
+ for (i = 0; i < dummy_ion_pdata.nr; i++) {
+ struct ion_platform_heap *heap_data = &dummy_ion_pdata.heaps[i];
+
+ if (heap_data->type == ION_HEAP_TYPE_CARVEOUT &&
+ !heap_data->base)
+ continue;
+
+ if (heap_data->type == ION_HEAP_TYPE_CHUNK && !heap_data->base)
+ continue;
+
+ heaps[i] = ion_heap_create(heap_data);
+ if (IS_ERR_OR_NULL(heaps[i])) {
+ err = PTR_ERR(heaps[i]);
+ goto err;
+ }
+ ion_device_add_heap(idev, heaps[i]);
+ }
+ return 0;
+err:
+ for (i = 0; i < dummy_ion_pdata.nr; i++) {
+ if (heaps[i])
+ ion_heap_destroy(heaps[i]);
+ }
+ kfree(heaps);
+
+ if (carveout_ptr) {
+ free_pages_exact(carveout_ptr,
+ dummy_heaps[ION_HEAP_TYPE_CARVEOUT].size);
+ carveout_ptr = NULL;
+ }
+ if (chunk_ptr) {
+ free_pages_exact(chunk_ptr,
+ dummy_heaps[ION_HEAP_TYPE_CHUNK].size);
+ chunk_ptr = NULL;
+ }
+ return err;
+}
+device_initcall(ion_dummy_init);
+
+static void __exit ion_dummy_exit(void)
+{
+ int i;
+
+ ion_device_destroy(idev);
+
+ for (i = 0; i < dummy_ion_pdata.nr; i++)
+ ion_heap_destroy(heaps[i]);
+ kfree(heaps);
+
+ if (carveout_ptr) {
+ free_pages_exact(carveout_ptr,
+ dummy_heaps[ION_HEAP_TYPE_CARVEOUT].size);
+ carveout_ptr = NULL;
+ }
+ if (chunk_ptr) {
+ free_pages_exact(chunk_ptr,
+ dummy_heaps[ION_HEAP_TYPE_CHUNK].size);
+ chunk_ptr = NULL;
+ }
+
+ return;
+}
+__exitcall(ion_dummy_exit);
diff --git a/drivers/staging/android/ion/ion_heap.c b/drivers/staging/android/ion/ion_heap.c
new file mode 100644
index 00000000000..4605e04712a
--- /dev/null
+++ b/drivers/staging/android/ion/ion_heap.c
@@ -0,0 +1,383 @@
+/*
+ * drivers/staging/android/ion/ion_heap.c
+ *
+ * Copyright (C) 2011 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 <linux/err.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/mm.h>
+#include <linux/rtmutex.h>
+#include <linux/sched.h>
+#include <linux/scatterlist.h>
+#include <linux/vmalloc.h>
+#include "ion.h"
+#include "ion_priv.h"
+
+void *ion_heap_map_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ struct scatterlist *sg;
+ int i, j;
+ void *vaddr;
+ pgprot_t pgprot;
+ struct sg_table *table = buffer->sg_table;
+ int npages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
+ struct page **pages = vmalloc(sizeof(struct page *) * npages);
+ struct page **tmp = pages;
+
+ if (!pages)
+ return NULL;
+
+ if (buffer->flags & ION_FLAG_CACHED)
+ pgprot = PAGE_KERNEL;
+ else
+ pgprot = pgprot_writecombine(PAGE_KERNEL);
+
+ for_each_sg(table->sgl, sg, table->nents, i) {
+ int npages_this_entry = PAGE_ALIGN(sg->length) / PAGE_SIZE;
+ struct page *page = sg_page(sg);
+
+ BUG_ON(i >= npages);
+ for (j = 0; j < npages_this_entry; j++)
+ *(tmp++) = page++;
+ }
+ vaddr = vmap(pages, npages, VM_MAP, pgprot);
+ vfree(pages);
+
+ if (vaddr == NULL)
+ return ERR_PTR(-ENOMEM);
+
+ return vaddr;
+}
+
+void ion_heap_unmap_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ vunmap(buffer->vaddr);
+}
+
+int ion_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer,
+ struct vm_area_struct *vma)
+{
+ struct sg_table *table = buffer->sg_table;
+ unsigned long addr = vma->vm_start;
+ unsigned long offset = vma->vm_pgoff * PAGE_SIZE;
+ struct scatterlist *sg;
+ int i;
+ int ret;
+
+ for_each_sg(table->sgl, sg, table->nents, i) {
+ struct page *page = sg_page(sg);
+ unsigned long remainder = vma->vm_end - addr;
+ unsigned long len = sg->length;
+
+ if (offset >= sg->length) {
+ offset -= sg->length;
+ continue;
+ } else if (offset) {
+ page += offset / PAGE_SIZE;
+ len = sg->length - offset;
+ offset = 0;
+ }
+ len = min(len, remainder);
+ ret = remap_pfn_range(vma, addr, page_to_pfn(page), len,
+ vma->vm_page_prot);
+ if (ret)
+ return ret;
+ addr += len;
+ if (addr >= vma->vm_end)
+ return 0;
+ }
+ return 0;
+}
+
+static int ion_heap_clear_pages(struct page **pages, int num, pgprot_t pgprot)
+{
+ void *addr = vm_map_ram(pages, num, -1, pgprot);
+
+ if (!addr)
+ return -ENOMEM;
+ memset(addr, 0, PAGE_SIZE * num);
+ vm_unmap_ram(addr, num);
+
+ return 0;
+}
+
+static int ion_heap_sglist_zero(struct scatterlist *sgl, unsigned int nents,
+ pgprot_t pgprot)
+{
+ int p = 0;
+ int ret = 0;
+ struct sg_page_iter piter;
+ struct page *pages[32];
+
+ for_each_sg_page(sgl, &piter, nents, 0) {
+ pages[p++] = sg_page_iter_page(&piter);
+ if (p == ARRAY_SIZE(pages)) {
+ ret = ion_heap_clear_pages(pages, p, pgprot);
+ if (ret)
+ return ret;
+ p = 0;
+ }
+ }
+ if (p)
+ ret = ion_heap_clear_pages(pages, p, pgprot);
+
+ return ret;
+}
+
+int ion_heap_buffer_zero(struct ion_buffer *buffer)
+{
+ struct sg_table *table = buffer->sg_table;
+ pgprot_t pgprot;
+
+ if (buffer->flags & ION_FLAG_CACHED)
+ pgprot = PAGE_KERNEL;
+ else
+ pgprot = pgprot_writecombine(PAGE_KERNEL);
+
+ return ion_heap_sglist_zero(table->sgl, table->nents, pgprot);
+}
+
+int ion_heap_pages_zero(struct page *page, size_t size, pgprot_t pgprot)
+{
+ struct scatterlist sg;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, page, size, 0);
+ return ion_heap_sglist_zero(&sg, 1, pgprot);
+}
+
+void ion_heap_freelist_add(struct ion_heap *heap, struct ion_buffer *buffer)
+{
+ spin_lock(&heap->free_lock);
+ list_add(&buffer->list, &heap->free_list);
+ heap->free_list_size += buffer->size;
+ spin_unlock(&heap->free_lock);
+ wake_up(&heap->waitqueue);
+}
+
+size_t ion_heap_freelist_size(struct ion_heap *heap)
+{
+ size_t size;
+
+ spin_lock(&heap->free_lock);
+ size = heap->free_list_size;
+ spin_unlock(&heap->free_lock);
+
+ return size;
+}
+
+static size_t _ion_heap_freelist_drain(struct ion_heap *heap, size_t size,
+ bool skip_pools)
+{
+ struct ion_buffer *buffer;
+ size_t total_drained = 0;
+
+ if (ion_heap_freelist_size(heap) == 0)
+ return 0;
+
+ spin_lock(&heap->free_lock);
+ if (size == 0)
+ size = heap->free_list_size;
+
+ while (!list_empty(&heap->free_list)) {
+ if (total_drained >= size)
+ break;
+ buffer = list_first_entry(&heap->free_list, struct ion_buffer,
+ list);
+ list_del(&buffer->list);
+ heap->free_list_size -= buffer->size;
+ if (skip_pools)
+ buffer->private_flags |= ION_PRIV_FLAG_SHRINKER_FREE;
+ total_drained += buffer->size;
+ spin_unlock(&heap->free_lock);
+ ion_buffer_destroy(buffer);
+ spin_lock(&heap->free_lock);
+ }
+ spin_unlock(&heap->free_lock);
+
+ return total_drained;
+}
+
+size_t ion_heap_freelist_drain(struct ion_heap *heap, size_t size)
+{
+ return _ion_heap_freelist_drain(heap, size, false);
+}
+
+size_t ion_heap_freelist_shrink(struct ion_heap *heap, size_t size)
+{
+ return _ion_heap_freelist_drain(heap, size, true);
+}
+
+static int ion_heap_deferred_free(void *data)
+{
+ struct ion_heap *heap = data;
+
+ while (true) {
+ struct ion_buffer *buffer;
+
+ wait_event_freezable(heap->waitqueue,
+ ion_heap_freelist_size(heap) > 0);
+
+ spin_lock(&heap->free_lock);
+ if (list_empty(&heap->free_list)) {
+ spin_unlock(&heap->free_lock);
+ continue;
+ }
+ buffer = list_first_entry(&heap->free_list, struct ion_buffer,
+ list);
+ list_del(&buffer->list);
+ heap->free_list_size -= buffer->size;
+ spin_unlock(&heap->free_lock);
+ ion_buffer_destroy(buffer);
+ }
+
+ return 0;
+}
+
+int ion_heap_init_deferred_free(struct ion_heap *heap)
+{
+ struct sched_param param = { .sched_priority = 0 };
+
+ INIT_LIST_HEAD(&heap->free_list);
+ heap->free_list_size = 0;
+ spin_lock_init(&heap->free_lock);
+ init_waitqueue_head(&heap->waitqueue);
+ heap->task = kthread_run(ion_heap_deferred_free, heap,
+ "%s", heap->name);
+ if (IS_ERR(heap->task)) {
+ pr_err("%s: creating thread for deferred free failed\n",
+ __func__);
+ return PTR_ERR_OR_ZERO(heap->task);
+ }
+ sched_setscheduler(heap->task, SCHED_IDLE, &param);
+ return 0;
+}
+
+static unsigned long ion_heap_shrink_count(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct ion_heap *heap = container_of(shrinker, struct ion_heap,
+ shrinker);
+ int total = 0;
+
+ total = ion_heap_freelist_size(heap) / PAGE_SIZE;
+ if (heap->ops->shrink)
+ total += heap->ops->shrink(heap, sc->gfp_mask, 0);
+ return total;
+}
+
+static unsigned long ion_heap_shrink_scan(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct ion_heap *heap = container_of(shrinker, struct ion_heap,
+ shrinker);
+ int freed = 0;
+ int to_scan = sc->nr_to_scan;
+
+ if (to_scan == 0)
+ return 0;
+
+ /*
+ * shrink the free list first, no point in zeroing the memory if we're
+ * just going to reclaim it. Also, skip any possible page pooling.
+ */
+ if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
+ freed = ion_heap_freelist_shrink(heap, to_scan * PAGE_SIZE) /
+ PAGE_SIZE;
+
+ to_scan -= freed;
+ if (to_scan <= 0)
+ return freed;
+
+ if (heap->ops->shrink)
+ freed += heap->ops->shrink(heap, sc->gfp_mask, to_scan);
+ return freed;
+}
+
+void ion_heap_init_shrinker(struct ion_heap *heap)
+{
+ heap->shrinker.count_objects = ion_heap_shrink_count;
+ heap->shrinker.scan_objects = ion_heap_shrink_scan;
+ heap->shrinker.seeks = DEFAULT_SEEKS;
+ heap->shrinker.batch = 0;
+ register_shrinker(&heap->shrinker);
+}
+
+struct ion_heap *ion_heap_create(struct ion_platform_heap *heap_data)
+{
+ struct ion_heap *heap = NULL;
+
+ switch (heap_data->type) {
+ case ION_HEAP_TYPE_SYSTEM_CONTIG:
+ heap = ion_system_contig_heap_create(heap_data);
+ break;
+ case ION_HEAP_TYPE_SYSTEM:
+ heap = ion_system_heap_create(heap_data);
+ break;
+ case ION_HEAP_TYPE_CARVEOUT:
+ heap = ion_carveout_heap_create(heap_data);
+ break;
+ case ION_HEAP_TYPE_CHUNK:
+ heap = ion_chunk_heap_create(heap_data);
+ break;
+ case ION_HEAP_TYPE_DMA:
+ heap = ion_cma_heap_create(heap_data);
+ break;
+ default:
+ pr_err("%s: Invalid heap type %d\n", __func__,
+ heap_data->type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (IS_ERR_OR_NULL(heap)) {
+ pr_err("%s: error creating heap %s type %d base %lu size %zu\n",
+ __func__, heap_data->name, heap_data->type,
+ heap_data->base, heap_data->size);
+ return ERR_PTR(-EINVAL);
+ }
+
+ heap->name = heap_data->name;
+ heap->id = heap_data->id;
+ return heap;
+}
+
+void ion_heap_destroy(struct ion_heap *heap)
+{
+ if (!heap)
+ return;
+
+ switch (heap->type) {
+ case ION_HEAP_TYPE_SYSTEM_CONTIG:
+ ion_system_contig_heap_destroy(heap);
+ break;
+ case ION_HEAP_TYPE_SYSTEM:
+ ion_system_heap_destroy(heap);
+ break;
+ case ION_HEAP_TYPE_CARVEOUT:
+ ion_carveout_heap_destroy(heap);
+ break;
+ case ION_HEAP_TYPE_CHUNK:
+ ion_chunk_heap_destroy(heap);
+ break;
+ case ION_HEAP_TYPE_DMA:
+ ion_cma_heap_destroy(heap);
+ break;
+ default:
+ pr_err("%s: Invalid heap type %d\n", __func__,
+ heap->type);
+ }
+}
diff --git a/drivers/staging/android/ion/ion_page_pool.c b/drivers/staging/android/ion/ion_page_pool.c
new file mode 100644
index 00000000000..5864f3dfcbc
--- /dev/null
+++ b/drivers/staging/android/ion/ion_page_pool.c
@@ -0,0 +1,182 @@
+/*
+ * drivers/staging/android/ion/ion_mem_pool.c
+ *
+ * Copyright (C) 2011 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 <linux/debugfs.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+#include "ion_priv.h"
+
+static void *ion_page_pool_alloc_pages(struct ion_page_pool *pool)
+{
+ struct page *page = alloc_pages(pool->gfp_mask, pool->order);
+
+ if (!page)
+ return NULL;
+ ion_pages_sync_for_device(NULL, page, PAGE_SIZE << pool->order,
+ DMA_BIDIRECTIONAL);
+ return page;
+}
+
+static void ion_page_pool_free_pages(struct ion_page_pool *pool,
+ struct page *page)
+{
+ __free_pages(page, pool->order);
+}
+
+static int ion_page_pool_add(struct ion_page_pool *pool, struct page *page)
+{
+ mutex_lock(&pool->mutex);
+ if (PageHighMem(page)) {
+ list_add_tail(&page->lru, &pool->high_items);
+ pool->high_count++;
+ } else {
+ list_add_tail(&page->lru, &pool->low_items);
+ pool->low_count++;
+ }
+ mutex_unlock(&pool->mutex);
+ return 0;
+}
+
+static struct page *ion_page_pool_remove(struct ion_page_pool *pool, bool high)
+{
+ struct page *page;
+
+ if (high) {
+ BUG_ON(!pool->high_count);
+ page = list_first_entry(&pool->high_items, struct page, lru);
+ pool->high_count--;
+ } else {
+ BUG_ON(!pool->low_count);
+ page = list_first_entry(&pool->low_items, struct page, lru);
+ pool->low_count--;
+ }
+
+ list_del(&page->lru);
+ return page;
+}
+
+struct page *ion_page_pool_alloc(struct ion_page_pool *pool)
+{
+ struct page *page = NULL;
+
+ BUG_ON(!pool);
+
+ mutex_lock(&pool->mutex);
+ if (pool->high_count)
+ page = ion_page_pool_remove(pool, true);
+ else if (pool->low_count)
+ page = ion_page_pool_remove(pool, false);
+ mutex_unlock(&pool->mutex);
+
+ if (!page)
+ page = ion_page_pool_alloc_pages(pool);
+
+ return page;
+}
+
+void ion_page_pool_free(struct ion_page_pool *pool, struct page *page)
+{
+ int ret;
+
+ BUG_ON(pool->order != compound_order(page));
+
+ ret = ion_page_pool_add(pool, page);
+ if (ret)
+ ion_page_pool_free_pages(pool, page);
+}
+
+static int ion_page_pool_total(struct ion_page_pool *pool, bool high)
+{
+ int count = pool->low_count;
+
+ if (high)
+ count += pool->high_count;
+
+ return count << pool->order;
+}
+
+int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask,
+ int nr_to_scan)
+{
+ int freed;
+ bool high;
+
+ if (current_is_kswapd())
+ high = 1;
+ else
+ high = !!(gfp_mask & __GFP_HIGHMEM);
+
+ if (nr_to_scan == 0)
+ return ion_page_pool_total(pool, high);
+
+ for (freed = 0; freed < nr_to_scan; freed++) {
+ struct page *page;
+
+ mutex_lock(&pool->mutex);
+ if (pool->low_count) {
+ page = ion_page_pool_remove(pool, false);
+ } else if (high && pool->high_count) {
+ page = ion_page_pool_remove(pool, true);
+ } else {
+ mutex_unlock(&pool->mutex);
+ break;
+ }
+ mutex_unlock(&pool->mutex);
+ ion_page_pool_free_pages(pool, page);
+ }
+
+ return freed;
+}
+
+struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order)
+{
+ struct ion_page_pool *pool = kmalloc(sizeof(struct ion_page_pool),
+ GFP_KERNEL);
+ if (!pool)
+ return NULL;
+ pool->high_count = 0;
+ pool->low_count = 0;
+ INIT_LIST_HEAD(&pool->low_items);
+ INIT_LIST_HEAD(&pool->high_items);
+ pool->gfp_mask = gfp_mask | __GFP_COMP;
+ pool->order = order;
+ mutex_init(&pool->mutex);
+ plist_node_init(&pool->list, order);
+
+ return pool;
+}
+
+void ion_page_pool_destroy(struct ion_page_pool *pool)
+{
+ kfree(pool);
+}
+
+static int __init ion_page_pool_init(void)
+{
+ return 0;
+}
+
+static void __exit ion_page_pool_exit(void)
+{
+}
+
+module_init(ion_page_pool_init);
+module_exit(ion_page_pool_exit);
diff --git a/drivers/staging/android/ion/ion_priv.h b/drivers/staging/android/ion/ion_priv.h
new file mode 100644
index 00000000000..c8f01757abf
--- /dev/null
+++ b/drivers/staging/android/ion/ion_priv.h
@@ -0,0 +1,405 @@
+/*
+ * drivers/staging/android/ion/ion_priv.h
+ *
+ * Copyright (C) 2011 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 _ION_PRIV_H
+#define _ION_PRIV_H
+
+#include <linux/device.h>
+#include <linux/dma-direction.h>
+#include <linux/kref.h>
+#include <linux/mm_types.h>
+#include <linux/mutex.h>
+#include <linux/rbtree.h>
+#include <linux/sched.h>
+#include <linux/shrinker.h>
+#include <linux/types.h>
+
+#include "ion.h"
+
+struct ion_buffer *ion_handle_buffer(struct ion_handle *handle);
+
+/**
+ * struct ion_buffer - metadata for a particular buffer
+ * @ref: refernce count
+ * @node: node in the ion_device buffers tree
+ * @dev: back pointer to the ion_device
+ * @heap: back pointer to the heap the buffer came from
+ * @flags: buffer specific flags
+ * @private_flags: internal buffer specific flags
+ * @size: size of the buffer
+ * @priv_virt: private data to the buffer representable as
+ * a void *
+ * @priv_phys: private data to the buffer representable as
+ * an ion_phys_addr_t (and someday a phys_addr_t)
+ * @lock: protects the buffers cnt fields
+ * @kmap_cnt: number of times the buffer is mapped to the kernel
+ * @vaddr: the kenrel mapping if kmap_cnt is not zero
+ * @dmap_cnt: number of times the buffer is mapped for dma
+ * @sg_table: the sg table for the buffer if dmap_cnt is not zero
+ * @pages: flat array of pages in the buffer -- used by fault
+ * handler and only valid for buffers that are faulted in
+ * @vmas: list of vma's mapping this buffer
+ * @handle_count: count of handles referencing this buffer
+ * @task_comm: taskcomm of last client to reference this buffer in a
+ * handle, used for debugging
+ * @pid: pid of last client to reference this buffer in a
+ * handle, used for debugging
+*/
+struct ion_buffer {
+ struct kref ref;
+ union {
+ struct rb_node node;
+ struct list_head list;
+ };
+ struct ion_device *dev;
+ struct ion_heap *heap;
+ unsigned long flags;
+ unsigned long private_flags;
+ size_t size;
+ union {
+ void *priv_virt;
+ ion_phys_addr_t priv_phys;
+ };
+ struct mutex lock;
+ int kmap_cnt;
+ void *vaddr;
+ int dmap_cnt;
+ struct sg_table *sg_table;
+ struct page **pages;
+ struct list_head vmas;
+ /* used to track orphaned buffers */
+ int handle_count;
+ char task_comm[TASK_COMM_LEN];
+ pid_t pid;
+};
+void ion_buffer_destroy(struct ion_buffer *buffer);
+
+/**
+ * struct ion_heap_ops - ops to operate on a given heap
+ * @allocate: allocate memory
+ * @free: free memory
+ * @phys get physical address of a buffer (only define on
+ * physically contiguous heaps)
+ * @map_dma map the memory for dma to a scatterlist
+ * @unmap_dma unmap the memory for dma
+ * @map_kernel map memory to the kernel
+ * @unmap_kernel unmap memory to the kernel
+ * @map_user map memory to userspace
+ *
+ * allocate, phys, and map_user return 0 on success, -errno on error.
+ * map_dma and map_kernel return pointer on success, ERR_PTR on
+ * error. @free will be called with ION_PRIV_FLAG_SHRINKER_FREE set in
+ * the buffer's private_flags when called from a shrinker. In that
+ * case, the pages being free'd must be truly free'd back to the
+ * system, not put in a page pool or otherwise cached.
+ */
+struct ion_heap_ops {
+ int (*allocate)(struct ion_heap *heap,
+ struct ion_buffer *buffer, unsigned long len,
+ unsigned long align, unsigned long flags);
+ void (*free)(struct ion_buffer *buffer);
+ int (*phys)(struct ion_heap *heap, struct ion_buffer *buffer,
+ ion_phys_addr_t *addr, size_t *len);
+ struct sg_table * (*map_dma)(struct ion_heap *heap,
+ struct ion_buffer *buffer);
+ void (*unmap_dma)(struct ion_heap *heap, struct ion_buffer *buffer);
+ void * (*map_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
+ void (*unmap_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
+ int (*map_user)(struct ion_heap *mapper, struct ion_buffer *buffer,
+ struct vm_area_struct *vma);
+ int (*shrink)(struct ion_heap *heap, gfp_t gfp_mask, int nr_to_scan);
+};
+
+/**
+ * heap flags - flags between the heaps and core ion code
+ */
+#define ION_HEAP_FLAG_DEFER_FREE (1 << 0)
+
+/**
+ * private flags - flags internal to ion
+ */
+/*
+ * Buffer is being freed from a shrinker function. Skip any possible
+ * heap-specific caching mechanism (e.g. page pools). Guarantees that
+ * any buffer storage that came from the system allocator will be
+ * returned to the system allocator.
+ */
+#define ION_PRIV_FLAG_SHRINKER_FREE (1 << 0)
+
+/**
+ * struct ion_heap - represents a heap in the system
+ * @node: rb node to put the heap on the device's tree of heaps
+ * @dev: back pointer to the ion_device
+ * @type: type of heap
+ * @ops: ops struct as above
+ * @flags: flags
+ * @id: id of heap, also indicates priority of this heap when
+ * allocating. These are specified by platform data and
+ * MUST be unique
+ * @name: used for debugging
+ * @shrinker: a shrinker for the heap
+ * @free_list: free list head if deferred free is used
+ * @free_list_size size of the deferred free list in bytes
+ * @lock: protects the free list
+ * @waitqueue: queue to wait on from deferred free thread
+ * @task: task struct of deferred free thread
+ * @debug_show: called when heap debug file is read to add any
+ * heap specific debug info to output
+ *
+ * Represents a pool of memory from which buffers can be made. In some
+ * systems the only heap is regular system memory allocated via vmalloc.
+ * On others, some blocks might require large physically contiguous buffers
+ * that are allocated from a specially reserved heap.
+ */
+struct ion_heap {
+ struct plist_node node;
+ struct ion_device *dev;
+ enum ion_heap_type type;
+ struct ion_heap_ops *ops;
+ unsigned long flags;
+ unsigned int id;
+ const char *name;
+ struct shrinker shrinker;
+ struct list_head free_list;
+ size_t free_list_size;
+ spinlock_t free_lock;
+ wait_queue_head_t waitqueue;
+ struct task_struct *task;
+
+ int (*debug_show)(struct ion_heap *heap, struct seq_file *, void *);
+};
+
+/**
+ * ion_buffer_cached - this ion buffer is cached
+ * @buffer: buffer
+ *
+ * indicates whether this ion buffer is cached
+ */
+bool ion_buffer_cached(struct ion_buffer *buffer);
+
+/**
+ * ion_buffer_fault_user_mappings - fault in user mappings of this buffer
+ * @buffer: buffer
+ *
+ * indicates whether userspace mappings of this buffer will be faulted
+ * in, this can affect how buffers are allocated from the heap.
+ */
+bool ion_buffer_fault_user_mappings(struct ion_buffer *buffer);
+
+/**
+ * ion_device_create - allocates and returns an ion device
+ * @custom_ioctl: arch specific ioctl function if applicable
+ *
+ * returns a valid device or -PTR_ERR
+ */
+struct ion_device *ion_device_create(long (*custom_ioctl)
+ (struct ion_client *client,
+ unsigned int cmd,
+ unsigned long arg));
+
+/**
+ * ion_device_destroy - free and device and it's resource
+ * @dev: the device
+ */
+void ion_device_destroy(struct ion_device *dev);
+
+/**
+ * ion_device_add_heap - adds a heap to the ion device
+ * @dev: the device
+ * @heap: the heap to add
+ */
+void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap);
+
+/**
+ * some helpers for common operations on buffers using the sg_table
+ * and vaddr fields
+ */
+void *ion_heap_map_kernel(struct ion_heap *, struct ion_buffer *);
+void ion_heap_unmap_kernel(struct ion_heap *, struct ion_buffer *);
+int ion_heap_map_user(struct ion_heap *, struct ion_buffer *,
+ struct vm_area_struct *);
+int ion_heap_buffer_zero(struct ion_buffer *buffer);
+int ion_heap_pages_zero(struct page *page, size_t size, pgprot_t pgprot);
+
+/**
+ * ion_heap_init_shrinker
+ * @heap: the heap
+ *
+ * If a heap sets the ION_HEAP_FLAG_DEFER_FREE flag or defines the shrink op
+ * this function will be called to setup a shrinker to shrink the freelists
+ * and call the heap's shrink op.
+ */
+void ion_heap_init_shrinker(struct ion_heap *heap);
+
+/**
+ * ion_heap_init_deferred_free -- initialize deferred free functionality
+ * @heap: the heap
+ *
+ * If a heap sets the ION_HEAP_FLAG_DEFER_FREE flag this function will
+ * be called to setup deferred frees. Calls to free the buffer will
+ * return immediately and the actual free will occur some time later
+ */
+int ion_heap_init_deferred_free(struct ion_heap *heap);
+
+/**
+ * ion_heap_freelist_add - add a buffer to the deferred free list
+ * @heap: the heap
+ * @buffer: the buffer
+ *
+ * Adds an item to the deferred freelist.
+ */
+void ion_heap_freelist_add(struct ion_heap *heap, struct ion_buffer *buffer);
+
+/**
+ * ion_heap_freelist_drain - drain the deferred free list
+ * @heap: the heap
+ * @size: ammount of memory to drain in bytes
+ *
+ * Drains the indicated amount of memory from the deferred freelist immediately.
+ * Returns the total amount freed. The total freed may be higher depending
+ * on the size of the items in the list, or lower if there is insufficient
+ * total memory on the freelist.
+ */
+size_t ion_heap_freelist_drain(struct ion_heap *heap, size_t size);
+
+/**
+ * ion_heap_freelist_shrink - drain the deferred free
+ * list, skipping any heap-specific
+ * pooling or caching mechanisms
+ *
+ * @heap: the heap
+ * @size: amount of memory to drain in bytes
+ *
+ * Drains the indicated amount of memory from the deferred freelist immediately.
+ * Returns the total amount freed. The total freed may be higher depending
+ * on the size of the items in the list, or lower if there is insufficient
+ * total memory on the freelist.
+ *
+ * Unlike with @ion_heap_freelist_drain, don't put any pages back into
+ * page pools or otherwise cache the pages. Everything must be
+ * genuinely free'd back to the system. If you're free'ing from a
+ * shrinker you probably want to use this. Note that this relies on
+ * the heap.ops.free callback honoring the ION_PRIV_FLAG_SHRINKER_FREE
+ * flag.
+ */
+size_t ion_heap_freelist_shrink(struct ion_heap *heap,
+ size_t size);
+
+/**
+ * ion_heap_freelist_size - returns the size of the freelist in bytes
+ * @heap: the heap
+ */
+size_t ion_heap_freelist_size(struct ion_heap *heap);
+
+
+/**
+ * functions for creating and destroying the built in ion heaps.
+ * architectures can add their own custom architecture specific
+ * heaps as appropriate.
+ */
+
+struct ion_heap *ion_heap_create(struct ion_platform_heap *);
+void ion_heap_destroy(struct ion_heap *);
+struct ion_heap *ion_system_heap_create(struct ion_platform_heap *);
+void ion_system_heap_destroy(struct ion_heap *);
+
+struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *);
+void ion_system_contig_heap_destroy(struct ion_heap *);
+
+struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *);
+void ion_carveout_heap_destroy(struct ion_heap *);
+
+struct ion_heap *ion_chunk_heap_create(struct ion_platform_heap *);
+void ion_chunk_heap_destroy(struct ion_heap *);
+struct ion_heap *ion_cma_heap_create(struct ion_platform_heap *);
+void ion_cma_heap_destroy(struct ion_heap *);
+
+/**
+ * kernel api to allocate/free from carveout -- used when carveout is
+ * used to back an architecture specific custom heap
+ */
+ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, unsigned long size,
+ unsigned long align);
+void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr,
+ unsigned long size);
+/**
+ * The carveout heap returns physical addresses, since 0 may be a valid
+ * physical address, this is used to indicate allocation failed
+ */
+#define ION_CARVEOUT_ALLOCATE_FAIL -1
+
+/**
+ * functions for creating and destroying a heap pool -- allows you
+ * to keep a pool of pre allocated memory to use from your heap. Keeping
+ * a pool of memory that is ready for dma, ie any cached mapping have been
+ * invalidated from the cache, provides a significant peformance benefit on
+ * many systems */
+
+/**
+ * struct ion_page_pool - pagepool struct
+ * @high_count: number of highmem items in the pool
+ * @low_count: number of lowmem items in the pool
+ * @high_items: list of highmem items
+ * @low_items: list of lowmem items
+ * @mutex: lock protecting this struct and especially the count
+ * item list
+ * @gfp_mask: gfp_mask to use from alloc
+ * @order: order of pages in the pool
+ * @list: plist node for list of pools
+ *
+ * Allows you to keep a pool of pre allocated pages to use from your heap.
+ * Keeping a pool of pages that is ready for dma, ie any cached mapping have
+ * been invalidated from the cache, provides a significant peformance benefit
+ * on many systems
+ */
+struct ion_page_pool {
+ int high_count;
+ int low_count;
+ struct list_head high_items;
+ struct list_head low_items;
+ struct mutex mutex;
+ gfp_t gfp_mask;
+ unsigned int order;
+ struct plist_node list;
+};
+
+struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order);
+void ion_page_pool_destroy(struct ion_page_pool *);
+struct page *ion_page_pool_alloc(struct ion_page_pool *);
+void ion_page_pool_free(struct ion_page_pool *, struct page *);
+
+/** ion_page_pool_shrink - shrinks the size of the memory cached in the pool
+ * @pool: the pool
+ * @gfp_mask: the memory type to reclaim
+ * @nr_to_scan: number of items to shrink in pages
+ *
+ * returns the number of items freed in pages
+ */
+int ion_page_pool_shrink(struct ion_page_pool *pool, gfp_t gfp_mask,
+ int nr_to_scan);
+
+/**
+ * ion_pages_sync_for_device - cache flush pages for use with the specified
+ * device
+ * @dev: the device the pages will be used with
+ * @page: the first page to be flushed
+ * @size: size in bytes of region to be flushed
+ * @dir: direction of dma transfer
+ */
+void ion_pages_sync_for_device(struct device *dev, struct page *page,
+ size_t size, enum dma_data_direction dir);
+
+#endif /* _ION_PRIV_H */
diff --git a/drivers/staging/android/ion/ion_system_heap.c b/drivers/staging/android/ion/ion_system_heap.c
new file mode 100644
index 00000000000..cb7ae08a5e2
--- /dev/null
+++ b/drivers/staging/android/ion/ion_system_heap.c
@@ -0,0 +1,446 @@
+/*
+ * drivers/staging/android/ion/ion_system_heap.c
+ *
+ * Copyright (C) 2011 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 <asm/page.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/highmem.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include "ion.h"
+#include "ion_priv.h"
+
+static gfp_t high_order_gfp_flags = (GFP_HIGHUSER | __GFP_ZERO | __GFP_NOWARN |
+ __GFP_NORETRY) & ~__GFP_WAIT;
+static gfp_t low_order_gfp_flags = (GFP_HIGHUSER | __GFP_ZERO | __GFP_NOWARN);
+static const unsigned int orders[] = {8, 4, 0};
+static const int num_orders = ARRAY_SIZE(orders);
+static int order_to_index(unsigned int order)
+{
+ int i;
+
+ for (i = 0; i < num_orders; i++)
+ if (order == orders[i])
+ return i;
+ BUG();
+ return -1;
+}
+
+static inline unsigned int order_to_size(int order)
+{
+ return PAGE_SIZE << order;
+}
+
+struct ion_system_heap {
+ struct ion_heap heap;
+ struct ion_page_pool **pools;
+};
+
+struct page_info {
+ struct page *page;
+ unsigned int order;
+ struct list_head list;
+};
+
+static struct page *alloc_buffer_page(struct ion_system_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long order)
+{
+ bool cached = ion_buffer_cached(buffer);
+ struct ion_page_pool *pool = heap->pools[order_to_index(order)];
+ struct page *page;
+
+ if (!cached) {
+ page = ion_page_pool_alloc(pool);
+ } else {
+ gfp_t gfp_flags = low_order_gfp_flags;
+
+ if (order > 4)
+ gfp_flags = high_order_gfp_flags;
+ page = alloc_pages(gfp_flags | __GFP_COMP, order);
+ if (!page)
+ return NULL;
+ ion_pages_sync_for_device(NULL, page, PAGE_SIZE << order,
+ DMA_BIDIRECTIONAL);
+ }
+
+ return page;
+}
+
+static void free_buffer_page(struct ion_system_heap *heap,
+ struct ion_buffer *buffer, struct page *page,
+ unsigned int order)
+{
+ bool cached = ion_buffer_cached(buffer);
+
+ if (!cached && !(buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE)) {
+ struct ion_page_pool *pool = heap->pools[order_to_index(order)];
+
+ ion_page_pool_free(pool, page);
+ } else {
+ __free_pages(page, order);
+ }
+}
+
+
+static struct page_info *alloc_largest_available(struct ion_system_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long size,
+ unsigned int max_order)
+{
+ struct page *page;
+ struct page_info *info;
+ int i;
+
+ info = kmalloc(sizeof(struct page_info), GFP_KERNEL);
+ if (!info)
+ return NULL;
+
+ for (i = 0; i < num_orders; i++) {
+ if (size < order_to_size(orders[i]))
+ continue;
+ if (max_order < orders[i])
+ continue;
+
+ page = alloc_buffer_page(heap, buffer, orders[i]);
+ if (!page)
+ continue;
+
+ info->page = page;
+ info->order = orders[i];
+ return info;
+ }
+ kfree(info);
+
+ return NULL;
+}
+
+static int ion_system_heap_allocate(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long size, unsigned long align,
+ unsigned long flags)
+{
+ struct ion_system_heap *sys_heap = container_of(heap,
+ struct ion_system_heap,
+ heap);
+ struct sg_table *table;
+ struct scatterlist *sg;
+ struct list_head pages;
+ struct page_info *info, *tmp_info;
+ int i = 0;
+ unsigned long size_remaining = PAGE_ALIGN(size);
+ unsigned int max_order = orders[0];
+
+ if (align > PAGE_SIZE)
+ return -EINVAL;
+
+ if (size / PAGE_SIZE > totalram_pages / 2)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&pages);
+ while (size_remaining > 0) {
+ info = alloc_largest_available(sys_heap, buffer, size_remaining,
+ max_order);
+ if (!info)
+ goto free_pages;
+ list_add_tail(&info->list, &pages);
+ size_remaining -= PAGE_SIZE << info->order;
+ max_order = info->order;
+ i++;
+ }
+ table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!table)
+ goto free_pages;
+
+ if (sg_alloc_table(table, i, GFP_KERNEL))
+ goto free_table;
+
+ sg = table->sgl;
+ list_for_each_entry_safe(info, tmp_info, &pages, list) {
+ struct page *page = info->page;
+ sg_set_page(sg, page, PAGE_SIZE << info->order, 0);
+ sg = sg_next(sg);
+ list_del(&info->list);
+ kfree(info);
+ }
+
+ buffer->priv_virt = table;
+ return 0;
+
+free_table:
+ kfree(table);
+free_pages:
+ list_for_each_entry_safe(info, tmp_info, &pages, list) {
+ free_buffer_page(sys_heap, buffer, info->page, info->order);
+ kfree(info);
+ }
+ return -ENOMEM;
+}
+
+static void ion_system_heap_free(struct ion_buffer *buffer)
+{
+ struct ion_system_heap *sys_heap = container_of(buffer->heap,
+ struct ion_system_heap,
+ heap);
+ struct sg_table *table = buffer->sg_table;
+ bool cached = ion_buffer_cached(buffer);
+ struct scatterlist *sg;
+ int i;
+
+ /* uncached pages come from the page pools, zero them before returning
+ for security purposes (other allocations are zerod at alloc time */
+ if (!cached && !(buffer->private_flags & ION_PRIV_FLAG_SHRINKER_FREE))
+ ion_heap_buffer_zero(buffer);
+
+ for_each_sg(table->sgl, sg, table->nents, i)
+ free_buffer_page(sys_heap, buffer, sg_page(sg),
+ get_order(sg->length));
+ sg_free_table(table);
+ kfree(table);
+}
+
+static struct sg_table *ion_system_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return buffer->priv_virt;
+}
+
+static void ion_system_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return;
+}
+
+static int ion_system_heap_shrink(struct ion_heap *heap, gfp_t gfp_mask,
+ int nr_to_scan)
+{
+ struct ion_system_heap *sys_heap;
+ int nr_total = 0;
+ int i;
+
+ sys_heap = container_of(heap, struct ion_system_heap, heap);
+
+ for (i = 0; i < num_orders; i++) {
+ struct ion_page_pool *pool = sys_heap->pools[i];
+
+ nr_total += ion_page_pool_shrink(pool, gfp_mask, nr_to_scan);
+ }
+
+ return nr_total;
+}
+
+static struct ion_heap_ops system_heap_ops = {
+ .allocate = ion_system_heap_allocate,
+ .free = ion_system_heap_free,
+ .map_dma = ion_system_heap_map_dma,
+ .unmap_dma = ion_system_heap_unmap_dma,
+ .map_kernel = ion_heap_map_kernel,
+ .unmap_kernel = ion_heap_unmap_kernel,
+ .map_user = ion_heap_map_user,
+ .shrink = ion_system_heap_shrink,
+};
+
+static int ion_system_heap_debug_show(struct ion_heap *heap, struct seq_file *s,
+ void *unused)
+{
+
+ struct ion_system_heap *sys_heap = container_of(heap,
+ struct ion_system_heap,
+ heap);
+ int i;
+
+ for (i = 0; i < num_orders; i++) {
+ struct ion_page_pool *pool = sys_heap->pools[i];
+
+ seq_printf(s, "%d order %u highmem pages in pool = %lu total\n",
+ pool->high_count, pool->order,
+ (PAGE_SIZE << pool->order) * pool->high_count);
+ seq_printf(s, "%d order %u lowmem pages in pool = %lu total\n",
+ pool->low_count, pool->order,
+ (PAGE_SIZE << pool->order) * pool->low_count);
+ }
+ return 0;
+}
+
+struct ion_heap *ion_system_heap_create(struct ion_platform_heap *unused)
+{
+ struct ion_system_heap *heap;
+ int i;
+
+ heap = kzalloc(sizeof(struct ion_system_heap), GFP_KERNEL);
+ if (!heap)
+ return ERR_PTR(-ENOMEM);
+ heap->heap.ops = &system_heap_ops;
+ heap->heap.type = ION_HEAP_TYPE_SYSTEM;
+ heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
+ heap->pools = kzalloc(sizeof(struct ion_page_pool *) * num_orders,
+ GFP_KERNEL);
+ if (!heap->pools)
+ goto free_heap;
+ for (i = 0; i < num_orders; i++) {
+ struct ion_page_pool *pool;
+ gfp_t gfp_flags = low_order_gfp_flags;
+
+ if (orders[i] > 4)
+ gfp_flags = high_order_gfp_flags;
+ pool = ion_page_pool_create(gfp_flags, orders[i]);
+ if (!pool)
+ goto destroy_pools;
+ heap->pools[i] = pool;
+ }
+
+ heap->heap.debug_show = ion_system_heap_debug_show;
+ return &heap->heap;
+
+destroy_pools:
+ while (i--)
+ ion_page_pool_destroy(heap->pools[i]);
+ kfree(heap->pools);
+free_heap:
+ kfree(heap);
+ return ERR_PTR(-ENOMEM);
+}
+
+void ion_system_heap_destroy(struct ion_heap *heap)
+{
+ struct ion_system_heap *sys_heap = container_of(heap,
+ struct ion_system_heap,
+ heap);
+ int i;
+
+ for (i = 0; i < num_orders; i++)
+ ion_page_pool_destroy(sys_heap->pools[i]);
+ kfree(sys_heap->pools);
+ kfree(sys_heap);
+}
+
+static int ion_system_contig_heap_allocate(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long len,
+ unsigned long align,
+ unsigned long flags)
+{
+ int order = get_order(len);
+ struct page *page;
+ struct sg_table *table;
+ unsigned long i;
+ int ret;
+
+ if (align > (PAGE_SIZE << order))
+ return -EINVAL;
+
+ page = alloc_pages(low_order_gfp_flags, order);
+ if (!page)
+ return -ENOMEM;
+
+ split_page(page, order);
+
+ len = PAGE_ALIGN(len);
+ for (i = len >> PAGE_SHIFT; i < (1 << order); i++)
+ __free_page(page + i);
+
+ table = kmalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!table) {
+ ret = -ENOMEM;
+ goto free_pages;
+ }
+
+ ret = sg_alloc_table(table, 1, GFP_KERNEL);
+ if (ret)
+ goto free_table;
+
+ sg_set_page(table->sgl, page, len, 0);
+
+ buffer->priv_virt = table;
+
+ ion_pages_sync_for_device(NULL, page, len, DMA_BIDIRECTIONAL);
+
+ return 0;
+
+free_table:
+ kfree(table);
+free_pages:
+ for (i = 0; i < len >> PAGE_SHIFT; i++)
+ __free_page(page + i);
+
+ return ret;
+}
+
+static void ion_system_contig_heap_free(struct ion_buffer *buffer)
+{
+ struct sg_table *table = buffer->priv_virt;
+ struct page *page = sg_page(table->sgl);
+ unsigned long pages = PAGE_ALIGN(buffer->size) >> PAGE_SHIFT;
+ unsigned long i;
+
+ for (i = 0; i < pages; i++)
+ __free_page(page + i);
+ sg_free_table(table);
+ kfree(table);
+}
+
+static int ion_system_contig_heap_phys(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ ion_phys_addr_t *addr, size_t *len)
+{
+ struct sg_table *table = buffer->priv_virt;
+ struct page *page = sg_page(table->sgl);
+ *addr = page_to_phys(page);
+ *len = buffer->size;
+ return 0;
+}
+
+static struct sg_table *ion_system_contig_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ return buffer->priv_virt;
+}
+
+static void ion_system_contig_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+}
+
+static struct ion_heap_ops kmalloc_ops = {
+ .allocate = ion_system_contig_heap_allocate,
+ .free = ion_system_contig_heap_free,
+ .phys = ion_system_contig_heap_phys,
+ .map_dma = ion_system_contig_heap_map_dma,
+ .unmap_dma = ion_system_contig_heap_unmap_dma,
+ .map_kernel = ion_heap_map_kernel,
+ .unmap_kernel = ion_heap_unmap_kernel,
+ .map_user = ion_heap_map_user,
+};
+
+struct ion_heap *ion_system_contig_heap_create(struct ion_platform_heap *unused)
+{
+ struct ion_heap *heap;
+
+ heap = kzalloc(sizeof(struct ion_heap), GFP_KERNEL);
+ if (!heap)
+ return ERR_PTR(-ENOMEM);
+ heap->ops = &kmalloc_ops;
+ heap->type = ION_HEAP_TYPE_SYSTEM_CONTIG;
+ return heap;
+}
+
+void ion_system_contig_heap_destroy(struct ion_heap *heap)
+{
+ kfree(heap);
+}
diff --git a/drivers/staging/android/ion/ion_test.c b/drivers/staging/android/ion/ion_test.c
new file mode 100644
index 00000000000..654acb5c8eb
--- /dev/null
+++ b/drivers/staging/android/ion/ion_test.c
@@ -0,0 +1,282 @@
+/*
+ *
+ * Copyright (C) 2013 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.
+ *
+ */
+
+#define pr_fmt(fmt) "ion-test: " fmt
+
+#include <linux/dma-buf.h>
+#include <linux/dma-direction.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "ion.h"
+#include "../uapi/ion_test.h"
+
+#define u64_to_uptr(x) ((void __user *)(unsigned long)(x))
+
+struct ion_test_device {
+ struct miscdevice misc;
+};
+
+struct ion_test_data {
+ struct dma_buf *dma_buf;
+ struct device *dev;
+};
+
+static int ion_handle_test_dma(struct device *dev, struct dma_buf *dma_buf,
+ void __user *ptr, size_t offset, size_t size, bool write)
+{
+ int ret = 0;
+ struct dma_buf_attachment *attach;
+ struct sg_table *table;
+ pgprot_t pgprot = pgprot_writecombine(PAGE_KERNEL);
+ enum dma_data_direction dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ struct sg_page_iter sg_iter;
+ unsigned long offset_page;
+
+ attach = dma_buf_attach(dma_buf, dev);
+ if (IS_ERR(attach))
+ return PTR_ERR(attach);
+
+ table = dma_buf_map_attachment(attach, dir);
+ if (IS_ERR(table))
+ return PTR_ERR(table);
+
+ offset_page = offset >> PAGE_SHIFT;
+ offset %= PAGE_SIZE;
+
+ for_each_sg_page(table->sgl, &sg_iter, table->nents, offset_page) {
+ struct page *page = sg_page_iter_page(&sg_iter);
+ void *vaddr = vmap(&page, 1, VM_MAP, pgprot);
+ size_t to_copy = PAGE_SIZE - offset;
+
+ to_copy = min(to_copy, size);
+ if (!vaddr) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (write)
+ ret = copy_from_user(vaddr + offset, ptr, to_copy);
+ else
+ ret = copy_to_user(ptr, vaddr + offset, to_copy);
+
+ vunmap(vaddr);
+ if (ret) {
+ ret = -EFAULT;
+ goto err;
+ }
+ size -= to_copy;
+ if (!size)
+ break;
+ ptr += to_copy;
+ offset = 0;
+ }
+
+err:
+ dma_buf_unmap_attachment(attach, table, dir);
+ dma_buf_detach(dma_buf, attach);
+ return ret;
+}
+
+static int ion_handle_test_kernel(struct dma_buf *dma_buf, void __user *ptr,
+ size_t offset, size_t size, bool write)
+{
+ int ret;
+ unsigned long page_offset = offset >> PAGE_SHIFT;
+ size_t copy_offset = offset % PAGE_SIZE;
+ size_t copy_size = size;
+ enum dma_data_direction dir = write ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+ if (offset > dma_buf->size || size > dma_buf->size - offset)
+ return -EINVAL;
+
+ ret = dma_buf_begin_cpu_access(dma_buf, offset, size, dir);
+ if (ret)
+ return ret;
+
+ while (copy_size > 0) {
+ size_t to_copy;
+ void *vaddr = dma_buf_kmap(dma_buf, page_offset);
+
+ if (!vaddr)
+ goto err;
+
+ to_copy = min_t(size_t, PAGE_SIZE - copy_offset, copy_size);
+
+ if (write)
+ ret = copy_from_user(vaddr + copy_offset, ptr, to_copy);
+ else
+ ret = copy_to_user(ptr, vaddr + copy_offset, to_copy);
+
+ dma_buf_kunmap(dma_buf, page_offset, vaddr);
+ if (ret) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ copy_size -= to_copy;
+ ptr += to_copy;
+ page_offset++;
+ copy_offset = 0;
+ }
+err:
+ dma_buf_end_cpu_access(dma_buf, offset, size, dir);
+ return ret;
+}
+
+static long ion_test_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct ion_test_data *test_data = filp->private_data;
+ int ret = 0;
+
+ union {
+ struct ion_test_rw_data test_rw;
+ } data;
+
+ if (_IOC_SIZE(cmd) > sizeof(data))
+ return -EINVAL;
+
+ if (_IOC_DIR(cmd) & _IOC_WRITE)
+ if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case ION_IOC_TEST_SET_FD:
+ {
+ struct dma_buf *dma_buf = NULL;
+ int fd = arg;
+
+ if (fd >= 0) {
+ dma_buf = dma_buf_get((int)arg);
+ if (IS_ERR(dma_buf))
+ return PTR_ERR(dma_buf);
+ }
+ if (test_data->dma_buf)
+ dma_buf_put(test_data->dma_buf);
+ test_data->dma_buf = dma_buf;
+ break;
+ }
+ case ION_IOC_TEST_DMA_MAPPING:
+ {
+ ret = ion_handle_test_dma(test_data->dev, test_data->dma_buf,
+ u64_to_uptr(data.test_rw.ptr),
+ data.test_rw.offset, data.test_rw.size,
+ data.test_rw.write);
+ break;
+ }
+ case ION_IOC_TEST_KERNEL_MAPPING:
+ {
+ ret = ion_handle_test_kernel(test_data->dma_buf,
+ u64_to_uptr(data.test_rw.ptr),
+ data.test_rw.offset, data.test_rw.size,
+ data.test_rw.write);
+ break;
+ }
+ default:
+ return -ENOTTY;
+ }
+
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ if (copy_to_user((void __user *)arg, &data, sizeof(data)))
+ return -EFAULT;
+ }
+ return ret;
+}
+
+static int ion_test_open(struct inode *inode, struct file *file)
+{
+ struct ion_test_data *data;
+ struct miscdevice *miscdev = file->private_data;
+
+ data = kzalloc(sizeof(struct ion_test_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = miscdev->parent;
+
+ file->private_data = data;
+
+ return 0;
+}
+
+static int ion_test_release(struct inode *inode, struct file *file)
+{
+ struct ion_test_data *data = file->private_data;
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct file_operations ion_test_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = ion_test_ioctl,
+ .compat_ioctl = ion_test_ioctl,
+ .open = ion_test_open,
+ .release = ion_test_release,
+};
+
+static int __init ion_test_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct ion_test_device *testdev;
+
+ testdev = devm_kzalloc(&pdev->dev, sizeof(struct ion_test_device),
+ GFP_KERNEL);
+ if (!testdev)
+ return -ENOMEM;
+
+ testdev->misc.minor = MISC_DYNAMIC_MINOR;
+ testdev->misc.name = "ion-test";
+ testdev->misc.fops = &ion_test_fops;
+ testdev->misc.parent = &pdev->dev;
+ ret = misc_register(&testdev->misc);
+ if (ret) {
+ pr_err("failed to register misc device.\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, testdev);
+
+ return 0;
+}
+
+static struct platform_driver ion_test_platform_driver = {
+ .driver = {
+ .name = "ion-test",
+ },
+};
+
+static int __init ion_test_init(void)
+{
+ platform_device_register_simple("ion-test", -1, NULL, 0);
+ return platform_driver_probe(&ion_test_platform_driver, ion_test_probe);
+}
+
+static void __exit ion_test_exit(void)
+{
+ platform_driver_unregister(&ion_test_platform_driver);
+}
+
+module_init(ion_test_init);
+module_exit(ion_test_exit);
diff --git a/drivers/staging/android/ion/tegra/Makefile b/drivers/staging/android/ion/tegra/Makefile
new file mode 100644
index 00000000000..11cd003fb08
--- /dev/null
+++ b/drivers/staging/android/ion/tegra/Makefile
@@ -0,0 +1 @@
+obj-y += tegra_ion.o
diff --git a/drivers/staging/android/ion/tegra/tegra_ion.c b/drivers/staging/android/ion/tegra/tegra_ion.c
new file mode 100644
index 00000000000..11c7cceb3c7
--- /dev/null
+++ b/drivers/staging/android/ion/tegra/tegra_ion.c
@@ -0,0 +1,82 @@
+/*
+ * drivers/gpu/tegra/tegra_ion.c
+ *
+ * Copyright (C) 2011 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 <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include "../ion.h"
+#include "../ion_priv.h"
+
+static struct ion_device *idev;
+static int num_heaps;
+static struct ion_heap **heaps;
+
+static int tegra_ion_probe(struct platform_device *pdev)
+{
+ struct ion_platform_data *pdata = pdev->dev.platform_data;
+ int err;
+ int i;
+
+ num_heaps = pdata->nr;
+
+ heaps = devm_kzalloc(&pdev->dev,
+ sizeof(struct ion_heap *) * pdata->nr,
+ GFP_KERNEL);
+
+ idev = ion_device_create(NULL);
+ if (IS_ERR_OR_NULL(idev))
+ return PTR_ERR(idev);
+
+ /* create the heaps as specified in the board file */
+ for (i = 0; i < num_heaps; i++) {
+ struct ion_platform_heap *heap_data = &pdata->heaps[i];
+
+ heaps[i] = ion_heap_create(heap_data);
+ if (IS_ERR_OR_NULL(heaps[i])) {
+ err = PTR_ERR(heaps[i]);
+ goto err;
+ }
+ ion_device_add_heap(idev, heaps[i]);
+ }
+ platform_set_drvdata(pdev, idev);
+ return 0;
+err:
+ for (i = 0; i < num_heaps; i++) {
+ if (heaps[i])
+ ion_heap_destroy(heaps[i]);
+ }
+ return err;
+}
+
+static int tegra_ion_remove(struct platform_device *pdev)
+{
+ struct ion_device *idev = platform_get_drvdata(pdev);
+ int i;
+
+ ion_device_destroy(idev);
+ for (i = 0; i < num_heaps; i++)
+ ion_heap_destroy(heaps[i]);
+ return 0;
+}
+
+static struct platform_driver ion_driver = {
+ .probe = tegra_ion_probe,
+ .remove = tegra_ion_remove,
+ .driver = { .name = "ion-tegra" }
+};
+
+module_platform_driver(ion_driver);
+
diff --git a/drivers/staging/android/logger.c b/drivers/staging/android/logger.c
new file mode 100644
index 00000000000..2772e01b37f
--- /dev/null
+++ b/drivers/staging/android/logger.c
@@ -0,0 +1,854 @@
+/*
+ * drivers/misc/logger.c
+ *
+ * A Logging Subsystem
+ *
+ * Copyright (C) 2007-2008 Google, Inc.
+ *
+ * Robert Love <rlove@google.com>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "logger: " fmt
+
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/aio.h>
+#include "logger.h"
+
+#include <asm/ioctls.h>
+
+/**
+ * struct logger_log - represents a specific log, such as 'main' or 'radio'
+ * @buffer: The actual ring buffer
+ * @misc: The "misc" device representing the log
+ * @wq: The wait queue for @readers
+ * @readers: This log's readers
+ * @mutex: The mutex that protects the @buffer
+ * @w_off: The current write head offset
+ * @head: The head, or location that readers start reading at.
+ * @size: The size of the log
+ * @logs: The list of log channels
+ *
+ * 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;
+ struct miscdevice misc;
+ wait_queue_head_t wq;
+ struct list_head readers;
+ struct mutex mutex;
+ size_t w_off;
+ size_t head;
+ size_t size;
+ struct list_head logs;
+};
+
+static LIST_HEAD(log_list);
+
+
+/**
+ * struct logger_reader - a logging device open for reading
+ * @log: The associated log
+ * @list: The associated entry in @logger_log's list
+ * @r_off: The current read head offset.
+ * @r_all: Reader can read all entries
+ * @r_ver: Reader ABI version
+ *
+ * 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;
+ struct list_head list;
+ size_t r_off;
+ bool r_all;
+ int r_ver;
+};
+
+/* logger_offset - returns index 'n' into the log via (optimized) modulus */
+static 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.
+ */
+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, kuid_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 (uid_eq(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 a<b, check for c between a and b
+ * and if a>b, 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)
+{
+ size_t len;
+
+ 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.
+ */
+static 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;
+ struct logger_entry header;
+ struct timespec now;
+ ssize_t ret = 0;
+
+ now = current_kernel_time();
+
+ header.pid = current->tgid;
+ header.tid = current->pid;
+ header.sec = now.tv_sec;
+ header.nsec = now.tv_nsec;
+ header.euid = current_euid();
+ header.len = min_t(size_t, iocb->ki_nbytes, 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);
+
+ orig = log->w_off;
+
+ /*
+ * 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);
+ 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 minor)
+{
+ struct logger_log *log;
+
+ list_for_each_entry(log, &log_list, logs)
+ if (log->misc.minor == minor)
+ return log;
+ return NULL;
+}
+
+/*
+ * 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_inode(file)->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 const 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,
+};
+
+/*
+ * Log size must must be a power of two, and greater than
+ * (LOGGER_ENTRY_MAX_PAYLOAD + sizeof(struct logger_entry)).
+ */
+static int __init create_log(char *log_name, int size)
+{
+ int ret = 0;
+ struct logger_log *log;
+ unsigned char *buffer;
+
+ buffer = vmalloc(size);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);
+ if (log == NULL) {
+ ret = -ENOMEM;
+ goto out_free_buffer;
+ }
+ log->buffer = buffer;
+
+ log->misc.minor = MISC_DYNAMIC_MINOR;
+ log->misc.name = kstrdup(log_name, GFP_KERNEL);
+ if (log->misc.name == NULL) {
+ ret = -ENOMEM;
+ goto out_free_log;
+ }
+
+ log->misc.fops = &logger_fops;
+ log->misc.parent = NULL;
+
+ init_waitqueue_head(&log->wq);
+ INIT_LIST_HEAD(&log->readers);
+ mutex_init(&log->mutex);
+ log->w_off = 0;
+ log->head = 0;
+ log->size = size;
+
+ INIT_LIST_HEAD(&log->logs);
+ list_add_tail(&log->logs, &log_list);
+
+ /* finally, initialize the misc device for this log */
+ ret = misc_register(&log->misc);
+ if (unlikely(ret)) {
+ pr_err("failed to register misc device for log '%s'!\n",
+ log->misc.name);
+ goto out_free_log;
+ }
+
+ pr_info("created %luK log '%s'\n",
+ (unsigned long) log->size >> 10, log->misc.name);
+
+ return 0;
+
+out_free_log:
+ kfree(log);
+
+out_free_buffer:
+ vfree(buffer);
+ return ret;
+}
+
+static int __init logger_init(void)
+{
+ int ret;
+
+ ret = create_log(LOGGER_LOG_MAIN, 256*1024);
+ if (unlikely(ret))
+ goto out;
+
+ ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
+ if (unlikely(ret))
+ goto out;
+
+ ret = create_log(LOGGER_LOG_RADIO, 256*1024);
+ if (unlikely(ret))
+ goto out;
+
+ ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
+ if (unlikely(ret))
+ goto out;
+
+out:
+ return ret;
+}
+
+static void __exit logger_exit(void)
+{
+ struct logger_log *current_log, *next_log;
+
+ list_for_each_entry_safe(current_log, next_log, &log_list, logs) {
+ /* we have to delete all the entry inside log_list */
+ misc_deregister(&current_log->misc);
+ vfree(current_log->buffer);
+ kfree(current_log->misc.name);
+ list_del(&current_log->logs);
+ kfree(current_log);
+ }
+}
+
+
+device_initcall(logger_init);
+module_exit(logger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robert Love, <rlove@google.com>");
+MODULE_DESCRIPTION("Android Logger");
diff --git a/drivers/staging/android/logger.h b/drivers/staging/android/logger.h
new file mode 100644
index 00000000000..70af7d805df
--- /dev/null
+++ b/drivers/staging/android/logger.h
@@ -0,0 +1,89 @@
+/* include/linux/logger.h
+ *
+ * Copyright (C) 2007-2008 Google, Inc.
+ * Author: Robert Love <rlove@android.com>
+ *
+ * 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 <linux/types.h>
+#include <linux/ioctl.h>
+
+/**
+ * struct user_logger_entry_compat - defines a single entry that is given to a logger
+ * @len: The length of the payload
+ * @__pad: Two bytes of padding that appear to be required
+ * @pid: The generating process' process ID
+ * @tid: The generating process' thread ID
+ * @sec: The number of seconds that have elapsed since the Epoch
+ * @nsec: The number of nanoseconds that have elapsed since @sec
+ * @msg: The message that is to be logged
+ *
+ * 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;
+ __u16 __pad;
+ __s32 pid;
+ __s32 tid;
+ __s32 sec;
+ __s32 nsec;
+ char msg[0];
+};
+
+/**
+ * struct logger_entry - defines a single entry that is given to a logger
+ * @len: The length of the payload
+ * @hdr_size: sizeof(struct logger_entry_v2)
+ * @pid: The generating process' process ID
+ * @tid: The generating process' thread ID
+ * @sec: The number of seconds that have elapsed since the Epoch
+ * @nsec: The number of nanoseconds that have elapsed since @sec
+ * @euid: Effective UID of logger
+ * @msg: The message that is to be logged
+ *
+ * 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;
+ __u16 hdr_size;
+ __s32 pid;
+ __s32 tid;
+ __s32 sec;
+ __s32 nsec;
+ kuid_t euid;
+ char msg[0];
+};
+
+#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 00000000000..b545d3d1da3
--- /dev/null
+++ b/drivers/staging/android/lowmemorykiller.c
@@ -0,0 +1,202 @@
+/* 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/oom.h>
+#include <linux/sched.h>
+#include <linux/swap.h>
+#include <linux/rcupdate.h>
+#include <linux/profile.h>
+#include <linux/notifier.h>
+
+static uint32_t lowmem_debug_level = 1;
+static short 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)) \
+ pr_info(x); \
+ } while (0)
+
+static unsigned long lowmem_count(struct shrinker *s,
+ struct shrink_control *sc)
+{
+ return global_page_state(NR_ACTIVE_ANON) +
+ global_page_state(NR_ACTIVE_FILE) +
+ global_page_state(NR_INACTIVE_ANON) +
+ global_page_state(NR_INACTIVE_FILE);
+}
+
+static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
+{
+ struct task_struct *tsk;
+ struct task_struct *selected = NULL;
+ unsigned long rem = 0;
+ int tasksize;
+ int i;
+ short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
+ int selected_tasksize = 0;
+ short 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) -
+ total_swapcache_pages();
+
+ 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;
+ }
+ }
+
+ lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n",
+ sc->nr_to_scan, sc->gfp_mask, other_free,
+ other_file, min_score_adj);
+
+ if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
+ lowmem_print(5, "lowmem_scan %lu, %x, return 0\n",
+ sc->nr_to_scan, sc->gfp_mask);
+ return 0;
+ }
+
+ selected_oom_score_adj = min_score_adj;
+
+ rcu_read_lock();
+ for_each_process(tsk) {
+ struct task_struct *p;
+ short 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 %hd, size %d, to kill\n",
+ p->pid, p->comm, oom_score_adj, tasksize);
+ }
+ if (selected) {
+ lowmem_print(1, "send sigkill to %d (%s), adj %hd, size %d\n",
+ selected->pid, selected->comm,
+ selected_oom_score_adj, selected_tasksize);
+ lowmem_deathpending_timeout = jiffies + HZ;
+ set_tsk_thread_flag(selected, TIF_MEMDIE);
+ send_sig(SIGKILL, selected, 0);
+ rem += selected_tasksize;
+ }
+
+ lowmem_print(4, "lowmem_scan %lu, %x, return %lu\n",
+ sc->nr_to_scan, sc->gfp_mask, rem);
+ rcu_read_unlock();
+ return rem;
+}
+
+static struct shrinker lowmem_shrinker = {
+ .scan_objects = lowmem_scan,
+ .count_objects = lowmem_count,
+ .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);
+}
+
+module_param_named(cost, lowmem_shrinker.seeks, int, S_IRUGO | S_IWUSR);
+module_param_array_named(adj, lowmem_adj, short, &lowmem_adj_size,
+ S_IRUGO | S_IWUSR);
+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/sw_sync.c b/drivers/staging/android/sw_sync.c
new file mode 100644
index 00000000000..12a136ec1ce
--- /dev/null
+++ b/drivers/staging/android/sw_sync.c
@@ -0,0 +1,268 @@
+/*
+ * drivers/base/sw_sync.c
+ *
+ * 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 <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+
+#include "sw_sync.h"
+
+static int sw_sync_cmp(u32 a, u32 b)
+{
+ if (a == b)
+ return 0;
+
+ return ((s32)a - (s32)b) < 0 ? -1 : 1;
+}
+
+struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, u32 value)
+{
+ struct sw_sync_pt *pt;
+
+ pt = (struct sw_sync_pt *)
+ sync_pt_create(&obj->obj, sizeof(struct sw_sync_pt));
+
+ pt->value = value;
+
+ return (struct sync_pt *)pt;
+}
+EXPORT_SYMBOL(sw_sync_pt_create);
+
+static struct sync_pt *sw_sync_pt_dup(struct sync_pt *sync_pt)
+{
+ struct sw_sync_pt *pt = (struct sw_sync_pt *) sync_pt;
+ struct sw_sync_timeline *obj =
+ (struct sw_sync_timeline *)sync_pt->parent;
+
+ return (struct sync_pt *) sw_sync_pt_create(obj, pt->value);
+}
+
+static int sw_sync_pt_has_signaled(struct sync_pt *sync_pt)
+{
+ struct sw_sync_pt *pt = (struct sw_sync_pt *)sync_pt;
+ struct sw_sync_timeline *obj =
+ (struct sw_sync_timeline *)sync_pt->parent;
+
+ return sw_sync_cmp(obj->value, pt->value) >= 0;
+}
+
+static int sw_sync_pt_compare(struct sync_pt *a, struct sync_pt *b)
+{
+ struct sw_sync_pt *pt_a = (struct sw_sync_pt *)a;
+ struct sw_sync_pt *pt_b = (struct sw_sync_pt *)b;
+
+ return sw_sync_cmp(pt_a->value, pt_b->value);
+}
+
+static int sw_sync_fill_driver_data(struct sync_pt *sync_pt,
+ void *data, int size)
+{
+ struct sw_sync_pt *pt = (struct sw_sync_pt *)sync_pt;
+
+ if (size < sizeof(pt->value))
+ return -ENOMEM;
+
+ memcpy(data, &pt->value, sizeof(pt->value));
+
+ return sizeof(pt->value);
+}
+
+static void sw_sync_timeline_value_str(struct sync_timeline *sync_timeline,
+ char *str, int size)
+{
+ struct sw_sync_timeline *timeline =
+ (struct sw_sync_timeline *)sync_timeline;
+ snprintf(str, size, "%d", timeline->value);
+}
+
+static void sw_sync_pt_value_str(struct sync_pt *sync_pt,
+ char *str, int size)
+{
+ struct sw_sync_pt *pt = (struct sw_sync_pt *)sync_pt;
+
+ snprintf(str, size, "%d", pt->value);
+}
+
+static struct sync_timeline_ops sw_sync_timeline_ops = {
+ .driver_name = "sw_sync",
+ .dup = sw_sync_pt_dup,
+ .has_signaled = sw_sync_pt_has_signaled,
+ .compare = sw_sync_pt_compare,
+ .fill_driver_data = sw_sync_fill_driver_data,
+ .timeline_value_str = sw_sync_timeline_value_str,
+ .pt_value_str = sw_sync_pt_value_str,
+};
+
+
+struct sw_sync_timeline *sw_sync_timeline_create(const char *name)
+{
+ struct sw_sync_timeline *obj = (struct sw_sync_timeline *)
+ sync_timeline_create(&sw_sync_timeline_ops,
+ sizeof(struct sw_sync_timeline),
+ name);
+
+ return obj;
+}
+EXPORT_SYMBOL(sw_sync_timeline_create);
+
+void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc)
+{
+ obj->value += inc;
+
+ sync_timeline_signal(&obj->obj);
+}
+EXPORT_SYMBOL(sw_sync_timeline_inc);
+
+#ifdef CONFIG_SW_SYNC_USER
+/* *WARNING*
+ *
+ * improper use of this can result in deadlocking kernel drivers from userspace.
+ */
+
+/* opening sw_sync create a new sync obj */
+static int sw_sync_open(struct inode *inode, struct file *file)
+{
+ struct sw_sync_timeline *obj;
+ char task_comm[TASK_COMM_LEN];
+
+ get_task_comm(task_comm, current);
+
+ obj = sw_sync_timeline_create(task_comm);
+ if (obj == NULL)
+ return -ENOMEM;
+
+ file->private_data = obj;
+
+ return 0;
+}
+
+static int sw_sync_release(struct inode *inode, struct file *file)
+{
+ struct sw_sync_timeline *obj = file->private_data;
+
+ sync_timeline_destroy(&obj->obj);
+ return 0;
+}
+
+static long sw_sync_ioctl_create_fence(struct sw_sync_timeline *obj,
+ unsigned long arg)
+{
+ int fd = get_unused_fd_flags(O_CLOEXEC);
+ int err;
+ struct sync_pt *pt;
+ struct sync_fence *fence;
+ struct sw_sync_create_fence_data data;
+
+ if (fd < 0)
+ return fd;
+
+ if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
+ err = -EFAULT;
+ goto err;
+ }
+
+ pt = sw_sync_pt_create(obj, data.value);
+ if (pt == NULL) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ data.name[sizeof(data.name) - 1] = '\0';
+ fence = sync_fence_create(data.name, pt);
+ if (fence == NULL) {
+ sync_pt_free(pt);
+ err = -ENOMEM;
+ goto err;
+ }
+
+ data.fence = fd;
+ if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
+ sync_fence_put(fence);
+ err = -EFAULT;
+ goto err;
+ }
+
+ sync_fence_install(fence, fd);
+
+ return 0;
+
+err:
+ put_unused_fd(fd);
+ return err;
+}
+
+static long sw_sync_ioctl_inc(struct sw_sync_timeline *obj, unsigned long arg)
+{
+ u32 value;
+
+ if (copy_from_user(&value, (void __user *)arg, sizeof(value)))
+ return -EFAULT;
+
+ sw_sync_timeline_inc(obj, value);
+
+ return 0;
+}
+
+static long sw_sync_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sw_sync_timeline *obj = file->private_data;
+
+ switch (cmd) {
+ case SW_SYNC_IOC_CREATE_FENCE:
+ return sw_sync_ioctl_create_fence(obj, arg);
+
+ case SW_SYNC_IOC_INC:
+ return sw_sync_ioctl_inc(obj, arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations sw_sync_fops = {
+ .owner = THIS_MODULE,
+ .open = sw_sync_open,
+ .release = sw_sync_release,
+ .unlocked_ioctl = sw_sync_ioctl,
+ .compat_ioctl = sw_sync_ioctl,
+};
+
+static struct miscdevice sw_sync_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "sw_sync",
+ .fops = &sw_sync_fops,
+};
+
+static int __init sw_sync_device_init(void)
+{
+ return misc_register(&sw_sync_dev);
+}
+
+static void __exit sw_sync_device_remove(void)
+{
+ misc_deregister(&sw_sync_dev);
+}
+
+module_init(sw_sync_device_init);
+module_exit(sw_sync_device_remove);
+
+#endif /* CONFIG_SW_SYNC_USER */
diff --git a/drivers/staging/android/sw_sync.h b/drivers/staging/android/sw_sync.h
new file mode 100644
index 00000000000..1a50669ec8a
--- /dev/null
+++ b/drivers/staging/android/sw_sync.h
@@ -0,0 +1,59 @@
+/*
+ * include/linux/sw_sync.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef _LINUX_SW_SYNC_H
+#define _LINUX_SW_SYNC_H
+
+#include <linux/types.h>
+#include <linux/kconfig.h>
+#include "sync.h"
+#include "uapi/sw_sync.h"
+
+struct sw_sync_timeline {
+ struct sync_timeline obj;
+
+ u32 value;
+};
+
+struct sw_sync_pt {
+ struct sync_pt pt;
+
+ u32 value;
+};
+
+#if IS_ENABLED(CONFIG_SW_SYNC)
+struct sw_sync_timeline *sw_sync_timeline_create(const char *name);
+void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc);
+
+struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj, u32 value);
+#else
+static inline struct sw_sync_timeline *sw_sync_timeline_create(const char *name)
+{
+ return NULL;
+}
+
+static inline void sw_sync_timeline_inc(struct sw_sync_timeline *obj, u32 inc)
+{
+}
+
+static inline struct sync_pt *sw_sync_pt_create(struct sw_sync_timeline *obj,
+ u32 value)
+{
+ return NULL;
+}
+#endif /* IS_ENABLED(CONFIG_SW_SYNC) */
+
+#endif /* _LINUX_SW_SYNC_H */
diff --git a/drivers/staging/android/sync.c b/drivers/staging/android/sync.c
new file mode 100644
index 00000000000..18174f7c871
--- /dev/null
+++ b/drivers/staging/android/sync.c
@@ -0,0 +1,1029 @@
+/*
+ * drivers/base/sync.c
+ *
+ * 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 <linux/debugfs.h>
+#include <linux/export.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/anon_inodes.h>
+
+#include "sync.h"
+
+#define CREATE_TRACE_POINTS
+#include "trace/sync.h"
+
+static void sync_fence_signal_pt(struct sync_pt *pt);
+static int _sync_pt_has_signaled(struct sync_pt *pt);
+static void sync_fence_free(struct kref *kref);
+static void sync_dump(void);
+
+static LIST_HEAD(sync_timeline_list_head);
+static DEFINE_SPINLOCK(sync_timeline_list_lock);
+
+static LIST_HEAD(sync_fence_list_head);
+static DEFINE_SPINLOCK(sync_fence_list_lock);
+
+struct sync_timeline *sync_timeline_create(const struct sync_timeline_ops *ops,
+ int size, const char *name)
+{
+ struct sync_timeline *obj;
+ unsigned long flags;
+
+ if (size < sizeof(struct sync_timeline))
+ return NULL;
+
+ obj = kzalloc(size, GFP_KERNEL);
+ if (obj == NULL)
+ return NULL;
+
+ kref_init(&obj->kref);
+ obj->ops = ops;
+ strlcpy(obj->name, name, sizeof(obj->name));
+
+ INIT_LIST_HEAD(&obj->child_list_head);
+ spin_lock_init(&obj->child_list_lock);
+
+ INIT_LIST_HEAD(&obj->active_list_head);
+ spin_lock_init(&obj->active_list_lock);
+
+ spin_lock_irqsave(&sync_timeline_list_lock, flags);
+ list_add_tail(&obj->sync_timeline_list, &sync_timeline_list_head);
+ spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
+
+ return obj;
+}
+EXPORT_SYMBOL(sync_timeline_create);
+
+static void sync_timeline_free(struct kref *kref)
+{
+ struct sync_timeline *obj =
+ container_of(kref, struct sync_timeline, kref);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sync_timeline_list_lock, flags);
+ list_del(&obj->sync_timeline_list);
+ spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
+
+ if (obj->ops->release_obj)
+ obj->ops->release_obj(obj);
+
+ kfree(obj);
+}
+
+void sync_timeline_destroy(struct sync_timeline *obj)
+{
+ obj->destroyed = true;
+ /*
+ * Ensure timeline is marked as destroyed before
+ * changing timeline's fences status.
+ */
+ smp_wmb();
+
+ /*
+ * signal any children that their parent is going away.
+ */
+ sync_timeline_signal(obj);
+
+ kref_put(&obj->kref, sync_timeline_free);
+}
+EXPORT_SYMBOL(sync_timeline_destroy);
+
+static void sync_timeline_add_pt(struct sync_timeline *obj, struct sync_pt *pt)
+{
+ unsigned long flags;
+
+ pt->parent = obj;
+
+ spin_lock_irqsave(&obj->child_list_lock, flags);
+ list_add_tail(&pt->child_list, &obj->child_list_head);
+ spin_unlock_irqrestore(&obj->child_list_lock, flags);
+}
+
+static void sync_timeline_remove_pt(struct sync_pt *pt)
+{
+ struct sync_timeline *obj = pt->parent;
+ unsigned long flags;
+
+ spin_lock_irqsave(&obj->active_list_lock, flags);
+ if (!list_empty(&pt->active_list))
+ list_del_init(&pt->active_list);
+ spin_unlock_irqrestore(&obj->active_list_lock, flags);
+
+ spin_lock_irqsave(&obj->child_list_lock, flags);
+ if (!list_empty(&pt->child_list))
+ list_del_init(&pt->child_list);
+
+ spin_unlock_irqrestore(&obj->child_list_lock, flags);
+}
+
+void sync_timeline_signal(struct sync_timeline *obj)
+{
+ unsigned long flags;
+ LIST_HEAD(signaled_pts);
+ struct list_head *pos, *n;
+
+ trace_sync_timeline(obj);
+
+ spin_lock_irqsave(&obj->active_list_lock, flags);
+
+ list_for_each_safe(pos, n, &obj->active_list_head) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, active_list);
+
+ if (_sync_pt_has_signaled(pt)) {
+ list_del_init(pos);
+ list_add(&pt->signaled_list, &signaled_pts);
+ kref_get(&pt->fence->kref);
+ }
+ }
+
+ spin_unlock_irqrestore(&obj->active_list_lock, flags);
+
+ list_for_each_safe(pos, n, &signaled_pts) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, signaled_list);
+
+ list_del_init(pos);
+ sync_fence_signal_pt(pt);
+ kref_put(&pt->fence->kref, sync_fence_free);
+ }
+}
+EXPORT_SYMBOL(sync_timeline_signal);
+
+struct sync_pt *sync_pt_create(struct sync_timeline *parent, int size)
+{
+ struct sync_pt *pt;
+
+ if (size < sizeof(struct sync_pt))
+ return NULL;
+
+ pt = kzalloc(size, GFP_KERNEL);
+ if (pt == NULL)
+ return NULL;
+
+ INIT_LIST_HEAD(&pt->active_list);
+ kref_get(&parent->kref);
+ sync_timeline_add_pt(parent, pt);
+
+ return pt;
+}
+EXPORT_SYMBOL(sync_pt_create);
+
+void sync_pt_free(struct sync_pt *pt)
+{
+ if (pt->parent->ops->free_pt)
+ pt->parent->ops->free_pt(pt);
+
+ sync_timeline_remove_pt(pt);
+
+ kref_put(&pt->parent->kref, sync_timeline_free);
+
+ kfree(pt);
+}
+EXPORT_SYMBOL(sync_pt_free);
+
+/* call with pt->parent->active_list_lock held */
+static int _sync_pt_has_signaled(struct sync_pt *pt)
+{
+ int old_status = pt->status;
+
+ if (!pt->status)
+ pt->status = pt->parent->ops->has_signaled(pt);
+
+ if (!pt->status && pt->parent->destroyed)
+ pt->status = -ENOENT;
+
+ if (pt->status != old_status)
+ pt->timestamp = ktime_get();
+
+ return pt->status;
+}
+
+static struct sync_pt *sync_pt_dup(struct sync_pt *pt)
+{
+ return pt->parent->ops->dup(pt);
+}
+
+/* Adds a sync pt to the active queue. Called when added to a fence */
+static void sync_pt_activate(struct sync_pt *pt)
+{
+ struct sync_timeline *obj = pt->parent;
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&obj->active_list_lock, flags);
+
+ err = _sync_pt_has_signaled(pt);
+ if (err != 0)
+ goto out;
+
+ list_add_tail(&pt->active_list, &obj->active_list_head);
+
+out:
+ spin_unlock_irqrestore(&obj->active_list_lock, flags);
+}
+
+static int sync_fence_release(struct inode *inode, struct file *file);
+static unsigned int sync_fence_poll(struct file *file, poll_table *wait);
+static long sync_fence_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+
+
+static const struct file_operations sync_fence_fops = {
+ .release = sync_fence_release,
+ .poll = sync_fence_poll,
+ .unlocked_ioctl = sync_fence_ioctl,
+ .compat_ioctl = sync_fence_ioctl,
+};
+
+static struct sync_fence *sync_fence_alloc(const char *name)
+{
+ struct sync_fence *fence;
+ unsigned long flags;
+
+ fence = kzalloc(sizeof(struct sync_fence), GFP_KERNEL);
+ if (fence == NULL)
+ return NULL;
+
+ fence->file = anon_inode_getfile("sync_fence", &sync_fence_fops,
+ fence, 0);
+ if (IS_ERR(fence->file))
+ goto err;
+
+ kref_init(&fence->kref);
+ strlcpy(fence->name, name, sizeof(fence->name));
+
+ INIT_LIST_HEAD(&fence->pt_list_head);
+ INIT_LIST_HEAD(&fence->waiter_list_head);
+ spin_lock_init(&fence->waiter_list_lock);
+
+ init_waitqueue_head(&fence->wq);
+
+ spin_lock_irqsave(&sync_fence_list_lock, flags);
+ list_add_tail(&fence->sync_fence_list, &sync_fence_list_head);
+ spin_unlock_irqrestore(&sync_fence_list_lock, flags);
+
+ return fence;
+
+err:
+ kfree(fence);
+ return NULL;
+}
+
+/* TODO: implement a create which takes more that one sync_pt */
+struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt)
+{
+ struct sync_fence *fence;
+
+ if (pt->fence)
+ return NULL;
+
+ fence = sync_fence_alloc(name);
+ if (fence == NULL)
+ return NULL;
+
+ pt->fence = fence;
+ list_add(&pt->pt_list, &fence->pt_list_head);
+ sync_pt_activate(pt);
+
+ /*
+ * signal the fence in case pt was activated before
+ * sync_pt_activate(pt) was called
+ */
+ sync_fence_signal_pt(pt);
+
+ return fence;
+}
+EXPORT_SYMBOL(sync_fence_create);
+
+static int sync_fence_copy_pts(struct sync_fence *dst, struct sync_fence *src)
+{
+ struct list_head *pos;
+
+ list_for_each(pos, &src->pt_list_head) {
+ struct sync_pt *orig_pt =
+ container_of(pos, struct sync_pt, pt_list);
+ struct sync_pt *new_pt = sync_pt_dup(orig_pt);
+
+ if (new_pt == NULL)
+ return -ENOMEM;
+
+ new_pt->fence = dst;
+ list_add(&new_pt->pt_list, &dst->pt_list_head);
+ }
+
+ return 0;
+}
+
+static int sync_fence_merge_pts(struct sync_fence *dst, struct sync_fence *src)
+{
+ struct list_head *src_pos, *dst_pos, *n;
+
+ list_for_each(src_pos, &src->pt_list_head) {
+ struct sync_pt *src_pt =
+ container_of(src_pos, struct sync_pt, pt_list);
+ bool collapsed = false;
+
+ list_for_each_safe(dst_pos, n, &dst->pt_list_head) {
+ struct sync_pt *dst_pt =
+ container_of(dst_pos, struct sync_pt, pt_list);
+ /* collapse two sync_pts on the same timeline
+ * to a single sync_pt that will signal at
+ * the later of the two
+ */
+ if (dst_pt->parent == src_pt->parent) {
+ if (dst_pt->parent->ops->compare(dst_pt, src_pt)
+ == -1) {
+ struct sync_pt *new_pt =
+ sync_pt_dup(src_pt);
+ if (new_pt == NULL)
+ return -ENOMEM;
+
+ new_pt->fence = dst;
+ list_replace(&dst_pt->pt_list,
+ &new_pt->pt_list);
+ sync_pt_free(dst_pt);
+ }
+ collapsed = true;
+ break;
+ }
+ }
+
+ if (!collapsed) {
+ struct sync_pt *new_pt = sync_pt_dup(src_pt);
+
+ if (new_pt == NULL)
+ return -ENOMEM;
+
+ new_pt->fence = dst;
+ list_add(&new_pt->pt_list, &dst->pt_list_head);
+ }
+ }
+
+ return 0;
+}
+
+static void sync_fence_detach_pts(struct sync_fence *fence)
+{
+ struct list_head *pos, *n;
+
+ list_for_each_safe(pos, n, &fence->pt_list_head) {
+ struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);
+
+ sync_timeline_remove_pt(pt);
+ }
+}
+
+static void sync_fence_free_pts(struct sync_fence *fence)
+{
+ struct list_head *pos, *n;
+
+ list_for_each_safe(pos, n, &fence->pt_list_head) {
+ struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);
+
+ sync_pt_free(pt);
+ }
+}
+
+struct sync_fence *sync_fence_fdget(int fd)
+{
+ struct file *file = fget(fd);
+
+ if (file == NULL)
+ return NULL;
+
+ if (file->f_op != &sync_fence_fops)
+ goto err;
+
+ return file->private_data;
+
+err:
+ fput(file);
+ return NULL;
+}
+EXPORT_SYMBOL(sync_fence_fdget);
+
+void sync_fence_put(struct sync_fence *fence)
+{
+ fput(fence->file);
+}
+EXPORT_SYMBOL(sync_fence_put);
+
+void sync_fence_install(struct sync_fence *fence, int fd)
+{
+ fd_install(fd, fence->file);
+}
+EXPORT_SYMBOL(sync_fence_install);
+
+static int sync_fence_get_status(struct sync_fence *fence)
+{
+ struct list_head *pos;
+ int status = 1;
+
+ list_for_each(pos, &fence->pt_list_head) {
+ struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);
+ int pt_status = pt->status;
+
+ if (pt_status < 0) {
+ status = pt_status;
+ break;
+ } else if (status == 1) {
+ status = pt_status;
+ }
+ }
+
+ return status;
+}
+
+struct sync_fence *sync_fence_merge(const char *name,
+ struct sync_fence *a, struct sync_fence *b)
+{
+ struct sync_fence *fence;
+ struct list_head *pos;
+ int err;
+
+ fence = sync_fence_alloc(name);
+ if (fence == NULL)
+ return NULL;
+
+ err = sync_fence_copy_pts(fence, a);
+ if (err < 0)
+ goto err;
+
+ err = sync_fence_merge_pts(fence, b);
+ if (err < 0)
+ goto err;
+
+ list_for_each(pos, &fence->pt_list_head) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, pt_list);
+ sync_pt_activate(pt);
+ }
+
+ /*
+ * signal the fence in case one of it's pts were activated before
+ * they were activated
+ */
+ sync_fence_signal_pt(list_first_entry(&fence->pt_list_head,
+ struct sync_pt,
+ pt_list));
+
+ return fence;
+err:
+ sync_fence_free_pts(fence);
+ kfree(fence);
+ return NULL;
+}
+EXPORT_SYMBOL(sync_fence_merge);
+
+static void sync_fence_signal_pt(struct sync_pt *pt)
+{
+ LIST_HEAD(signaled_waiters);
+ struct sync_fence *fence = pt->fence;
+ struct list_head *pos;
+ struct list_head *n;
+ unsigned long flags;
+ int status;
+
+ status = sync_fence_get_status(fence);
+
+ spin_lock_irqsave(&fence->waiter_list_lock, flags);
+ /*
+ * this should protect against two threads racing on the signaled
+ * false -> true transition
+ */
+ if (status && !fence->status) {
+ list_for_each_safe(pos, n, &fence->waiter_list_head)
+ list_move(pos, &signaled_waiters);
+
+ fence->status = status;
+ } else {
+ status = 0;
+ }
+ spin_unlock_irqrestore(&fence->waiter_list_lock, flags);
+
+ if (status) {
+ list_for_each_safe(pos, n, &signaled_waiters) {
+ struct sync_fence_waiter *waiter =
+ container_of(pos, struct sync_fence_waiter,
+ waiter_list);
+
+ list_del(pos);
+ waiter->callback(fence, waiter);
+ }
+ wake_up(&fence->wq);
+ }
+}
+
+int sync_fence_wait_async(struct sync_fence *fence,
+ struct sync_fence_waiter *waiter)
+{
+ unsigned long flags;
+ int err = 0;
+
+ spin_lock_irqsave(&fence->waiter_list_lock, flags);
+
+ if (fence->status) {
+ err = fence->status;
+ goto out;
+ }
+
+ list_add_tail(&waiter->waiter_list, &fence->waiter_list_head);
+out:
+ spin_unlock_irqrestore(&fence->waiter_list_lock, flags);
+
+ return err;
+}
+EXPORT_SYMBOL(sync_fence_wait_async);
+
+int sync_fence_cancel_async(struct sync_fence *fence,
+ struct sync_fence_waiter *waiter)
+{
+ struct list_head *pos;
+ struct list_head *n;
+ unsigned long flags;
+ int ret = -ENOENT;
+
+ spin_lock_irqsave(&fence->waiter_list_lock, flags);
+ /*
+ * Make sure waiter is still in waiter_list because it is possible for
+ * the waiter to be removed from the list while the callback is still
+ * pending.
+ */
+ list_for_each_safe(pos, n, &fence->waiter_list_head) {
+ struct sync_fence_waiter *list_waiter =
+ container_of(pos, struct sync_fence_waiter,
+ waiter_list);
+ if (list_waiter == waiter) {
+ list_del(pos);
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&fence->waiter_list_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL(sync_fence_cancel_async);
+
+static bool sync_fence_check(struct sync_fence *fence)
+{
+ /*
+ * Make sure that reads to fence->status are ordered with the
+ * wait queue event triggering
+ */
+ smp_rmb();
+ return fence->status != 0;
+}
+
+int sync_fence_wait(struct sync_fence *fence, long timeout)
+{
+ int err = 0;
+ struct sync_pt *pt;
+
+ trace_sync_wait(fence, 1);
+ list_for_each_entry(pt, &fence->pt_list_head, pt_list)
+ trace_sync_pt(pt);
+
+ if (timeout > 0) {
+ timeout = msecs_to_jiffies(timeout);
+ err = wait_event_interruptible_timeout(fence->wq,
+ sync_fence_check(fence),
+ timeout);
+ } else if (timeout < 0) {
+ err = wait_event_interruptible(fence->wq,
+ sync_fence_check(fence));
+ }
+ trace_sync_wait(fence, 0);
+
+ if (err < 0)
+ return err;
+
+ if (fence->status < 0) {
+ pr_info("fence error %d on [%p]\n", fence->status, fence);
+ sync_dump();
+ return fence->status;
+ }
+
+ if (fence->status == 0) {
+ if (timeout > 0) {
+ pr_info("fence timeout on [%p] after %dms\n", fence,
+ jiffies_to_msecs(timeout));
+ sync_dump();
+ }
+ return -ETIME;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sync_fence_wait);
+
+static void sync_fence_free(struct kref *kref)
+{
+ struct sync_fence *fence = container_of(kref, struct sync_fence, kref);
+
+ sync_fence_free_pts(fence);
+
+ kfree(fence);
+}
+
+static int sync_fence_release(struct inode *inode, struct file *file)
+{
+ struct sync_fence *fence = file->private_data;
+ unsigned long flags;
+
+ /*
+ * We need to remove all ways to access this fence before droping
+ * our ref.
+ *
+ * start with its membership in the global fence list
+ */
+ spin_lock_irqsave(&sync_fence_list_lock, flags);
+ list_del(&fence->sync_fence_list);
+ spin_unlock_irqrestore(&sync_fence_list_lock, flags);
+
+ /*
+ * remove its pts from their parents so that sync_timeline_signal()
+ * can't reference the fence.
+ */
+ sync_fence_detach_pts(fence);
+
+ kref_put(&fence->kref, sync_fence_free);
+
+ return 0;
+}
+
+static unsigned int sync_fence_poll(struct file *file, poll_table *wait)
+{
+ struct sync_fence *fence = file->private_data;
+
+ poll_wait(file, &fence->wq, wait);
+
+ /*
+ * Make sure that reads to fence->status are ordered with the
+ * wait queue event triggering
+ */
+ smp_rmb();
+
+ if (fence->status == 1)
+ return POLLIN;
+ else if (fence->status < 0)
+ return POLLERR;
+ else
+ return 0;
+}
+
+static long sync_fence_ioctl_wait(struct sync_fence *fence, unsigned long arg)
+{
+ __s32 value;
+
+ if (copy_from_user(&value, (void __user *)arg, sizeof(value)))
+ return -EFAULT;
+
+ return sync_fence_wait(fence, value);
+}
+
+static long sync_fence_ioctl_merge(struct sync_fence *fence, unsigned long arg)
+{
+ int fd = get_unused_fd_flags(O_CLOEXEC);
+ int err;
+ struct sync_fence *fence2, *fence3;
+ struct sync_merge_data data;
+
+ if (fd < 0)
+ return fd;
+
+ if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
+ err = -EFAULT;
+ goto err_put_fd;
+ }
+
+ fence2 = sync_fence_fdget(data.fd2);
+ if (fence2 == NULL) {
+ err = -ENOENT;
+ goto err_put_fd;
+ }
+
+ data.name[sizeof(data.name) - 1] = '\0';
+ fence3 = sync_fence_merge(data.name, fence, fence2);
+ if (fence3 == NULL) {
+ err = -ENOMEM;
+ goto err_put_fence2;
+ }
+
+ data.fence = fd;
+ if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
+ err = -EFAULT;
+ goto err_put_fence3;
+ }
+
+ sync_fence_install(fence3, fd);
+ sync_fence_put(fence2);
+ return 0;
+
+err_put_fence3:
+ sync_fence_put(fence3);
+
+err_put_fence2:
+ sync_fence_put(fence2);
+
+err_put_fd:
+ put_unused_fd(fd);
+ return err;
+}
+
+static int sync_fill_pt_info(struct sync_pt *pt, void *data, int size)
+{
+ struct sync_pt_info *info = data;
+ int ret;
+
+ if (size < sizeof(struct sync_pt_info))
+ return -ENOMEM;
+
+ info->len = sizeof(struct sync_pt_info);
+
+ if (pt->parent->ops->fill_driver_data) {
+ ret = pt->parent->ops->fill_driver_data(pt, info->driver_data,
+ size - sizeof(*info));
+ if (ret < 0)
+ return ret;
+
+ info->len += ret;
+ }
+
+ strlcpy(info->obj_name, pt->parent->name, sizeof(info->obj_name));
+ strlcpy(info->driver_name, pt->parent->ops->driver_name,
+ sizeof(info->driver_name));
+ info->status = pt->status;
+ info->timestamp_ns = ktime_to_ns(pt->timestamp);
+
+ return info->len;
+}
+
+static long sync_fence_ioctl_fence_info(struct sync_fence *fence,
+ unsigned long arg)
+{
+ struct sync_fence_info_data *data;
+ struct list_head *pos;
+ __u32 size;
+ __u32 len = 0;
+ int ret;
+
+ if (copy_from_user(&size, (void __user *)arg, sizeof(size)))
+ return -EFAULT;
+
+ if (size < sizeof(struct sync_fence_info_data))
+ return -EINVAL;
+
+ if (size > 4096)
+ size = 4096;
+
+ data = kzalloc(size, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ strlcpy(data->name, fence->name, sizeof(data->name));
+ data->status = fence->status;
+ len = sizeof(struct sync_fence_info_data);
+
+ list_for_each(pos, &fence->pt_list_head) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, pt_list);
+
+ ret = sync_fill_pt_info(pt, (u8 *)data + len, size - len);
+
+ if (ret < 0)
+ goto out;
+
+ len += ret;
+ }
+
+ data->len = len;
+
+ if (copy_to_user((void __user *)arg, data, len))
+ ret = -EFAULT;
+ else
+ ret = 0;
+
+out:
+ kfree(data);
+
+ return ret;
+}
+
+static long sync_fence_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sync_fence *fence = file->private_data;
+
+ switch (cmd) {
+ case SYNC_IOC_WAIT:
+ return sync_fence_ioctl_wait(fence, arg);
+
+ case SYNC_IOC_MERGE:
+ return sync_fence_ioctl_merge(fence, arg);
+
+ case SYNC_IOC_FENCE_INFO:
+ return sync_fence_ioctl_fence_info(fence, arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+#ifdef CONFIG_DEBUG_FS
+static const char *sync_status_str(int status)
+{
+ if (status > 0)
+ return "signaled";
+ else if (status == 0)
+ return "active";
+ else
+ return "error";
+}
+
+static void sync_print_pt(struct seq_file *s, struct sync_pt *pt, bool fence)
+{
+ int status = pt->status;
+
+ seq_printf(s, " %s%spt %s",
+ fence ? pt->parent->name : "",
+ fence ? "_" : "",
+ sync_status_str(status));
+ if (pt->status) {
+ struct timeval tv = ktime_to_timeval(pt->timestamp);
+
+ seq_printf(s, "@%ld.%06ld", tv.tv_sec, tv.tv_usec);
+ }
+
+ if (pt->parent->ops->timeline_value_str &&
+ pt->parent->ops->pt_value_str) {
+ char value[64];
+
+ pt->parent->ops->pt_value_str(pt, value, sizeof(value));
+ seq_printf(s, ": %s", value);
+ if (fence) {
+ pt->parent->ops->timeline_value_str(pt->parent, value,
+ sizeof(value));
+ seq_printf(s, " / %s", value);
+ }
+ } else if (pt->parent->ops->print_pt) {
+ seq_puts(s, ": ");
+ pt->parent->ops->print_pt(s, pt);
+ }
+
+ seq_puts(s, "\n");
+}
+
+static void sync_print_obj(struct seq_file *s, struct sync_timeline *obj)
+{
+ struct list_head *pos;
+ unsigned long flags;
+
+ seq_printf(s, "%s %s", obj->name, obj->ops->driver_name);
+
+ if (obj->ops->timeline_value_str) {
+ char value[64];
+
+ obj->ops->timeline_value_str(obj, value, sizeof(value));
+ seq_printf(s, ": %s", value);
+ } else if (obj->ops->print_obj) {
+ seq_puts(s, ": ");
+ obj->ops->print_obj(s, obj);
+ }
+
+ seq_puts(s, "\n");
+
+ spin_lock_irqsave(&obj->child_list_lock, flags);
+ list_for_each(pos, &obj->child_list_head) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, child_list);
+ sync_print_pt(s, pt, false);
+ }
+ spin_unlock_irqrestore(&obj->child_list_lock, flags);
+}
+
+static void sync_print_fence(struct seq_file *s, struct sync_fence *fence)
+{
+ struct list_head *pos;
+ unsigned long flags;
+
+ seq_printf(s, "[%p] %s: %s\n", fence, fence->name,
+ sync_status_str(fence->status));
+
+ list_for_each(pos, &fence->pt_list_head) {
+ struct sync_pt *pt =
+ container_of(pos, struct sync_pt, pt_list);
+ sync_print_pt(s, pt, true);
+ }
+
+ spin_lock_irqsave(&fence->waiter_list_lock, flags);
+ list_for_each(pos, &fence->waiter_list_head) {
+ struct sync_fence_waiter *waiter =
+ container_of(pos, struct sync_fence_waiter,
+ waiter_list);
+
+ seq_printf(s, "waiter %pF\n", waiter->callback);
+ }
+ spin_unlock_irqrestore(&fence->waiter_list_lock, flags);
+}
+
+static int sync_debugfs_show(struct seq_file *s, void *unused)
+{
+ unsigned long flags;
+ struct list_head *pos;
+
+ seq_puts(s, "objs:\n--------------\n");
+
+ spin_lock_irqsave(&sync_timeline_list_lock, flags);
+ list_for_each(pos, &sync_timeline_list_head) {
+ struct sync_timeline *obj =
+ container_of(pos, struct sync_timeline,
+ sync_timeline_list);
+
+ sync_print_obj(s, obj);
+ seq_puts(s, "\n");
+ }
+ spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
+
+ seq_puts(s, "fences:\n--------------\n");
+
+ spin_lock_irqsave(&sync_fence_list_lock, flags);
+ list_for_each(pos, &sync_fence_list_head) {
+ struct sync_fence *fence =
+ container_of(pos, struct sync_fence, sync_fence_list);
+
+ sync_print_fence(s, fence);
+ seq_puts(s, "\n");
+ }
+ spin_unlock_irqrestore(&sync_fence_list_lock, flags);
+ return 0;
+}
+
+static int sync_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sync_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations sync_debugfs_fops = {
+ .open = sync_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static __init int sync_debugfs_init(void)
+{
+ debugfs_create_file("sync", S_IRUGO, NULL, NULL, &sync_debugfs_fops);
+ return 0;
+}
+late_initcall(sync_debugfs_init);
+
+#define DUMP_CHUNK 256
+static char sync_dump_buf[64 * 1024];
+static void sync_dump(void)
+{
+ struct seq_file s = {
+ .buf = sync_dump_buf,
+ .size = sizeof(sync_dump_buf) - 1,
+ };
+ int i;
+
+ sync_debugfs_show(&s, NULL);
+
+ for (i = 0; i < s.count; i += DUMP_CHUNK) {
+ if ((s.count - i) > DUMP_CHUNK) {
+ char c = s.buf[i + DUMP_CHUNK];
+
+ s.buf[i + DUMP_CHUNK] = 0;
+ pr_cont("%s", s.buf + i);
+ s.buf[i + DUMP_CHUNK] = c;
+ } else {
+ s.buf[s.count] = 0;
+ pr_cont("%s", s.buf + i);
+ }
+ }
+}
+#else
+static void sync_dump(void)
+{
+}
+#endif
diff --git a/drivers/staging/android/sync.h b/drivers/staging/android/sync.h
new file mode 100644
index 00000000000..eaf57cccf62
--- /dev/null
+++ b/drivers/staging/android/sync.h
@@ -0,0 +1,344 @@
+/*
+ * include/linux/sync.h
+ *
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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_SYNC_H
+#define _LINUX_SYNC_H
+
+#include <linux/types.h>
+#include <linux/kref.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+
+#include "uapi/sync.h"
+
+struct sync_timeline;
+struct sync_pt;
+struct sync_fence;
+
+/**
+ * struct sync_timeline_ops - sync object implementation ops
+ * @driver_name: name of the implementation
+ * @dup: duplicate a sync_pt
+ * @has_signaled: returns:
+ * 1 if pt has signaled
+ * 0 if pt has not signaled
+ * <0 on error
+ * @compare: returns:
+ * 1 if b will signal before a
+ * 0 if a and b will signal at the same time
+ * -1 if a will signal before b
+ * @free_pt: called before sync_pt is freed
+ * @release_obj: called before sync_timeline is freed
+ * @print_obj: deprecated
+ * @print_pt: deprecated
+ * @fill_driver_data: write implementation specific driver data to data.
+ * should return an error if there is not enough room
+ * as specified by size. This information is returned
+ * to userspace by SYNC_IOC_FENCE_INFO.
+ * @timeline_value_str: fill str with the value of the sync_timeline's counter
+ * @pt_value_str: fill str with the value of the sync_pt
+ */
+struct sync_timeline_ops {
+ const char *driver_name;
+
+ /* required */
+ struct sync_pt * (*dup)(struct sync_pt *pt);
+
+ /* required */
+ int (*has_signaled)(struct sync_pt *pt);
+
+ /* required */
+ int (*compare)(struct sync_pt *a, struct sync_pt *b);
+
+ /* optional */
+ void (*free_pt)(struct sync_pt *sync_pt);
+
+ /* optional */
+ void (*release_obj)(struct sync_timeline *sync_timeline);
+
+ /* deprecated */
+ void (*print_obj)(struct seq_file *s,
+ struct sync_timeline *sync_timeline);
+
+ /* deprecated */
+ void (*print_pt)(struct seq_file *s, struct sync_pt *sync_pt);
+
+ /* optional */
+ int (*fill_driver_data)(struct sync_pt *syncpt, void *data, int size);
+
+ /* optional */
+ void (*timeline_value_str)(struct sync_timeline *timeline, char *str,
+ int size);
+
+ /* optional */
+ void (*pt_value_str)(struct sync_pt *pt, char *str, int size);
+};
+
+/**
+ * struct sync_timeline - sync object
+ * @kref: reference count on fence.
+ * @ops: ops that define the implementation of the sync_timeline
+ * @name: name of the sync_timeline. Useful for debugging
+ * @destroyed: set when sync_timeline is destroyed
+ * @child_list_head: list of children sync_pts for this sync_timeline
+ * @child_list_lock: lock protecting @child_list_head, destroyed, and
+ * sync_pt.status
+ * @active_list_head: list of active (unsignaled/errored) sync_pts
+ * @sync_timeline_list: membership in global sync_timeline_list
+ */
+struct sync_timeline {
+ struct kref kref;
+ const struct sync_timeline_ops *ops;
+ char name[32];
+
+ /* protected by child_list_lock */
+ bool destroyed;
+
+ struct list_head child_list_head;
+ spinlock_t child_list_lock;
+
+ struct list_head active_list_head;
+ spinlock_t active_list_lock;
+
+ struct list_head sync_timeline_list;
+};
+
+/**
+ * struct sync_pt - sync point
+ * @parent: sync_timeline to which this sync_pt belongs
+ * @child_list: membership in sync_timeline.child_list_head
+ * @active_list: membership in sync_timeline.active_list_head
+ * @signaled_list: membership in temporary signaled_list on stack
+ * @fence: sync_fence to which the sync_pt belongs
+ * @pt_list: membership in sync_fence.pt_list_head
+ * @status: 1: signaled, 0:active, <0: error
+ * @timestamp: time which sync_pt status transitioned from active to
+ * signaled or error.
+ */
+struct sync_pt {
+ struct sync_timeline *parent;
+ struct list_head child_list;
+
+ struct list_head active_list;
+ struct list_head signaled_list;
+
+ struct sync_fence *fence;
+ struct list_head pt_list;
+
+ /* protected by parent->active_list_lock */
+ int status;
+
+ ktime_t timestamp;
+};
+
+/**
+ * struct sync_fence - sync fence
+ * @file: file representing this fence
+ * @kref: reference count on fence.
+ * @name: name of sync_fence. Useful for debugging
+ * @pt_list_head: list of sync_pts in the fence. immutable once fence
+ * is created
+ * @waiter_list_head: list of asynchronous waiters on this fence
+ * @waiter_list_lock: lock protecting @waiter_list_head and @status
+ * @status: 1: signaled, 0:active, <0: error
+ *
+ * @wq: wait queue for fence signaling
+ * @sync_fence_list: membership in global fence list
+ */
+struct sync_fence {
+ struct file *file;
+ struct kref kref;
+ char name[32];
+
+ /* this list is immutable once the fence is created */
+ struct list_head pt_list_head;
+
+ struct list_head waiter_list_head;
+ spinlock_t waiter_list_lock; /* also protects status */
+ int status;
+
+ wait_queue_head_t wq;
+
+ struct list_head sync_fence_list;
+};
+
+struct sync_fence_waiter;
+typedef void (*sync_callback_t)(struct sync_fence *fence,
+ struct sync_fence_waiter *waiter);
+
+/**
+ * struct sync_fence_waiter - metadata for asynchronous waiter on a fence
+ * @waiter_list: membership in sync_fence.waiter_list_head
+ * @callback: function pointer to call when fence signals
+ * @callback_data: pointer to pass to @callback
+ */
+struct sync_fence_waiter {
+ struct list_head waiter_list;
+
+ sync_callback_t callback;
+};
+
+static inline void sync_fence_waiter_init(struct sync_fence_waiter *waiter,
+ sync_callback_t callback)
+{
+ waiter->callback = callback;
+}
+
+/*
+ * API for sync_timeline implementers
+ */
+
+/**
+ * sync_timeline_create() - creates a sync object
+ * @ops: specifies the implementation ops for the object
+ * @size: size to allocate for this obj
+ * @name: sync_timeline name
+ *
+ * Creates a new sync_timeline which will use the implementation specified by
+ * @ops. @size bytes will be allocated allowing for implementation specific
+ * data to be kept after the generic sync_timeline struct.
+ */
+struct sync_timeline *sync_timeline_create(const struct sync_timeline_ops *ops,
+ int size, const char *name);
+
+/**
+ * sync_timeline_destroy() - destroys a sync object
+ * @obj: sync_timeline to destroy
+ *
+ * A sync implementation should call this when the @obj is going away
+ * (i.e. module unload.) @obj won't actually be freed until all its children
+ * sync_pts are freed.
+ */
+void sync_timeline_destroy(struct sync_timeline *obj);
+
+/**
+ * sync_timeline_signal() - signal a status change on a sync_timeline
+ * @obj: sync_timeline to signal
+ *
+ * A sync implementation should call this any time one of it's sync_pts
+ * has signaled or has an error condition.
+ */
+void sync_timeline_signal(struct sync_timeline *obj);
+
+/**
+ * sync_pt_create() - creates a sync pt
+ * @parent: sync_pt's parent sync_timeline
+ * @size: size to allocate for this pt
+ *
+ * Creates a new sync_pt as a child of @parent. @size bytes will be
+ * allocated allowing for implementation specific data to be kept after
+ * the generic sync_timeline struct.
+ */
+struct sync_pt *sync_pt_create(struct sync_timeline *parent, int size);
+
+/**
+ * sync_pt_free() - frees a sync pt
+ * @pt: sync_pt to free
+ *
+ * This should only be called on sync_pts which have been created but
+ * not added to a fence.
+ */
+void sync_pt_free(struct sync_pt *pt);
+
+/**
+ * sync_fence_create() - creates a sync fence
+ * @name: name of fence to create
+ * @pt: sync_pt to add to the fence
+ *
+ * Creates a fence containg @pt. Once this is called, the fence takes
+ * ownership of @pt.
+ */
+struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt);
+
+/*
+ * API for sync_fence consumers
+ */
+
+/**
+ * sync_fence_merge() - merge two fences
+ * @name: name of new fence
+ * @a: fence a
+ * @b: fence b
+ *
+ * Creates a new fence which contains copies of all the sync_pts in both
+ * @a and @b. @a and @b remain valid, independent fences.
+ */
+struct sync_fence *sync_fence_merge(const char *name,
+ struct sync_fence *a, struct sync_fence *b);
+
+/**
+ * sync_fence_fdget() - get a fence from an fd
+ * @fd: fd referencing a fence
+ *
+ * Ensures @fd references a valid fence, increments the refcount of the backing
+ * file, and returns the fence.
+ */
+struct sync_fence *sync_fence_fdget(int fd);
+
+/**
+ * sync_fence_put() - puts a reference of a sync fence
+ * @fence: fence to put
+ *
+ * Puts a reference on @fence. If this is the last reference, the fence and
+ * all it's sync_pts will be freed
+ */
+void sync_fence_put(struct sync_fence *fence);
+
+/**
+ * sync_fence_install() - installs a fence into a file descriptor
+ * @fence: fence to install
+ * @fd: file descriptor in which to install the fence
+ *
+ * Installs @fence into @fd. @fd's should be acquired through get_unused_fd().
+ */
+void sync_fence_install(struct sync_fence *fence, int fd);
+
+/**
+ * sync_fence_wait_async() - registers and async wait on the fence
+ * @fence: fence to wait on
+ * @waiter: waiter callback struck
+ *
+ * Returns 1 if @fence has already signaled.
+ *
+ * Registers a callback to be called when @fence signals or has an error.
+ * @waiter should be initialized with sync_fence_waiter_init().
+ */
+int sync_fence_wait_async(struct sync_fence *fence,
+ struct sync_fence_waiter *waiter);
+
+/**
+ * sync_fence_cancel_async() - cancels an async wait
+ * @fence: fence to wait on
+ * @waiter: waiter callback struck
+ *
+ * returns 0 if waiter was removed from fence's async waiter list.
+ * returns -ENOENT if waiter was not found on fence's async waiter list.
+ *
+ * Cancels a previously registered async wait. Will fail gracefully if
+ * @waiter was never registered or if @fence has already signaled @waiter.
+ */
+int sync_fence_cancel_async(struct sync_fence *fence,
+ struct sync_fence_waiter *waiter);
+
+/**
+ * sync_fence_wait() - wait on fence
+ * @fence: fence to wait on
+ * @tiemout: timeout in ms
+ *
+ * Wait for @fence to be signaled or have an error. Waits indefinitely
+ * if @timeout < 0
+ */
+int sync_fence_wait(struct sync_fence *fence, long timeout);
+
+#endif /* _LINUX_SYNC_H */
diff --git a/drivers/staging/android/timed_gpio.c b/drivers/staging/android/timed_gpio.c
new file mode 100644
index 00000000000..180c209a009
--- /dev/null
+++ b/drivers/staging/android/timed_gpio.c
@@ -0,0 +1,167 @@
+/* drivers/misc/timed_gpio.c
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+
+#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 = devm_kzalloc(&pdev->dev,
+ 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);
+ }
+
+ 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);
+ }
+
+ 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,
+ },
+};
+
+module_platform_driver(timed_gpio_driver);
+
+MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
+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 00000000000..d29e169d7eb
--- /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 00000000000..c341ac11c5a
--- /dev/null
+++ b/drivers/staging/android/timed_output.c
@@ -0,0 +1,118 @@
+/* drivers/misc/timed_output.c
+ *
+ * Copyright (C) 2009 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) "timed_output: " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+
+#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_RW(enable);
+
+static struct attribute *timed_output_attrs[] = {
+ &dev_attr_enable.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(timed_output);
+
+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);
+ timed_output_class->dev_groups = timed_output_groups;
+ }
+
+ 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, "%s", tdev->name);
+ if (IS_ERR(tdev->dev))
+ return PTR_ERR(tdev->dev);
+
+ dev_set_drvdata(tdev->dev, tdev);
+ tdev->state = 0;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(timed_output_dev_register);
+
+void timed_output_dev_unregister(struct timed_output_dev *tdev)
+{
+ tdev->enable(tdev, 0);
+ device_destroy(timed_output_class, MKDEV(0, tdev->index));
+}
+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 <lockwood@android.com>");
+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 00000000000..13d2ca51cbe
--- /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;
+};
+
+int timed_output_dev_register(struct timed_output_dev *dev);
+void timed_output_dev_unregister(struct timed_output_dev *dev);
+
+#endif
diff --git a/drivers/staging/android/trace/sync.h b/drivers/staging/android/trace/sync.h
new file mode 100644
index 00000000000..95462359ba5
--- /dev/null
+++ b/drivers/staging/android/trace/sync.h
@@ -0,0 +1,82 @@
+#undef TRACE_SYSTEM
+#define TRACE_INCLUDE_PATH ../../drivers/staging/android/trace
+#define TRACE_SYSTEM sync
+
+#if !defined(_TRACE_SYNC_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SYNC_H
+
+#include "../sync.h"
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(sync_timeline,
+ TP_PROTO(struct sync_timeline *timeline),
+
+ TP_ARGS(timeline),
+
+ TP_STRUCT__entry(
+ __string(name, timeline->name)
+ __array(char, value, 32)
+ ),
+
+ TP_fast_assign(
+ __assign_str(name, timeline->name);
+ if (timeline->ops->timeline_value_str) {
+ timeline->ops->timeline_value_str(timeline,
+ __entry->value,
+ sizeof(__entry->value));
+ } else {
+ __entry->value[0] = '\0';
+ }
+ ),
+
+ TP_printk("name=%s value=%s", __get_str(name), __entry->value)
+);
+
+TRACE_EVENT(sync_wait,
+ TP_PROTO(struct sync_fence *fence, int begin),
+
+ TP_ARGS(fence, begin),
+
+ TP_STRUCT__entry(
+ __string(name, fence->name)
+ __field(s32, status)
+ __field(u32, begin)
+ ),
+
+ TP_fast_assign(
+ __assign_str(name, fence->name);
+ __entry->status = fence->status;
+ __entry->begin = begin;
+ ),
+
+ TP_printk("%s name=%s state=%d", __entry->begin ? "begin" : "end",
+ __get_str(name), __entry->status)
+);
+
+TRACE_EVENT(sync_pt,
+ TP_PROTO(struct sync_pt *pt),
+
+ TP_ARGS(pt),
+
+ TP_STRUCT__entry(
+ __string(timeline, pt->parent->name)
+ __array(char, value, 32)
+ ),
+
+ TP_fast_assign(
+ __assign_str(timeline, pt->parent->name);
+ if (pt->parent->ops->pt_value_str) {
+ pt->parent->ops->pt_value_str(pt, __entry->value,
+ sizeof(__entry->value));
+ } else {
+ __entry->value[0] = '\0';
+ }
+ ),
+
+ TP_printk("name=%s value=%s", __get_str(timeline), __entry->value)
+);
+
+#endif /* if !defined(_TRACE_SYNC_H) || defined(TRACE_HEADER_MULTI_READ) */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/drivers/staging/android/uapi/android_alarm.h b/drivers/staging/android/uapi/android_alarm.h
new file mode 100644
index 00000000000..aa013f6f5f3
--- /dev/null
+++ b/drivers/staging/android/uapi/android_alarm.h
@@ -0,0 +1,62 @@
+/* drivers/staging/android/uapi/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 _UAPI_LINUX_ANDROID_ALARM_H
+#define _UAPI_LINUX_ANDROID_ALARM_H
+
+#include <linux/ioctl.h>
+#include <linux/time.h>
+
+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/uapi/ashmem.h b/drivers/staging/android/uapi/ashmem.h
new file mode 100644
index 00000000000..ba4743c71d6
--- /dev/null
+++ b/drivers/staging/android/uapi/ashmem.h
@@ -0,0 +1,47 @@
+/*
+ * drivers/staging/android/uapi/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 _UAPI_LINUX_ASHMEM_H
+#define _UAPI_LINUX_ASHMEM_H
+
+#include <linux/ioctl.h>
+
+#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 /* _UAPI_LINUX_ASHMEM_H */
diff --git a/drivers/staging/android/uapi/binder.h b/drivers/staging/android/uapi/binder.h
new file mode 100644
index 00000000000..904adb7600c
--- /dev/null
+++ b/drivers/staging/android/uapi/binder.h
@@ -0,0 +1,351 @@
+/*
+ * 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 _UAPI_LINUX_BINDER_H
+#define _UAPI_LINUX_BINDER_H
+
+#include <linux/ioctl.h>
+
+#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,
+};
+
+#ifdef BINDER_IPC_32BIT
+typedef __u32 binder_size_t;
+typedef __u32 binder_uintptr_t;
+#else
+typedef __u64 binder_size_t;
+typedef __u64 binder_uintptr_t;
+#endif
+
+/*
+ * 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. */
+ __u32 type;
+ __u32 flags;
+
+ /* 8 bytes of data. */
+ union {
+ binder_uintptr_t binder; /* local object */
+ __u32 handle; /* remote object */
+ };
+
+ /* extra data associated with local object */
+ binder_uintptr_t cookie;
+};
+
+/*
+ * On 64-bit platforms where user code may run in 32-bits the driver must
+ * translate the buffer (and local binder) addresses appropriately.
+ */
+
+struct binder_write_read {
+ binder_size_t write_size; /* bytes to write */
+ binder_size_t write_consumed; /* bytes consumed by driver */
+ binder_uintptr_t write_buffer;
+ binder_size_t read_size; /* bytes to read */
+ binder_size_t read_consumed; /* bytes consumed by driver */
+ binder_uintptr_t read_buffer;
+};
+
+/* Use with BINDER_VERSION, driver fills in fields. */
+struct binder_version {
+ /* driver protocol version -- increment with incompatible change */
+ __s32 protocol_version;
+};
+
+/* This is the current protocol version. */
+#ifdef BINDER_IPC_32BIT
+#define BINDER_CURRENT_PROTOCOL_VERSION 7
+#else
+#define BINDER_CURRENT_PROTOCOL_VERSION 8
+#endif
+
+#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
+#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64)
+#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)
+#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32)
+#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32)
+#define BINDER_THREAD_EXIT _IOW('b', 8, __s32)
+#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 {
+ /* target descriptor of command transaction */
+ __u32 handle;
+ /* target descriptor of return transaction */
+ binder_uintptr_t ptr;
+ } target;
+ binder_uintptr_t cookie; /* target object cookie */
+ __u32 code; /* transaction command */
+
+ /* General information about the transaction. */
+ __u32 flags;
+ pid_t sender_pid;
+ uid_t sender_euid;
+ binder_size_t data_size; /* number of bytes of data */
+ binder_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 */
+ binder_uintptr_t buffer;
+ /* offsets from buffer to flat_binder_object structs */
+ binder_uintptr_t offsets;
+ } ptr;
+ __u8 buf[8];
+ } data;
+};
+
+struct binder_ptr_cookie {
+ binder_uintptr_t ptr;
+ binder_uintptr_t cookie;
+};
+
+struct binder_handle_cookie {
+ __u32 handle;
+ binder_uintptr_t cookie;
+} __attribute__((packed));
+
+struct binder_pri_desc {
+ __s32 priority;
+ __u32 desc;
+};
+
+struct binder_pri_ptr_cookie {
+ __s32 priority;
+ binder_uintptr_t ptr;
+ binder_uintptr_t cookie;
+};
+
+enum binder_driver_return_protocol {
+ BR_ERROR = _IOR('r', 0, __s32),
+ /*
+ * 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, __s32),
+ /*
+ * 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 incoming 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, binder_uintptr_t),
+ /*
+ * void *: cookie
+ */
+ BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, binder_uintptr_t),
+ /*
+ * 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 binder_driver_command_protocol {
+ 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, __s32),
+ /*
+ * 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, binder_uintptr_t),
+ /*
+ * void *: ptr to transaction data received on a read
+ */
+
+ BC_INCREFS = _IOW('c', 4, __u32),
+ BC_ACQUIRE = _IOW('c', 5, __u32),
+ BC_RELEASE = _IOW('c', 6, __u32),
+ BC_DECREFS = _IOW('c', 7, __u32),
+ /*
+ * 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_handle_cookie),
+ /*
+ * int: handle
+ * void *: cookie
+ */
+
+ BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15,
+ struct binder_handle_cookie),
+ /*
+ * int: handle
+ * void *: cookie
+ */
+
+ BC_DEAD_BINDER_DONE = _IOW('c', 16, binder_uintptr_t),
+ /*
+ * void *: cookie
+ */
+};
+
+#endif /* _UAPI_LINUX_BINDER_H */
+
diff --git a/drivers/staging/android/uapi/ion.h b/drivers/staging/android/uapi/ion.h
new file mode 100644
index 00000000000..6aa49567337
--- /dev/null
+++ b/drivers/staging/android/uapi/ion.h
@@ -0,0 +1,196 @@
+/*
+ * drivers/staging/android/uapi/ion.h
+ *
+ * Copyright (C) 2011 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 _UAPI_LINUX_ION_H
+#define _UAPI_LINUX_ION_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+typedef int ion_user_handle_t;
+
+/**
+ * enum ion_heap_types - list of all possible types of heaps
+ * @ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc
+ * @ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kmalloc
+ * @ION_HEAP_TYPE_CARVEOUT: memory allocated from a prereserved
+ * carveout heap, allocations are physically
+ * contiguous
+ * @ION_HEAP_TYPE_DMA: memory allocated via DMA API
+ * @ION_NUM_HEAPS: helper for iterating over heaps, a bit mask
+ * is used to identify the heaps, so only 32
+ * total heap types are supported
+ */
+enum ion_heap_type {
+ ION_HEAP_TYPE_SYSTEM,
+ ION_HEAP_TYPE_SYSTEM_CONTIG,
+ ION_HEAP_TYPE_CARVEOUT,
+ ION_HEAP_TYPE_CHUNK,
+ ION_HEAP_TYPE_DMA,
+ ION_HEAP_TYPE_CUSTOM, /* must be last so device specific heaps always
+ are at the end of this enum */
+ ION_NUM_HEAPS = 16,
+};
+
+#define ION_HEAP_SYSTEM_MASK (1 << ION_HEAP_TYPE_SYSTEM)
+#define ION_HEAP_SYSTEM_CONTIG_MASK (1 << ION_HEAP_TYPE_SYSTEM_CONTIG)
+#define ION_HEAP_CARVEOUT_MASK (1 << ION_HEAP_TYPE_CARVEOUT)
+#define ION_HEAP_TYPE_DMA_MASK (1 << ION_HEAP_TYPE_DMA)
+
+#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8)
+
+/**
+ * allocation flags - the lower 16 bits are used by core ion, the upper 16
+ * bits are reserved for use by the heaps themselves.
+ */
+#define ION_FLAG_CACHED 1 /* mappings of this buffer should be
+ cached, ion will do cache
+ maintenance when the buffer is
+ mapped for dma */
+#define ION_FLAG_CACHED_NEEDS_SYNC 2 /* mappings of this buffer will created
+ at mmap time, if this is set
+ caches must be managed manually */
+
+/**
+ * DOC: Ion Userspace API
+ *
+ * create a client by opening /dev/ion
+ * most operations handled via following ioctls
+ *
+ */
+
+/**
+ * struct ion_allocation_data - metadata passed from userspace for allocations
+ * @len: size of the allocation
+ * @align: required alignment of the allocation
+ * @heap_id_mask: mask of heap ids to allocate from
+ * @flags: flags passed to heap
+ * @handle: pointer that will be populated with a cookie to use to
+ * refer to this allocation
+ *
+ * Provided by userspace as an argument to the ioctl
+ */
+struct ion_allocation_data {
+ size_t len;
+ size_t align;
+ unsigned int heap_id_mask;
+ unsigned int flags;
+ ion_user_handle_t handle;
+};
+
+/**
+ * struct ion_fd_data - metadata passed to/from userspace for a handle/fd pair
+ * @handle: a handle
+ * @fd: a file descriptor representing that handle
+ *
+ * For ION_IOC_SHARE or ION_IOC_MAP userspace populates the handle field with
+ * the handle returned from ion alloc, and the kernel returns the file
+ * descriptor to share or map in the fd field. For ION_IOC_IMPORT, userspace
+ * provides the file descriptor and the kernel returns the handle.
+ */
+struct ion_fd_data {
+ ion_user_handle_t handle;
+ int fd;
+};
+
+/**
+ * struct ion_handle_data - a handle passed to/from the kernel
+ * @handle: a handle
+ */
+struct ion_handle_data {
+ ion_user_handle_t handle;
+};
+
+/**
+ * struct ion_custom_data - metadata passed to/from userspace for a custom ioctl
+ * @cmd: the custom ioctl function to call
+ * @arg: additional data to pass to the custom ioctl, typically a user
+ * pointer to a predefined structure
+ *
+ * This works just like the regular cmd and arg fields of an ioctl.
+ */
+struct ion_custom_data {
+ unsigned int cmd;
+ unsigned long arg;
+};
+
+#define ION_IOC_MAGIC 'I'
+
+/**
+ * DOC: ION_IOC_ALLOC - allocate memory
+ *
+ * Takes an ion_allocation_data struct and returns it with the handle field
+ * populated with the opaque handle for the allocation.
+ */
+#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \
+ struct ion_allocation_data)
+
+/**
+ * DOC: ION_IOC_FREE - free memory
+ *
+ * Takes an ion_handle_data struct and frees the handle.
+ */
+#define ION_IOC_FREE _IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
+
+/**
+ * DOC: ION_IOC_MAP - get a file descriptor to mmap
+ *
+ * Takes an ion_fd_data struct with the handle field populated with a valid
+ * opaque handle. Returns the struct with the fd field set to a file
+ * descriptor open in the current address space. This file descriptor
+ * can then be used as an argument to mmap.
+ */
+#define ION_IOC_MAP _IOWR(ION_IOC_MAGIC, 2, struct ion_fd_data)
+
+/**
+ * DOC: ION_IOC_SHARE - creates a file descriptor to use to share an allocation
+ *
+ * Takes an ion_fd_data struct with the handle field populated with a valid
+ * opaque handle. Returns the struct with the fd field set to a file
+ * descriptor open in the current address space. This file descriptor
+ * can then be passed to another process. The corresponding opaque handle can
+ * be retrieved via ION_IOC_IMPORT.
+ */
+#define ION_IOC_SHARE _IOWR(ION_IOC_MAGIC, 4, struct ion_fd_data)
+
+/**
+ * DOC: ION_IOC_IMPORT - imports a shared file descriptor
+ *
+ * Takes an ion_fd_data struct with the fd field populated with a valid file
+ * descriptor obtained from ION_IOC_SHARE and returns the struct with the handle
+ * filed set to the corresponding opaque handle.
+ */
+#define ION_IOC_IMPORT _IOWR(ION_IOC_MAGIC, 5, struct ion_fd_data)
+
+/**
+ * DOC: ION_IOC_SYNC - syncs a shared file descriptors to memory
+ *
+ * Deprecated in favor of using the dma_buf api's correctly (syncing
+ * will happend automatically when the buffer is mapped to a device).
+ * If necessary should be used after touching a cached buffer from the cpu,
+ * this will make the buffer in memory coherent.
+ */
+#define ION_IOC_SYNC _IOWR(ION_IOC_MAGIC, 7, struct ion_fd_data)
+
+/**
+ * DOC: ION_IOC_CUSTOM - call architecture specific ion ioctl
+ *
+ * Takes the argument of the architecture specific ioctl to call and
+ * passes appropriate userdata for that ioctl
+ */
+#define ION_IOC_CUSTOM _IOWR(ION_IOC_MAGIC, 6, struct ion_custom_data)
+
+#endif /* _UAPI_LINUX_ION_H */
diff --git a/drivers/staging/android/uapi/ion_test.h b/drivers/staging/android/uapi/ion_test.h
new file mode 100644
index 00000000000..ffef06f6313
--- /dev/null
+++ b/drivers/staging/android/uapi/ion_test.h
@@ -0,0 +1,70 @@
+/*
+ * drivers/staging/android/uapi/ion.h
+ *
+ * Copyright (C) 2011 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 _UAPI_LINUX_ION_TEST_H
+#define _UAPI_LINUX_ION_TEST_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * struct ion_test_rw_data - metadata passed to the kernel to read handle
+ * @ptr: a pointer to an area at least as large as size
+ * @offset: offset into the ion buffer to start reading
+ * @size: size to read or write
+ * @write: 1 to write, 0 to read
+ */
+struct ion_test_rw_data {
+ __u64 ptr;
+ __u64 offset;
+ __u64 size;
+ int write;
+ int __padding;
+};
+
+#define ION_IOC_MAGIC 'I'
+
+/**
+ * DOC: ION_IOC_TEST_SET_DMA_BUF - attach a dma buf to the test driver
+ *
+ * Attaches a dma buf fd to the test driver. Passing a second fd or -1 will
+ * release the first fd.
+ */
+#define ION_IOC_TEST_SET_FD \
+ _IO(ION_IOC_MAGIC, 0xf0)
+
+/**
+ * DOC: ION_IOC_TEST_DMA_MAPPING - read or write memory from a handle as DMA
+ *
+ * Reads or writes the memory from a handle using an uncached mapping. Can be
+ * used by unit tests to emulate a DMA engine as close as possible. Only
+ * expected to be used for debugging and testing, may not always be available.
+ */
+#define ION_IOC_TEST_DMA_MAPPING \
+ _IOW(ION_IOC_MAGIC, 0xf1, struct ion_test_rw_data)
+
+/**
+ * DOC: ION_IOC_TEST_KERNEL_MAPPING - read or write memory from a handle
+ *
+ * Reads or writes the memory from a handle using a kernel mapping. Can be
+ * used by unit tests to test heap map_kernel functions. Only expected to be
+ * used for debugging and testing, may not always be available.
+ */
+#define ION_IOC_TEST_KERNEL_MAPPING \
+ _IOW(ION_IOC_MAGIC, 0xf2, struct ion_test_rw_data)
+
+
+#endif /* _UAPI_LINUX_ION_H */
diff --git a/drivers/staging/android/uapi/sw_sync.h b/drivers/staging/android/uapi/sw_sync.h
new file mode 100644
index 00000000000..9b5d4869505
--- /dev/null
+++ b/drivers/staging/android/uapi/sw_sync.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _UAPI_LINUX_SW_SYNC_H
+#define _UAPI_LINUX_SW_SYNC_H
+
+#include <linux/types.h>
+
+struct sw_sync_create_fence_data {
+ __u32 value;
+ char name[32];
+ __s32 fence; /* fd of new fence */
+};
+
+#define SW_SYNC_IOC_MAGIC 'W'
+
+#define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0,\
+ struct sw_sync_create_fence_data)
+#define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32)
+
+#endif /* _UAPI_LINUX_SW_SYNC_H */
diff --git a/drivers/staging/android/uapi/sync.h b/drivers/staging/android/uapi/sync.h
new file mode 100644
index 00000000000..e964c751f6b
--- /dev/null
+++ b/drivers/staging/android/uapi/sync.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT 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 _UAPI_LINUX_SYNC_H
+#define _UAPI_LINUX_SYNC_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * struct sync_merge_data - data passed to merge ioctl
+ * @fd2: file descriptor of second fence
+ * @name: name of new fence
+ * @fence: returns the fd of the new fence to userspace
+ */
+struct sync_merge_data {
+ __s32 fd2; /* fd of second fence */
+ char name[32]; /* name of new fence */
+ __s32 fence; /* fd on newly created fence */
+};
+
+/**
+ * struct sync_pt_info - detailed sync_pt information
+ * @len: length of sync_pt_info including any driver_data
+ * @obj_name: name of parent sync_timeline
+ * @driver_name: name of driver implementing the parent
+ * @status: status of the sync_pt 0:active 1:signaled <0:error
+ * @timestamp_ns: timestamp of status change in nanoseconds
+ * @driver_data: any driver dependent data
+ */
+struct sync_pt_info {
+ __u32 len;
+ char obj_name[32];
+ char driver_name[32];
+ __s32 status;
+ __u64 timestamp_ns;
+
+ __u8 driver_data[0];
+};
+
+/**
+ * struct sync_fence_info_data - data returned from fence info ioctl
+ * @len: ioctl caller writes the size of the buffer its passing in.
+ * ioctl returns length of sync_fence_data returned to userspace
+ * including pt_info.
+ * @name: name of fence
+ * @status: status of fence. 1: signaled 0:active <0:error
+ * @pt_info: a sync_pt_info struct for every sync_pt in the fence
+ */
+struct sync_fence_info_data {
+ __u32 len;
+ char name[32];
+ __s32 status;
+
+ __u8 pt_info[0];
+};
+
+#define SYNC_IOC_MAGIC '>'
+
+/**
+ * DOC: SYNC_IOC_WAIT - wait for a fence to signal
+ *
+ * pass timeout in milliseconds. Waits indefinitely timeout < 0.
+ */
+#define SYNC_IOC_WAIT _IOW(SYNC_IOC_MAGIC, 0, __s32)
+
+/**
+ * DOC: SYNC_IOC_MERGE - merge two fences
+ *
+ * Takes a struct sync_merge_data. Creates a new fence containing copies of
+ * the sync_pts in both the calling fd and sync_merge_data.fd2. Returns the
+ * new fence's fd in sync_merge_data.fence
+ */
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 1, struct sync_merge_data)
+
+/**
+ * DOC: SYNC_IOC_FENCE_INFO - get detailed information on a fence
+ *
+ * Takes a struct sync_fence_info_data with extra space allocated for pt_info.
+ * Caller should write the size of the buffer into len. On return, len is
+ * updated to reflect the total size of the sync_fence_info_data including
+ * pt_info.
+ *
+ * pt_info is a buffer containing sync_pt_infos for every sync_pt in the fence.
+ * To iterate over the sync_pt_infos, use the sync_pt_info.len field.
+ */
+#define SYNC_IOC_FENCE_INFO _IOWR(SYNC_IOC_MAGIC, 2,\
+ struct sync_fence_info_data)
+
+#endif /* _UAPI_LINUX_SYNC_H */
diff --git a/drivers/staging/bcm/Adapter.h b/drivers/staging/bcm/Adapter.h
new file mode 100644
index 00000000000..1b2d9f3bd55
--- /dev/null
+++ b/drivers/staging/bcm/Adapter.h
@@ -0,0 +1,457 @@
+/***********************************
+* Adapter.h
+************************************/
+#ifndef __ADAPTER_H__
+#define __ADAPTER_H__
+
+#define MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES 256
+#include "Debug.h"
+
+struct bcm_leader {
+ USHORT Vcid;
+ USHORT PLength;
+ UCHAR Status;
+ UCHAR Unused[3];
+} __packed;
+
+struct bcm_packettosend {
+ struct bcm_leader Leader;
+ UCHAR ucPayload;
+} __packed;
+
+struct bcm_control_packet {
+ PVOID ControlBuff;
+ UINT ControlBuffLen;
+ struct bcm_control_packet *next;
+} __packed;
+
+struct bcm_link_request {
+ struct bcm_leader Leader;
+ UCHAR szData[4];
+} __packed;
+
+#define MAX_IP_RANGE_LENGTH 4
+#define MAX_PORT_RANGE 4
+#define MAX_PROTOCOL_LENGTH 32
+#define IPV6_ADDRESS_SIZEINBYTES 0x10
+
+union u_ip_address {
+ struct {
+ /* Source Ip Address Range */
+ ULONG ulIpv4Addr[MAX_IP_RANGE_LENGTH];
+ /* Source Ip Mask Address Range */
+ ULONG ulIpv4Mask[MAX_IP_RANGE_LENGTH];
+ };
+ 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];
+ };
+};
+
+struct bcm_hdr_suppression_contextinfo {
+ UCHAR ucaHdrSuppressionInBuf[MAX_PHS_LENGTHS]; /* Intermediate buffer to accumulate pkt Header for PHS */
+ UCHAR ucaHdrSuppressionOutBuf[MAX_PHS_LENGTHS + PHSI_LEN]; /* Intermediate buffer containing pkt Header after PHS */
+};
+
+struct bcm_classifier_rule {
+ ULONG ulSFID;
+ UCHAR ucReserved[2];
+ B_UINT16 uiClassifierRuleIndex;
+ bool bUsed;
+ USHORT usVCID_Value;
+ B_UINT8 u8ClassifierRulePriority; /* This field detemines the Classifier Priority */
+ union u_ip_address stSrcIpAddress;
+ UCHAR ucIPSourceAddressLength; /* Ip Source Address Length */
+
+ union 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;
+
+ bool bProtocolValid;
+ bool bTOSValid;
+ bool bDestIpValid;
+ bool bSrcIpValid;
+
+ /* For IPv6 Addressing */
+ UCHAR ucDirection;
+ bool bIpv6Protocol;
+ UINT32 u32PHSRuleID;
+ struct bcm_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;
+};
+
+struct bcm_fragmented_packet_info {
+ bool bUsed;
+ ULONG ulSrcIpAddress;
+ USHORT usIpIdentification;
+ struct bcm_classifier_rule *pstMatchedClassifierEntry;
+ bool bOutOfOrderFragment;
+};
+
+struct bcm_packet_info {
+ /* classification extension Rule */
+ ULONG ulSFID;
+ USHORT usVCID_Value;
+ UINT uiThreshold;
+ /* This field determines the priority of the SF Queues */
+ B_UINT8 u8TrafficPriority;
+
+ bool bValid;
+ bool bActive;
+ bool 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;
+ struct bcm_mibs_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;
+ };
+ };
+
+ bool bProtocolValid;
+ bool bTOSValid;
+ bool bDestIpValid;
+ bool bSrcIpValid;
+
+ bool bActiveSet;
+ bool bAdmittedSet;
+ bool bAuthorizedSet;
+ bool bClassifierPriority;
+ UCHAR ucServiceClassName[MAX_CLASS_NAME_LENGTH];
+ bool bHeaderSuppressionEnabled;
+ spinlock_t SFQueueLock;
+ void *pstSFIndication;
+ struct timeval stLastUpdateTokenAt;
+ atomic_t uiPerSFTxResourceCount;
+ UINT uiMaxLatency;
+ UCHAR bIPCSSupport;
+ UCHAR bEthCSSupport;
+};
+
+struct bcm_tarang_data {
+ struct bcm_tarang_data *next;
+ struct bcm_mini_adapter *Adapter;
+ struct sk_buff *RxAppControlHead;
+ struct sk_buff *RxAppControlTail;
+ int AppCtrlQueueLen;
+ bool MacTracingEnabled;
+ bool bApplicationToExit;
+ struct bcm_mibs_dropped_cntrl_msg stDroppedAppCntrlMsgs;
+ ULONG RxCntrlMsgBitMask;
+};
+
+struct bcm_targetdsx_buffer {
+ ULONG ulTargetDsxBuffer;
+ B_UINT16 tid;
+ bool valid;
+};
+
+typedef int (*FP_FLASH_WRITE)(struct bcm_mini_adapter *, UINT, PVOID);
+
+typedef int (*FP_FLASH_WRITE_STATUS)(struct bcm_mini_adapter *, UINT, PVOID);
+
+/*
+ * Driver adapter data structure
+ */
+struct bcm_mini_adapter {
+ struct bcm_mini_adapter *next;
+ struct net_device *dev;
+ u32 msg_enable;
+ CHAR *caDsxReqResp;
+ atomic_t ApplicationRunning;
+ bool AppCtrlQueueOverFlow;
+ atomic_t CurrentApplicationCount;
+ atomic_t RegisteredApplicationCount;
+ bool LinkUpStatus;
+ bool TimerActive;
+ u32 StatisticsPointer;
+ struct sk_buff *RxControlHead;
+ struct sk_buff *RxControlTail;
+ struct semaphore RxAppControlQueuelock;
+ struct semaphore fw_download_sema;
+ struct bcm_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;
+ struct bcm_packet_info PackInfo[NO_OF_QUEUES];
+ struct bcm_classifier_rule astClassifierTable[MAX_CLASSIFIERS];
+ bool TransferMode;
+
+ /*************** qos ******************/
+ bool bETHCSEnabled;
+ ULONG BEBucketSize;
+ ULONG rtPSBucketSize;
+ UCHAR LinkStatus;
+ bool AutoLinkUp;
+ bool AutoSyncup;
+
+ int major;
+ int minor;
+ wait_queue_head_t tx_packet_wait_queue;
+ wait_queue_head_t process_rx_cntrlpkt;
+ atomic_t process_waiting;
+ bool 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;
+
+ struct bcm_targetdsx_buffer astTargetDsxBuffer[MAX_TARGET_DSX_BUFFERS];
+ ULONG ulFreeTargetBufferCnt;
+ ULONG ulCurrentTargetBuffer;
+ ULONG ulTotalTargetBuffersAvailable;
+ unsigned long chip_id;
+ wait_queue_head_t lowpower_mode_wait_queue;
+ bool bFlashBoot;
+ bool bBinDownloaded;
+ bool bCfgDownloaded;
+ bool bSyncUpRequestSent;
+ USHORT usBestEffortQueueIndex;
+ wait_queue_head_t ioctl_fw_dnld_wait_queue;
+ bool waiting_to_fw_download_done;
+ pid_t fw_download_process_pid;
+ struct bcm_target_params *pstargetparams;
+ bool device_removed;
+ bool DeviceAccess;
+ bool bIsAutoCorrectEnabled;
+ bool 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 */
+ struct bcm_led_info LEDInfo;
+
+ /* Driver State for LED Blinking */
+ enum bcm_led_events 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);
+ bool IdleMode;
+ bool bDregRequestSentInIdleMode;
+ bool bTriedToWakeUpFromlowPowerMode;
+ bool bShutStatus;
+ bool bWakeUpDevice;
+ unsigned int usIdleModePattern;
+ /* BOOLEAN bTriedToWakeUpFromShutdown; */
+ bool bLinkDownRequested;
+ int downloadDDR;
+ struct bcm_phs_extension stBCMPhsContext;
+ struct bcm_hdr_suppression_contextinfo stPhsTxContextInfo;
+ uint8_t ucaPHSPktRestoreBuf[2048];
+ uint8_t bPHSEnabled;
+ bool AutoFirmDld;
+ bool bMipsConfig;
+ bool bDPLLConfig;
+ UINT32 aTxPktSizeHist[MIBS_MAX_HIST_ENTRIES];
+ UINT32 aRxPktSizeHist[MIBS_MAX_HIST_ENTRIES];
+ struct bcm_fragmented_packet_info astFragmentedPktClassifierTable[MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES];
+ atomic_t uiMBupdate;
+ UINT32 PmuMode;
+ enum bcm_nvm_type eNVMType;
+ UINT uiSectorSize;
+ UINT uiSectorSizeInCFG;
+ bool bSectorSizeOverride;
+ bool 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; */
+ struct bcm_flash2x_cs_info *psFlash2xCSInfo;
+ struct bcm_flash_cs_info *psFlashCSInfo;
+ struct bcm_flash2x_vendor_info *psFlash2xVendorInfo;
+ UINT uiFlashBaseAdd; /* Flash start address */
+ UINT uiActiveISOOffset; /* Active ISO offset chosen before f/w download */
+ enum bcm_flash2x_section_val eActiveISO; /* Active ISO section val */
+ enum bcm_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;
+ bool bAllDSDWriteAllow;
+ bool bSigCorrupted;
+ /* this should be set who so ever want to change the Headers. after Write it should be reset immediately. */
+ bool bHeaderChangeAllowed;
+ int SelectedChip;
+ bool bEndPointHalted;
+ /* while bFlashRawRead will be true, Driver ignore map lay out and consider flash as of without any map. */
+ bool bFlashRawRead;
+ bool bPreparingForLowPowerMode;
+ bool bDoSuspend;
+ UINT syscfgBefFwDld;
+ bool StopAllXaction;
+ UINT32 liTimeSinceLastNetEntry; /* Used to Support extended CAPI requirements from */
+ struct semaphore LowPowerModeSync;
+ ULONG liDrainCalculated;
+ UINT gpioBitMap;
+ struct bcm_debug_state stDebugState;
+};
+
+#define GET_BCM_ADAPTER(net_dev) netdev_priv(net_dev)
+
+struct bcm_eth_header {
+ UCHAR au8DestinationAddress[6];
+ UCHAR au8SourceAddress[6];
+ USHORT u16Etype;
+} __packed;
+
+struct bcm_firmware_info {
+ void __user *pvMappedFirmwareAddress;
+ ULONG u32FirmwareLength;
+ ULONG u32StartingAddress;
+} __packed;
+
+/* holds the value of net_device structure.. */
+extern struct net_device *gblpnetdev;
+
+struct bcm_ddr_setting {
+ UINT ulRegAddress;
+ UINT ulRegValue;
+};
+int InitAdapter(struct bcm_mini_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
+
+enum bcm_einterface_setting {
+ DEFAULT_SETTING_0 = 0,
+ ALTERNATE_SETTING_1 = 1,
+};
+
+#endif /* __ADAPTER_H__ */
diff --git a/drivers/staging/bcm/Bcmchar.c b/drivers/staging/bcm/Bcmchar.c
new file mode 100644
index 00000000000..606d5f5e921
--- /dev/null
+++ b/drivers/staging/bcm/Bcmchar.c
@@ -0,0 +1,2652 @@
+#include <linux/fs.h>
+
+#include "headers.h"
+
+static int bcm_handle_nvm_read_cmd(struct bcm_mini_adapter *Adapter,
+ PUCHAR pReadData, struct bcm_nvm_readwrite *stNVMReadWrite)
+{
+ INT Status = STATUS_FAILURE;
+
+ 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;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+static int handle_flash2x_adapter(struct bcm_mini_adapter *Adapter,
+ PUCHAR pReadData, struct bcm_nvm_readwrite *stNVMReadWrite)
+{
+ /*
+ * 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.
+ */
+ INT Status;
+ ULONG ulDSDMagicNumInUsrBuff = 0;
+
+ Status = BcmFlash2xCorruptSig(Adapter, Adapter->eActiveDSD);
+ if (Status == STATUS_SUCCESS)
+ return 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;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+/***************************************************************
+* 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)
+{
+ struct bcm_mini_adapter *Adapter = NULL;
+ struct bcm_tarang_data *pTarang = NULL;
+
+ Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ pTarang = kzalloc(sizeof(struct bcm_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)
+{
+ struct bcm_tarang_data *pTarang, *tmp, *ptmp;
+ struct bcm_mini_adapter *Adapter = NULL;
+ struct sk_buff *pkt, *npkt;
+
+ pTarang = (struct bcm_tarang_data *)filp->private_data;
+
+ if (pTarang == NULL)
+ 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)
+{
+ struct bcm_tarang_data *pTarang = filp->private_data;
+ struct bcm_mini_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 int bcm_char_ioctl_reg_read_private(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_rdm_buffer sRdmBuffer = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ PCHAR temp_buff;
+ INT Status = STATUS_FAILURE;
+ UINT Bufflen;
+ u16 temp_value;
+ int bytes;
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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);
+ return Status;
+}
+
+static int bcm_char_ioctl_reg_write_private(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_wrm_buffer sWrmBuffer = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ UINT uiTempVar = 0;
+ INT Status;
+
+ /* Copy Ioctl Buffer structure */
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_eeprom_reg_read(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_rdm_buffer sRdmBuffer = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ PCHAR temp_buff = NULL;
+ UINT uiTempVar = 0;
+ INT Status;
+ int bytes;
+
+ 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(struct bcm_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);
+ return Status;
+}
+
+static int bcm_char_ioctl_eeprom_reg_write(void __user *argp,
+ struct bcm_mini_adapter *Adapter,
+ UINT cmd)
+{
+ struct bcm_wrm_buffer sWrmBuffer = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ UINT uiTempVar = 0;
+ INT Status;
+
+ 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(struct bcm_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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_gpio_set_request(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_gpio_info gpio_info = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ UCHAR ucResetValue[4];
+ UINT value = 0;
+ UINT uiBit = 0;
+ UINT uiOperation = 0;
+ INT Status;
+ int bytes;
+
+ 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(struct bcm_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<<uiBit);
+
+ if (IsReqGpioIsLedInNVM(Adapter, value) == false) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG,
+ DBG_LVL_ALL,
+ "Sorry, Requested GPIO<0x%X> is not correspond to LED !!!",
+ value);
+ return -EINVAL;
+ }
+
+ /* 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);
+ return Status;
+ }
+ } 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);
+ return Status;
+ }
+ }
+
+ 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");
+ return Status;
+ } else {
+ Status = STATUS_SUCCESS;
+ }
+
+ /* Set the gpio mode register to output */
+ *(UINT *)ucResetValue |= (1<<uiBit);
+ Status = wrmaltWithLock(Adapter, GPIO_MODE_REGISTER,
+ (PUINT)ucResetValue, sizeof(UINT));
+
+ if (Status == STATUS_SUCCESS) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG,
+ DBG_LVL_ALL,
+ "Set the GPIO to output Mode\n");
+ } else {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG,
+ DBG_LVL_ALL,
+ "Failed to put GPIO in Output Mode\n");
+ }
+
+ return Status;
+}
+
+static int bcm_char_ioctl_led_thread_state_change_req(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_user_thread_req threadReq = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL,
+ "User made LED thread InActive");
+
+ 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(struct bcm_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);
+ }
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_gpio_status_request(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_gpio_info gpio_info = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ ULONG uiBit = 0;
+ UCHAR ucRead[4];
+ INT Status;
+ int bytes;
+
+ if ((Adapter->IdleMode == TRUE) ||
+ (Adapter->bShutStatus == TRUE) ||
+ (Adapter->bPreparingForLowPowerMode == TRUE))
+ return -EACCES;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_gpio_multi_request(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_gpio_multi_info gpio_multi_info[MAX_IDX];
+ struct bcm_gpio_multi_info *pgpio_multi_info =
+ (struct bcm_gpio_multi_info *)gpio_multi_info;
+ struct bcm_ioctl_buffer IoBuffer;
+ UCHAR ucResetValue[4];
+ INT Status = STATUS_FAILURE;
+ int bytes;
+
+ memset(pgpio_multi_info, 0,
+ MAX_IDX * sizeof(struct bcm_gpio_multi_info));
+
+ if ((Adapter->IdleMode == TRUE) ||
+ (Adapter->bShutStatus == TRUE) ||
+ (Adapter->bPreparingForLowPowerMode == TRUE))
+ return -EINVAL;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.InputLength > sizeof(gpio_multi_info))
+ return -EINVAL;
+ if (IoBuffer.OutputLength > sizeof(gpio_multi_info))
+ IoBuffer.OutputLength = sizeof(gpio_multi_info);
+
+ 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);
+ return -EINVAL;
+ }
+
+ /* 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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_gpio_mode_request(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_gpio_multi_mode gpio_multi_mode[MAX_IDX];
+ struct bcm_gpio_multi_mode *pgpio_multi_mode =
+ (struct bcm_gpio_multi_mode *)gpio_multi_mode;
+ struct bcm_ioctl_buffer IoBuffer;
+ UCHAR ucResetValue[4];
+ INT Status;
+ int bytes;
+
+ if ((Adapter->IdleMode == TRUE) ||
+ (Adapter->bShutStatus == TRUE) ||
+ (Adapter->bPreparingForLowPowerMode == TRUE))
+ return -EINVAL;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.InputLength > sizeof(gpio_multi_mode))
+ return -EINVAL;
+ if (IoBuffer.OutputLength > sizeof(gpio_multi_mode))
+ IoBuffer.OutputLength = sizeof(gpio_multi_mode);
+
+ 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);
+ return -EINVAL;
+ }
+
+ 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");
+ return -EFAULT;
+ }
+ } 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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_misc_request(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ PVOID pvBuffer = NULL;
+ INT Status;
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.InputLength < sizeof(struct bcm_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);
+ return Status;
+}
+
+static int bcm_char_ioctl_buffer_download_start(
+ struct bcm_mini_adapter *Adapter)
+{
+ INT Status;
+
+ 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;
+}
+
+static int bcm_char_ioctl_buffer_download(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_firmware_info *psFwInfo = NULL;
+ struct bcm_ioctl_buffer IoBuffer;
+ INT Status;
+
+ 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);
+ return -EINVAL;
+ }
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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(struct bcm_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);
+ kfree(psFwInfo);
+ 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);
+ kfree(psFwInfo);
+ 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;
+}
+
+static int bcm_char_ioctl_buffer_download_stop(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ INT Status;
+ int timeout = 0;
+
+ 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((struct bcm_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;
+}
+
+static int bcm_char_ioctl_chip_reset(struct bcm_mini_adapter *Adapter)
+{
+ INT Status;
+ INT NVMAccess;
+
+ 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);
+ return Status;
+}
+
+static int bcm_char_ioctl_qos_threshold(ULONG arg,
+ struct bcm_mini_adapter *Adapter)
+{
+ USHORT uiLoopIndex;
+
+ for (uiLoopIndex = 0; uiLoopIndex < NO_OF_QUEUES; uiLoopIndex++) {
+ if (get_user(Adapter->PackInfo[uiLoopIndex].uiThreshold,
+ (unsigned long __user *)arg)) {
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+static int bcm_char_ioctl_switch_transfer_mode(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ 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;
+ }
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_get_driver_version(void __user *argp)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ ulong len;
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ len = min_t(ulong, IoBuffer.OutputLength, strlen(DRV_VERSION) + 1);
+
+ if (copy_to_user(IoBuffer.OutputBuffer, DRV_VERSION, len))
+ return -EFAULT;
+
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_get_current_status(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_link_state link_state;
+ struct bcm_ioctl_buffer IoBuffer;
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer))) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "copy_from_user failed..\n");
+ return -EFAULT;
+ }
+
+ if (IoBuffer.OutputLength != sizeof(link_state))
+ return -EINVAL;
+
+ 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;
+ }
+ return STATUS_SUCCESS;
+}
+
+
+static int bcm_char_ioctl_set_mac_tracing(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ UINT tracing_flag;
+
+ /* copy ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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;
+
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_get_dsx_indication(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ ULONG ulSFId = 0;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.OutputLength < sizeof(struct bcm_add_indication_alt)) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "Mismatch req: %lx needed is =0x%zx!!!",
+ IoBuffer.OutputLength,
+ sizeof(struct bcm_add_indication_alt));
+ 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);
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_get_host_mibs(void __user *argp,
+ struct bcm_mini_adapter *Adapter, struct bcm_tarang_data *pTarang)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ INT Status = STATUS_FAILURE;
+ PVOID temp_buff;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.OutputLength != sizeof(struct bcm_host_stats_mibs)) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "Length Check failed %lu %zd\n", IoBuffer.OutputLength,
+ sizeof(struct bcm_host_stats_mibs));
+ return -EINVAL;
+ }
+
+ /* FIXME: HOST_STATS are too big for kmalloc (122048)! */
+ temp_buff = kzalloc(sizeof(struct bcm_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(struct bcm_host_stats_mibs))) {
+ kfree(temp_buff);
+ return -EFAULT;
+ }
+ }
+
+ kfree(temp_buff);
+ return Status;
+}
+
+static int bcm_char_ioctl_bulk_wrm(void __user *argp,
+ struct bcm_mini_adapter *Adapter, UINT cmd)
+{
+ struct bcm_bulk_wrm_buffer *pBulkBuffer;
+ struct bcm_ioctl_buffer IoBuffer;
+ UINT uiTempVar = 0;
+ INT Status = STATUS_FAILURE;
+ 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");
+ return -EACCES;
+ }
+
+ /* Copy Ioctl Buffer structure */
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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 = (struct bcm_bulk_wrm_buffer *)pvBuffer;
+
+ if (((ULONG)pBulkBuffer->Register & 0x0F000000) != 0x0F000000 ||
+ ((ULONG)pBulkBuffer->Register & 0x3)) {
+ BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "WRM Done On invalid Address : %x Access Denied.\n",
+ (int)pBulkBuffer->Register);
+ kfree(pvBuffer);
+ return -EINVAL;
+ }
+
+ 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");
+ return -EFAULT;
+ }
+
+ 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);
+ return Status;
+}
+
+static int bcm_char_ioctl_get_nvm_size(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+
+ if (copy_from_user(&IoBuffer, argp, sizeof(struct bcm_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;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_cal_init(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ UINT uiSectorSize = 0;
+ INT Status = STATUS_FAILURE;
+
+ if (Adapter->eNVMType == NVM_FLASH) {
+ if (copy_from_user(&IoBuffer, argp,
+ sizeof(struct bcm_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;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_set_debug(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+#ifdef DEBUG
+ struct bcm_ioctl_buffer IoBuffer;
+ struct bcm_user_debug_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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (copy_from_user(&sUserDebugState, IoBuffer.InputBuffer,
+ sizeof(struct bcm_user_debug_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
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_nvm_rw(void __user *argp,
+ struct bcm_mini_adapter *Adapter, UINT cmd)
+{
+ struct bcm_nvm_readwrite stNVMReadWrite;
+ struct timeval tv0, tv1;
+ struct bcm_ioctl_buffer IoBuffer;
+ PUCHAR pReadData = NULL;
+ INT Status = STATUS_FAILURE;
+
+ 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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (copy_from_user(&stNVMReadWrite,
+ (IOCTL_BCM_NVM_READ == cmd) ?
+ IoBuffer.OutputBuffer : IoBuffer.InputBuffer,
+ sizeof(struct bcm_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)
+ 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) {
+ int ret = bcm_handle_nvm_read_cmd(Adapter, pReadData,
+ &stNVMReadWrite);
+ if (ret != STATUS_SUCCESS)
+ return ret;
+ } 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)) {
+ int ret = handle_flash2x_adapter(Adapter,
+ pReadData,
+ &stNVMReadWrite);
+ if (ret != STATUS_SUCCESS)
+ return ret;
+ }
+
+ 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;
+}
+
+static int bcm_char_ioctl_flash2x_section_read(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_flash2x_readwrite sFlash2xRead = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ PUCHAR pReadBuff = NULL;
+ UINT NOB = 0;
+ UINT BuffSize = 0;
+ UINT ReadBytes = 0;
+ UINT ReadOffset = 0;
+ INT Status = STATUS_FAILURE;
+ 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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ /* Reading FLASH 2.x READ structure */
+ if (copy_from_user(&sFlash2xRead, IoBuffer.InputBuffer,
+ sizeof(struct bcm_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);
+ return Status;
+}
+
+static int bcm_char_ioctl_flash2x_section_write(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_flash2x_readwrite sFlash2xWrite = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ PUCHAR pWriteBuff;
+ void __user *InputAddr;
+ UINT NOB = 0;
+ UINT BuffSize = 0;
+ UINT WriteOffset = 0;
+ UINT WriteBytes = 0;
+ INT Status = STATUS_FAILURE;
+
+ 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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ /* Reading FLASH 2.x READ structure */
+ if (copy_from_user(&sFlash2xWrite, IoBuffer.InputBuffer,
+ sizeof(struct bcm_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);
+ return Status;
+}
+
+static int bcm_char_ioctl_flash2x_section_bitmap(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_flash2x_bitmap *psFlash2xBitMap;
+ struct bcm_ioctl_buffer IoBuffer;
+
+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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.OutputLength != sizeof(struct bcm_flash2x_bitmap))
+ return -EINVAL;
+
+ psFlash2xBitMap = kzalloc(sizeof(struct bcm_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(struct bcm_flash2x_bitmap))) {
+ kfree(psFlash2xBitMap);
+ return -EFAULT;
+ }
+
+ kfree(psFlash2xBitMap);
+ return STATUS_FAILURE;
+}
+
+static int bcm_char_ioctl_set_active_section(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ enum bcm_flash2x_section_val eFlash2xSectionVal = 0;
+ INT Status = STATUS_FAILURE;
+ struct bcm_ioctl_buffer IoBuffer;
+
+ 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(struct bcm_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);
+
+ return Status;
+}
+
+static int bcm_char_ioctl_copy_section(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_flash2x_copy_section sCopySectStrut = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+ INT 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(struct bcm_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(struct bcm_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 exist 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 exist 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);
+ return Status;
+}
+
+static int bcm_char_ioctl_get_flash_cs_info(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ INT 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(struct bcm_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");
+ return -EINVAL;
+ }
+
+ if (IsFlash2x(Adapter) == TRUE) {
+ if (IoBuffer.OutputLength < sizeof(struct bcm_flash2x_cs_info))
+ return -EINVAL;
+
+ if (copy_to_user(IoBuffer.OutputBuffer,
+ Adapter->psFlash2xCSInfo,
+ sizeof(struct bcm_flash2x_cs_info)))
+ return -EFAULT;
+ } else {
+ if (IoBuffer.OutputLength < sizeof(struct bcm_flash_cs_info))
+ return -EINVAL;
+
+ if (copy_to_user(IoBuffer.OutputBuffer, Adapter->psFlashCSInfo,
+ sizeof(struct bcm_flash_cs_info)))
+ return -EFAULT;
+ }
+ return Status;
+}
+
+static int bcm_char_ioctl_select_dsd(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ INT Status = STATUS_FAILURE;
+ UINT SectOfset = 0;
+ enum bcm_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(struct bcm_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 exist in Flash 2.x",
+ eFlash2xSectionVal);
+ return -EINVAL;
+ }
+
+ Adapter->bAllDSDWriteAllow = TRUE;
+ Adapter->ulFlashCalStart = SectOfset;
+ Adapter->eActiveDSD = eFlash2xSectionVal;
+
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_nvm_raw_read(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_nvm_readwrite stNVMRead;
+ struct bcm_ioctl_buffer IoBuffer;
+ unsigned int NOB;
+ INT BuffSize;
+ INT ReadOffset = 0;
+ UINT ReadBytes = 0;
+ PUCHAR pReadBuff;
+ void __user *OutPutBuff;
+ INT Status = STATUS_FAILURE;
+
+ 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(struct bcm_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(struct bcm_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");
+ 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");
+ 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);
+ return Status;
+}
+
+static int bcm_char_ioctl_cntrlmsg_mask(void __user *argp,
+ struct bcm_mini_adapter *Adapter, struct bcm_tarang_data *pTarang)
+{
+ struct bcm_ioctl_buffer IoBuffer;
+ INT Status = STATUS_FAILURE;
+ ULONG RxCntrlMsgBitMask = 0;
+
+ /* Copy Ioctl Buffer structure */
+ Status = copy_from_user(&IoBuffer, argp,
+ sizeof(struct bcm_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))
+ return -EINVAL;
+
+ 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;
+
+ return Status;
+}
+
+static int bcm_char_ioctl_get_device_driver_info(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_driver_info DevInfo;
+ struct bcm_ioctl_buffer IoBuffer;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL,
+ "Called IOCTL_BCM_GET_DEVICE_DRIVER_INFO\n");
+
+ memset(&DevInfo, 0, sizeof(DevInfo));
+ 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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.OutputLength < sizeof(DevInfo))
+ return -EINVAL;
+
+ if (copy_to_user(IoBuffer.OutputBuffer, &DevInfo, sizeof(DevInfo)))
+ return -EFAULT;
+
+ return STATUS_SUCCESS;
+}
+
+static int bcm_char_ioctl_time_since_net_entry(void __user *argp,
+ struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_time_elapsed stTimeElapsedSinceNetEntry = {0};
+ struct bcm_ioctl_buffer IoBuffer;
+
+ 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(struct bcm_ioctl_buffer)))
+ return -EFAULT;
+
+ if (IoBuffer.OutputLength < sizeof(struct bcm_time_elapsed))
+ return -EINVAL;
+
+ stTimeElapsedSinceNetEntry.ul64TimeElapsedSinceNetEntry =
+ get_seconds() - Adapter->liTimeSinceLastNetEntry;
+
+ if (copy_to_user(IoBuffer.OutputBuffer, &stTimeElapsedSinceNetEntry,
+ sizeof(struct bcm_time_elapsed)))
+ return -EFAULT;
+
+ return STATUS_SUCCESS;
+}
+
+
+static long bcm_char_ioctl(struct file *filp, UINT cmd, ULONG arg)
+{
+ struct bcm_tarang_data *pTarang = filp->private_data;
+ void __user *argp = (void __user *)arg;
+ struct bcm_mini_adapter *Adapter = pTarang->Adapter;
+ INT Status = STATUS_FAILURE;
+
+ 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:
+ Status = bcm_char_ioctl_reg_read_private(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_REGISTER_WRITE_PRIVATE:
+ Status = bcm_char_ioctl_reg_write_private(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_REGISTER_READ:
+ case IOCTL_BCM_EEPROM_REGISTER_READ:
+ Status = bcm_char_ioctl_eeprom_reg_read(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_REGISTER_WRITE:
+ case IOCTL_BCM_EEPROM_REGISTER_WRITE:
+ Status = bcm_char_ioctl_eeprom_reg_write(argp, Adapter, cmd);
+ return Status;
+
+ case IOCTL_BCM_GPIO_SET_REQUEST:
+ Status = bcm_char_ioctl_gpio_set_request(argp, Adapter);
+ return Status;
+
+ case BCM_LED_THREAD_STATE_CHANGE_REQ:
+ Status = bcm_char_ioctl_led_thread_state_change_req(argp,
+ Adapter);
+ return Status;
+
+ case IOCTL_BCM_GPIO_STATUS_REQUEST:
+ Status = bcm_char_ioctl_gpio_status_request(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GPIO_MULTI_REQUEST:
+ Status = bcm_char_ioctl_gpio_multi_request(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GPIO_MODE_REQUEST:
+ Status = bcm_char_ioctl_gpio_mode_request(argp, Adapter);
+ return Status;
+
+ 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:
+ Status = bcm_char_ioctl_misc_request(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_BUFFER_DOWNLOAD_START:
+ Status = bcm_char_ioctl_buffer_download_start(Adapter);
+ return Status;
+
+ case IOCTL_BCM_BUFFER_DOWNLOAD:
+ Status = bcm_char_ioctl_buffer_download(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_BUFFER_DOWNLOAD_STOP:
+ Status = bcm_char_ioctl_buffer_download_stop(argp, Adapter);
+ 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:
+ Status = bcm_char_ioctl_chip_reset(Adapter);
+ return Status;
+
+ case IOCTL_QOS_THRESHOLD:
+ Status = bcm_char_ioctl_qos_threshold(arg, Adapter);
+ return Status;
+
+ 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(struct bcm_packet_info)*NO_OF_QUEUES))
+ return -EFAULT;
+ Status = STATUS_SUCCESS;
+ break;
+
+ case IOCTL_BCM_SWITCH_TRANSFER_MODE:
+ Status = bcm_char_ioctl_switch_transfer_mode(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GET_DRIVER_VERSION:
+ Status = bcm_char_ioctl_get_driver_version(argp);
+ return Status;
+
+ case IOCTL_BCM_GET_CURRENT_STATUS:
+ Status = bcm_char_ioctl_get_current_status(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_SET_MAC_TRACING:
+ Status = bcm_char_ioctl_set_mac_tracing(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GET_DSX_INDICATION:
+ Status = bcm_char_ioctl_get_dsx_indication(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GET_HOST_MIBS:
+ Status = bcm_char_ioctl_get_host_mibs(argp, Adapter, pTarang);
+ return Status;
+
+ 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:
+ Status = bcm_char_ioctl_bulk_wrm(argp, Adapter, cmd);
+ return Status;
+
+ case IOCTL_BCM_GET_NVM_SIZE:
+ Status = bcm_char_ioctl_get_nvm_size(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_CAL_INIT:
+ Status = bcm_char_ioctl_cal_init(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_SET_DEBUG:
+ Status = bcm_char_ioctl_set_debug(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_NVM_READ:
+ case IOCTL_BCM_NVM_WRITE:
+ Status = bcm_char_ioctl_nvm_rw(argp, Adapter, cmd);
+ return Status;
+
+ case IOCTL_BCM_FLASH2X_SECTION_READ:
+ Status = bcm_char_ioctl_flash2x_section_read(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_FLASH2X_SECTION_WRITE:
+ Status = bcm_char_ioctl_flash2x_section_write(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GET_FLASH2X_SECTION_BITMAP:
+ Status = bcm_char_ioctl_flash2x_section_bitmap(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_SET_ACTIVE_SECTION:
+ Status = bcm_char_ioctl_set_active_section(argp, Adapter);
+ return Status;
+
+ 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:
+ Status = bcm_char_ioctl_copy_section(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_GET_FLASH_CS_INFO:
+ Status = bcm_char_ioctl_get_flash_cs_info(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_SELECT_DSD:
+ Status = bcm_char_ioctl_select_dsd(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_NVM_RAW_READ:
+ Status = bcm_char_ioctl_nvm_raw_read(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_CNTRLMSG_MASK:
+ Status = bcm_char_ioctl_cntrlmsg_mask(argp, Adapter, pTarang);
+ return Status;
+
+ case IOCTL_BCM_GET_DEVICE_DRIVER_INFO:
+ Status = bcm_char_ioctl_get_device_driver_info(argp, Adapter);
+ return Status;
+
+ case IOCTL_BCM_TIME_SINCE_NET_ENTRY:
+ Status = bcm_char_ioctl_time_since_net_entry(argp, Adapter);
+ return Status;
+
+ 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(struct bcm_mini_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(struct bcm_mini_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 00000000000..95a2358267b
--- /dev/null
+++ b/drivers/staging/bcm/Bcmnet.c
@@ -0,0 +1,241 @@
+#include "headers.h"
+
+struct net_device *gblpnetdev;
+
+static INT bcm_open(struct net_device *dev)
+{
+ struct bcm_mini_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)
+{
+ struct bcm_mini_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,
+ void *accel_priv, select_queue_fallback_t fallback)
+{
+ 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)
+{
+ struct bcm_mini_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)
+{
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(dev);
+ struct bcm_interface_adapter *psIntfAdapter =
+ Adapter->pvInterfaceAdapter;
+ struct usb_device *udev = interface_to_usbdev(psIntfAdapter->interface);
+
+ strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
+ strlcpy(info->version, DRV_VERSION, sizeof(info->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)
+{
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(dev);
+
+ return Adapter->LinkUpStatus;
+}
+
+static u32 bcm_get_msglevel(struct net_device *dev)
+{
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(dev);
+
+ return Adapter->msg_enable;
+}
+
+static void bcm_set_msglevel(struct net_device *dev, u32 level)
+{
+ struct bcm_mini_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(struct bcm_mini_adapter *Adapter)
+{
+ struct net_device *net = Adapter->dev;
+ struct bcm_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(struct bcm_mini_adapter *Adapter)
+{
+ struct net_device *net = Adapter->dev;
+ struct bcm_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 00000000000..fb1d932c5d7
--- /dev/null
+++ b/drivers/staging/bcm/CmHost.c
@@ -0,0 +1,2253 @@
+/************************************************************
+ * CMHOST.C
+ * This file contains the routines for handling Connection
+ * Management.
+ ************************************************************/
+
+#include "headers.h"
+
+enum E_CLASSIFIER_ACTION {
+ eInvalidClassifierAction,
+ eAddClassifier,
+ eReplaceClassifier,
+ eDeleteClassifier
+};
+
+static ULONG GetNextTargetBufferLocation(struct bcm_mini_adapter *Adapter,
+ B_UINT16 tid);
+static void restore_endianess_of_pstClassifierEntry(
+ struct bcm_classifier_rule *pstClassifierEntry,
+ enum bcm_ipaddr_context eIpAddrContext);
+
+static void apply_phs_rule_to_all_classifiers(
+ register struct bcm_mini_adapter *Adapter,
+ register UINT uiSearchRuleIndex,
+ USHORT uVCID,
+ struct bcm_phs_rule *sPhsRule,
+ struct bcm_phs_rules *cPhsRule,
+ struct bcm_add_indication_alt *pstAddIndication);
+
+/************************************************************
+ * 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(struct bcm_mini_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(struct bcm_mini_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: struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mibs_table));
+}
+
+static inline VOID
+CopyIpAddrToClassifier(struct bcm_classifier_rule *pstClassifierEntry,
+ B_UINT8 u8IpAddressLen, B_UINT8 *pu8IpAddressMaskSrc,
+ bool bIpVersion6, enum bcm_ipaddr_context eIpAddrContext)
+{
+ int i = 0;
+ UINT nSizeOfIPAddressInBytes = IP_LENGTH_OF_ADDRESS;
+ UCHAR *ptrClassifierIpAddress = NULL;
+ UCHAR *ptrClassifierIpMask = NULL;
+ struct bcm_mini_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) {
+
+ union u_ip_address *st_dest_ip =
+ &pstClassifierEntry->stDestIpAddress;
+
+ union u_ip_address *st_src_ip =
+ &pstClassifierEntry->stSrcIpAddress;
+
+ /*
+ * 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 =
+ st_dest_ip->ucIpv6Address;
+ ptrClassifierIpMask =
+ st_dest_ip->ucIpv6Mask;
+ } else {
+ ptrClassifierIpAddress =
+ st_dest_ip->ucIpv4Address;
+ ptrClassifierIpMask =
+ st_dest_ip->ucIpv4Mask;
+ }
+ } else if (eIpAddrContext == eSrcIpAddress) {
+ pstClassifierEntry->ucIPSourceAddressLength =
+ u8IpAddressLen/(nSizeOfIPAddressInBytes * 2);
+ if (bIpVersion6) {
+ ptrClassifierIpAddress =
+ st_src_ip->ucIpv6Address;
+ ptrClassifierIpMask = st_src_ip->ucIpv6Mask;
+ } else {
+ ptrClassifierIpAddress =
+ st_src_ip->ucIpv4Address;
+ ptrClassifierIpMask = st_src_ip->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) {
+ st_src_ip->ulIpv4Addr[i] =
+ ntohl(st_src_ip->ulIpv4Addr[i]);
+ BCM_DEBUG_PRINT(Adapter,
+ DBG_TYPE_OTHERS,
+ CONN_MSG,
+ DBG_LVL_ALL,
+ "Src Ip Address:0x%luX ",
+ st_src_ip->ulIpv4Addr[i]);
+ } else if (eIpAddrContext == eDestIpAddress) {
+ st_dest_ip->ulIpv4Addr[i] =
+ ntohl(st_dest_ip->ulIpv4Addr[i]);
+ BCM_DEBUG_PRINT(Adapter,
+ DBG_TYPE_OTHERS,
+ CONN_MSG,
+ DBG_LVL_ALL,
+ "Dest Ip Address:0x%luX ",
+ st_dest_ip->ulIpv4Addr[i]);
+ }
+ }
+ u8IpAddressLen -= nSizeOfIPAddressInBytes;
+ if (u8IpAddressLen >= nSizeOfIPAddressInBytes) {
+ memcpy(ptrClassifierIpMask +
+ (i * nSizeOfIPAddressInBytes),
+ (pu8IpAddressMaskSrc
+ + nSizeOfIPAddressInBytes
+ + (i * nSizeOfIPAddressInBytes * 2)),
+ nSizeOfIPAddressInBytes);
+
+ if (!bIpVersion6) {
+ if (eIpAddrContext == eSrcIpAddress) {
+ st_src_ip->ulIpv4Mask[i] =
+ ntohl(st_src_ip->ulIpv4Mask[i]);
+ BCM_DEBUG_PRINT(Adapter,
+ DBG_TYPE_OTHERS,
+ CONN_MSG,
+ DBG_LVL_ALL,
+ "Src Ip Mask Address:0x%luX ",
+ st_src_ip->ulIpv4Mask[i]);
+ } else if (eIpAddrContext == eDestIpAddress) {
+ st_dest_ip->ulIpv4Mask[i] =
+ ntohl(st_dest_ip->ulIpv4Mask[i]);
+ BCM_DEBUG_PRINT(Adapter,
+ DBG_TYPE_OTHERS,
+ CONN_MSG,
+ DBG_LVL_ALL,
+ "Dest Ip Mask Address:0x%luX ",
+ st_dest_ip->ulIpv4Mask[i]);
+ }
+ }
+ u8IpAddressLen -= nSizeOfIPAddressInBytes;
+ }
+ if (u8IpAddressLen == 0)
+ pstClassifierEntry->bDestIpValid = TRUE;
+
+ i++;
+ }
+ if (bIpVersion6) {
+ /* Restore EndianNess of Struct */
+ restore_endianess_of_pstClassifierEntry(
+ pstClassifierEntry,
+ eIpAddrContext
+ );
+ }
+ }
+}
+
+void ClearTargetDSXBuffer(struct bcm_mini_adapter *Adapter, B_UINT16 TID, bool bFreeAll)
+{
+ int i;
+ struct bcm_targetdsx_buffer *curr_buf;
+
+ for (i = 0; i < Adapter->ulTotalTargetBuffersAvailable; i++) {
+ curr_buf = &Adapter->astTargetDsxBuffer[i];
+
+ if (curr_buf->valid)
+ continue;
+
+ if ((bFreeAll) || (curr_buf->tid == TID)) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "ClearTargetDSXBuffer: found tid %d buffer cleared %lx\n",
+ TID, curr_buf->ulTargetDsxBuffer);
+ curr_buf->valid = 1;
+ curr_buf->tid = 0;
+ Adapter->ulFreeTargetBufferCnt++;
+ }
+ }
+}
+
+/*
+ * @ingroup ctrl_pkt_functions
+ * copy classifier rule into the specified SF index
+ */
+static inline VOID CopyClassifierRuleToSF(struct bcm_mini_adapter *Adapter,
+ struct bcm_convergence_types *psfCSType,
+ UINT uiSearchRuleIndex,
+ UINT nClassifierIndex)
+{
+ struct bcm_classifier_rule *pstClassifierEntry = NULL;
+ /* VOID *pvPhsContext = NULL; */
+ int i;
+ /* UCHAR ucProtocolLength=0; */
+ /* ULONG ulPhsStatus; */
+
+ struct bcm_packet_class_rules *pack_class_rule =
+ &psfCSType->cCPacketClassificationRule;
+
+ 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(pack_class_rule->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 =
+ pack_class_rule->u8ProtocolDestPortRangeLength / 4;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL,
+ "Destination Port Range Length:0x%X ",
+ pstClassifierEntry->ucDestPortRangeLength);
+
+ if (pack_class_rule->u8ProtocolDestPortRangeLength <= MAX_PORT_RANGE) {
+ for (i = 0; i < (pstClassifierEntry->ucDestPortRangeLength); i++) {
+ pstClassifierEntry->usDestPortRangeLo[i] =
+ *((PUSHORT)(pack_class_rule->u8ProtocolDestPortRange+i));
+ pstClassifierEntry->usDestPortRangeHi[i] =
+ *((PUSHORT)(pack_class_rule->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 ",
+ pack_class_rule->u8ProtocolSourcePortRangeLength);
+ if (pack_class_rule->u8ProtocolSourcePortRangeLength <= MAX_PORT_RANGE) {
+ pstClassifierEntry->ucSrcPortRangeLength =
+ pack_class_rule->u8ProtocolSourcePortRangeLength/4;
+ for (i = 0; i < (pstClassifierEntry->ucSrcPortRangeLength); i++) {
+ pstClassifierEntry->usSrcPortRangeLo[i] =
+ *((PUSHORT)(pack_class_rule->
+ u8ProtocolSourcePortRange+i));
+ pstClassifierEntry->usSrcPortRangeHi[i] =
+ *((PUSHORT)(pack_class_rule->
+ 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,
+ pack_class_rule->u8IPDestinationAddressLength,
+ pack_class_rule->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,
+ pack_class_rule->u8IPMaskedSourceAddressLength,
+ pack_class_rule->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 ",
+ pack_class_rule->u8IPTypeOfServiceLength);
+ if (pack_class_rule->u8IPTypeOfServiceLength == 3) {
+ pstClassifierEntry->ucIPTypeOfServiceLength =
+ pack_class_rule->u8IPTypeOfServiceLength;
+ pstClassifierEntry->ucTosLow =
+ pack_class_rule->u8IPTypeOfService[0];
+ pstClassifierEntry->ucTosHigh =
+ pack_class_rule->u8IPTypeOfService[1];
+ pstClassifierEntry->ucTosMask =
+ pack_class_rule->u8IPTypeOfService[2];
+ pstClassifierEntry->bTOSValid = TRUE;
+ }
+ if (pack_class_rule->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] = pack_class_rule->u8Protocol;
+ pstClassifierEntry->u8ClassifierRulePriority =
+ pack_class_rule->u8ClassifierRulePriority;
+
+ /* store the classifier rule ID and set this classifier entry as valid */
+ pstClassifierEntry->ucDirection =
+ Adapter->PackInfo[uiSearchRuleIndex].ucDirection;
+ pstClassifierEntry->uiClassifierRuleIndex =
+ ntohs(pack_class_rule->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 (pack_class_rule->u8AssociatedPHSI)
+ pstClassifierEntry->u8AssociatedPHSI =
+ pack_class_rule->u8AssociatedPHSI;
+
+ /* Copy ETH CS Parameters */
+ pstClassifierEntry->ucEthCSSrcMACLen =
+ (pack_class_rule->u8EthernetSourceMACAddressLength);
+ memcpy(pstClassifierEntry->au8EThCSSrcMAC,
+ pack_class_rule->u8EthernetSourceMACAddress,
+ MAC_ADDRESS_SIZE);
+ memcpy(pstClassifierEntry->au8EThCSSrcMACMask,
+ pack_class_rule->u8EthernetSourceMACAddress
+ + MAC_ADDRESS_SIZE, MAC_ADDRESS_SIZE);
+ pstClassifierEntry->ucEthCSDestMACLen =
+ (pack_class_rule->u8EthernetDestMacAddressLength);
+ memcpy(pstClassifierEntry->au8EThCSDestMAC,
+ pack_class_rule->u8EthernetDestMacAddress,
+ MAC_ADDRESS_SIZE);
+ memcpy(pstClassifierEntry->au8EThCSDestMACMask,
+ pack_class_rule->u8EthernetDestMacAddress
+ + MAC_ADDRESS_SIZE, MAC_ADDRESS_SIZE);
+ pstClassifierEntry->ucEtherTypeLen =
+ (pack_class_rule->u8EthertypeLength);
+ memcpy(pstClassifierEntry->au8EthCSEtherType,
+ pack_class_rule->u8Ethertype,
+ NUM_ETHERTYPE_BYTES);
+ memcpy(pstClassifierEntry->usUserPriority,
+ &pack_class_rule->u16UserPriority, 2);
+ pstClassifierEntry->usVLANID =
+ ntohs(pack_class_rule->u16VLANID);
+ pstClassifierEntry->usValidityBitMap =
+ ntohs(pack_class_rule->u16ValidityBitMap);
+
+ pstClassifierEntry->bUsed = TRUE;
+ }
+}
+
+/*
+ * @ingroup ctrl_pkt_functions
+ */
+static inline VOID DeleteClassifierRuleFromSF(struct bcm_mini_adapter *Adapter,
+ UINT uiSearchRuleIndex, UINT nClassifierIndex)
+{
+ struct bcm_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(struct bcm_classifier_rule));
+
+ /* Delete the PHS Rule for this classifier */
+ PhsDeleteClassifierRule(&Adapter->stBCMPhsContext, usVCID,
+ u16PacketClassificationRuleIndex);
+ }
+}
+
+/*
+ * @ingroup ctrl_pkt_functions
+ */
+VOID DeleteAllClassifiersForSF(struct bcm_mini_adapter *Adapter,
+ UINT uiSearchRuleIndex)
+{
+ struct bcm_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 struct bcm_mini_adapter *Adapter, /* <Pointer to the Adapter structure */
+ register struct bcm_connect_mgr_params *psfLocalSet, /* Pointer to the connection manager parameters structure */
+ register UINT uiSearchRuleIndex, /* <Index of Queue, to which this data belongs */
+ register UCHAR ucDsxType,
+ struct bcm_add_indication_alt *pstAddIndication) {
+
+ /* UCHAR ucProtocolLength = 0; */
+ ULONG ulSFID;
+ UINT nClassifierIndex = 0;
+ enum E_CLASSIFIER_ACTION eClassifierAction = eInvalidClassifierAction;
+ B_UINT16 u16PacketClassificationRuleIndex = 0;
+ int i;
+ struct bcm_convergence_types *psfCSType = NULL;
+ struct bcm_phs_rule sPhsRule;
+ struct bcm_packet_info *curr_packinfo =
+ &Adapter->PackInfo[uiSearchRuleIndex];
+ USHORT uVCID = curr_packinfo->usVCID_Value;
+ UINT UGIValue = 0;
+
+ curr_packinfo->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 */
+
+ curr_packinfo->bIPCSSupport = 0;
+ curr_packinfo->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:
+ curr_packinfo->bIPCSSupport = IPV4_CS;
+ break;
+ case eCSPacketIPV6:
+ curr_packinfo->bIPCSSupport = IPV6_CS;
+ break;
+ case eCS802_3PacketEthernet:
+ case eCS802_1QPacketVLAN:
+ curr_packinfo->bEthCSSupport = ETH_CS_802_3;
+ break;
+ case eCSPacketIPV4Over802_1QVLAN:
+ case eCSPacketIPV4Over802_3Ethernet:
+ curr_packinfo->bIPCSSupport = IPV4_CS;
+ curr_packinfo->bEthCSSupport = ETH_CS_802_3;
+ break;
+ case eCSPacketIPV6Over802_1QVLAN:
+ case eCSPacketIPV6Over802_3Ethernet:
+ curr_packinfo->bIPCSSupport = IPV6_CS;
+ curr_packinfo->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");
+ curr_packinfo->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,
+ curr_packinfo->bEthCSSupport,
+ curr_packinfo->bIPCSSupport);
+
+ /* Store IP Version used */
+ /* Get The Version Of IP used (IPv6 or IPv4) from CSSpecification field of SF */
+ if (curr_packinfo->bIPCSSupport == IPV6_CS)
+ curr_packinfo->ucIpVersion = IPV6;
+ else
+ curr_packinfo->ucIpVersion = IPV4;
+
+ /* To ensure that the ETH CS code doesn't gets executed if the BS doesn't supports ETH CS */
+ if (!Adapter->bETHCSEnabled)
+ curr_packinfo->bEthCSSupport = 0;
+
+ if (psfLocalSet->u8ServiceClassNameLength > 0 && psfLocalSet->u8ServiceClassNameLength < 32)
+ memcpy(curr_packinfo->ucServiceClassName,
+ psfLocalSet->u8ServiceClassName,
+ psfLocalSet->u8ServiceClassNameLength);
+
+ curr_packinfo->u8QueueType = psfLocalSet->u8ServiceFlowSchedulingType;
+
+ if (curr_packinfo->u8QueueType == BE && curr_packinfo->ucDirection)
+ Adapter->usBestEffortQueueIndex = uiSearchRuleIndex;
+
+ curr_packinfo->ulSFID = ntohl(psfLocalSet->u32SFID);
+
+ curr_packinfo->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)
+ curr_packinfo->bClassifierPriority = TRUE;
+
+ if (psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority)
+ curr_packinfo->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 */
+ apply_phs_rule_to_all_classifiers(Adapter,
+ uiSearchRuleIndex,
+ uVCID,
+ &sPhsRule,
+ &psfCSType->cPhsRule,
+ pstAddIndication);
+ }
+ break;
+ }
+ }
+
+ if (psfLocalSet->u32MaxSustainedTrafficRate == 0) {
+ /* No Rate Limit . Set Max Sustained Traffic Rate to Maximum */
+ curr_packinfo->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 */
+ curr_packinfo->uiMaxAllowedRate = WIMAX_MAX_ALLOWED_RATE;
+ } else {
+ curr_packinfo->uiMaxAllowedRate =
+ ntohl(psfLocalSet->u32MaxSustainedTrafficRate);
+ }
+
+ curr_packinfo->uiMaxLatency = ntohl(psfLocalSet->u32MaximumLatency);
+ if (curr_packinfo->uiMaxLatency == 0) /* 0 should be treated as infinite */
+ curr_packinfo->uiMaxLatency = MAX_LATENCY_ALLOWED;
+
+ if ((curr_packinfo->u8QueueType == ERTPS ||
+ curr_packinfo->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...
+ */
+ curr_packinfo->uiMaxBucketSize =
+ (DEFAULT_UGI_FACTOR*curr_packinfo->uiMaxAllowedRate*UGIValue)/1000;
+
+ if (curr_packinfo->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 = (curr_packinfo->uiMaxLatency/UGIValue + 1);
+
+ if (UGIFactor > DEFAULT_UGI_FACTOR)
+ curr_packinfo->uiMaxBucketSize =
+ (UGIFactor*curr_packinfo->uiMaxAllowedRate*UGIValue)/1000;
+
+ if (curr_packinfo->uiMaxBucketSize > WIMAX_MAX_MTU*8)
+ curr_packinfo->uiMaxBucketSize = WIMAX_MAX_MTU*8;
+ }
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL,
+ "LAT: %d, UGI: %d\n", curr_packinfo->uiMaxLatency,
+ UGIValue);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL,
+ "uiMaxAllowedRate: 0x%x, u32MaxSustainedTrafficRate: 0x%x ,uiMaxBucketSize: 0x%x",
+ curr_packinfo->uiMaxAllowedRate,
+ ntohl(psfLocalSet->u32MaxSustainedTrafficRate),
+ curr_packinfo->uiMaxBucketSize);
+
+ /* copy the extended SF Parameters to Support MIBS */
+ CopyMIBSExtendedSFParameters(Adapter, psfLocalSet, uiSearchRuleIndex);
+
+ /* store header suppression enabled flag per SF */
+ curr_packinfo->bHeaderSuppressionEnabled =
+ !(psfLocalSet->u8RequesttransmissionPolicy &
+ MASK_DISABLE_HEADER_SUPPRESSION);
+
+ kfree(curr_packinfo->pstSFIndication);
+ curr_packinfo->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;
+ struct bcm_add_indication_alt *pstAddIndication;
+ UINT nCurClassifierCnt;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+ pstAddIndication = 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++) {
+ struct bcm_convergence_types *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]: %pM",
+ psfCSType->cCPacketClassificationRule.
+ u8EthernetDestMacAddress);
+
+ 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]: %pM",
+ psfCSType->cCPacketClassificationRule.
+ u8EthernetSourceMACAddress);
+
+ 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%*ph ",
+ 6, psfCSType->cCPacketClassificationRule.
+ u8IPv6FlowLable);
+#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%*ph",
+ 6, pstAddIndication->sfAdmittedSet.u8ServiceClassName);
+
+ 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++) {
+ struct bcm_convergence_types *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%*ph",
+ 3, psfCSType->cCPacketClassificationRule.
+ u8IPTypeOfService);
+ 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%*ph ",
+ 4, psfCSType->cCPacketClassificationRule.
+ u8ProtocolSourcePortRange);
+
+ 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%*ph ",
+ 4, psfCSType->cCPacketClassificationRule.
+ u8ProtocolDestPortRange);
+
+ 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]: %pM",
+ psfCSType->cCPacketClassificationRule.
+ u8EthernetDestMacAddress);
+
+ 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]: %pM",
+ psfCSType->cCPacketClassificationRule.
+ u8EthernetSourceMACAddress);
+
+ 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%*ph",
+ 3, psfCSType->cCPacketClassificationRule.
+ u8Ethertype);
+
+ 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%*ph ",
+ 6, psfCSType->cCPacketClassificationRule.
+ u8IPv6FlowLable);
+#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%*ph",
+ 6, pstAddIndication->sfActiveSet.u8ServiceClassName);
+
+ 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++) {
+ struct bcm_convergence_types *psfCSType = NULL;
+ struct bcm_packet_class_rules *clsRule = NULL;
+
+ psfCSType = &pstAddIndication->sfActiveSet.cConvergenceSLTypes[nIndex];
+ clsRule = &psfCSType->cCPacketClassificationRule;
+
+ 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 ",
+ clsRule->u8ClassifierRulePriority);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u8IPTypeOfServiceLength: 0x%X ",
+ clsRule->u8IPTypeOfServiceLength);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8IPTypeOfService[3]: 0x%X ,0x%X ,0x%X ",
+ clsRule->u8IPTypeOfService[0],
+ clsRule->u8IPTypeOfService[1],
+ clsRule->u8IPTypeOfService[2]);
+
+ for (uiLoopIndex = 0; uiLoopIndex < 1; uiLoopIndex++)
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8Protocol: 0x%X ",
+ clsRule->u8Protocol);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ "u8IPMaskedSourceAddressLength: 0x%X ",
+ clsRule->u8IPMaskedSourceAddressLength);
+
+ for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++)
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ "u8IPMaskedSourceAddress[32]: 0x%X ",
+ clsRule->u8IPMaskedSourceAddress[uiLoopIndex]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ "u8IPDestinationAddressLength: 0x%02X ",
+ clsRule->u8IPDestinationAddressLength);
+
+ for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++)
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8IPDestinationAddress[32]:0x%X ",
+ clsRule->u8IPDestinationAddress[uiLoopIndex]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8ProtocolSourcePortRangeLength: 0x%X ",
+ clsRule->u8ProtocolSourcePortRangeLength);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8ProtocolSourcePortRange[4]: 0x%X ,0x%X ,0x%X ,0x%X ",
+ clsRule->u8ProtocolSourcePortRange[0],
+ clsRule->u8ProtocolSourcePortRange[1],
+ clsRule->u8ProtocolSourcePortRange[2],
+ clsRule->u8ProtocolSourcePortRange[3]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8ProtocolDestPortRangeLength: 0x%X ",
+ clsRule->u8ProtocolDestPortRangeLength);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8ProtocolDestPortRange[4]: 0x%X ,0x%X ,0x%X ,0x%X ",
+ clsRule->u8ProtocolDestPortRange[0],
+ clsRule->u8ProtocolDestPortRange[1],
+ clsRule->u8ProtocolDestPortRange[2],
+ clsRule->u8ProtocolDestPortRange[3]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8EthernetDestMacAddressLength: 0x%X ",
+ clsRule->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",
+ clsRule->u8EthernetDestMacAddress[0],
+ clsRule->u8EthernetDestMacAddress[1],
+ clsRule->u8EthernetDestMacAddress[2],
+ clsRule->u8EthernetDestMacAddress[3],
+ clsRule->u8EthernetDestMacAddress[4],
+ clsRule->u8EthernetDestMacAddress[5]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8EthernetSourceMACAddressLength: 0x%X ",
+ clsRule->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",
+ clsRule->u8EthernetSourceMACAddress[0],
+ clsRule->u8EthernetSourceMACAddress[1],
+ clsRule->u8EthernetSourceMACAddress[2],
+ clsRule->u8EthernetSourceMACAddress[3],
+ clsRule->u8EthernetSourceMACAddress[4],
+ clsRule->u8EthernetSourceMACAddress[5]);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u8EthertypeLength: 0x%X ",
+ clsRule->u8EthertypeLength);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8Ethertype[3]: 0x%X ,0x%X ,0x%X ",
+ clsRule->u8Ethertype[0],
+ clsRule->u8Ethertype[1],
+ clsRule->u8Ethertype[2]);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u16UserPriority: 0x%X ",
+ clsRule->u16UserPriority);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u16VLANID: 0x%X ",
+ clsRule->u16VLANID);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u8AssociatedPHSI: 0x%X ",
+ clsRule->u8AssociatedPHSI);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u16PacketClassificationRuleIndex:0x%X ",
+ clsRule->u16PacketClassificationRuleIndex);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8VendorSpecificClassifierParamLength:0x%X ",
+ clsRule->u8VendorSpecificClassifierParamLength);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL,
+ " u8VendorSpecificClassifierParam[1]:0x%X ",
+ clsRule->u8VendorSpecificClassifierParam[0]);
+#ifdef VERSION_D5
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL,
+ DBG_LVL_ALL, " u8IPv6FlowLableLength: 0x%X ",
+ clsRule->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 ",
+ clsRule->u8IPv6FlowLable[0],
+ clsRule->u8IPv6FlowLable[1],
+ clsRule->u8IPv6FlowLable[2],
+ clsRule->u8IPv6FlowLable[3],
+ clsRule->u8IPv6FlowLable[4],
+ clsRule->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(struct bcm_mini_adapter *Adapter,
+ ULONG ulAddrSFParamSet, PUCHAR pucDestBuffer)
+{
+ UINT nBytesToRead = sizeof(struct bcm_connect_mgr_params);
+
+ 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(struct bcm_mini_adapter *Adapter, PUCHAR pucSrcBuffer,
+ ULONG ulAddrSFParamSet)
+{
+ UINT nBytesToWrite = sizeof(struct bcm_connect_mgr_params);
+ 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(struct bcm_mini_adapter *Adapter,
+ PVOID pvBuffer, UINT *puBufferLength)
+{
+ struct bcm_add_indication_alt *pstAddIndicationAlt = NULL;
+ struct bcm_add_indication *pstAddIndication = NULL;
+ struct bcm_del_request *pstDeletionRequest;
+ UINT uiSearchRuleIndex;
+ ULONG ulSFID;
+
+ pstAddIndicationAlt = 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 = 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(struct bcm_add_indication),
+ GFP_KERNEL);
+ if (pstAddIndication == NULL)
+ return 0;
+
+ /* AUTHORIZED SET */
+ pstAddIndication->psfAuthorizedSet = (struct bcm_connect_mgr_params *)
+ 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 =
+ (struct bcm_connect_mgr_params *) ntohl(
+ (ULONG)pstAddIndication->psfAuthorizedSet);
+
+ if (pstAddIndicationAlt->u8Type == DSA_REQ) {
+ struct bcm_add_request 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(struct bcm_add_request);
+ memcpy(pvBuffer, &AddRequest, sizeof(struct bcm_add_request));
+ 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 = (struct bcm_connect_mgr_params *)
+ 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 =
+ (struct bcm_connect_mgr_params *) ntohl(
+ (ULONG) pstAddIndication->psfAdmittedSet);
+
+ /* ACTIVE SET */
+ pstAddIndication->psfActiveSet = (struct bcm_connect_mgr_params *)
+ 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 =
+ (struct bcm_connect_mgr_params *) ntohl(
+ (ULONG)pstAddIndication->psfActiveSet);
+
+ (*puBufferLength) = sizeof(struct bcm_add_indication);
+ *(struct bcm_add_indication *)pvBuffer = *pstAddIndication;
+ kfree(pstAddIndication);
+ return 1;
+}
+
+static inline struct bcm_add_indication_alt
+*RestoreCmControlResponseMessage(register struct bcm_mini_adapter *Adapter,
+ register PVOID pvBuffer)
+{
+ ULONG ulStatus = 0;
+ struct bcm_add_indication *pstAddIndication = NULL;
+ struct bcm_add_indication_alt *pstAddIndicationDest = NULL;
+
+ pstAddIndication = 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 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(struct bcm_add_indication_alt),
+ GFP_KERNEL);
+
+ if (pstAddIndicationDest) {
+ memset(pstAddIndicationDest, 0,
+ sizeof(struct bcm_add_indication_alt));
+ } 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(struct bcm_mini_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 connection manager parameters): %zx ",
+ sizeof(struct bcm_connect_mgr_params));
+ 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(struct bcm_connect_mgr_params);
+
+ 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(struct bcm_connect_mgr_params);
+ 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(struct bcm_mini_adapter *Adapter,
+ B_UINT16 tid)
+{
+ ULONG dsx_buf;
+ ULONG idx, max_try;
+
+ if ((Adapter->ulTotalTargetBuffersAvailable == 0)
+ || (Adapter->ulFreeTargetBufferCnt == 0)) {
+ ClearTargetDSXBuffer(Adapter, tid, false);
+ return 0;
+ }
+
+ idx = Adapter->ulCurrentTargetBuffer;
+ max_try = Adapter->ulTotalTargetBuffersAvailable;
+ while ((max_try) && (Adapter->astTargetDsxBuffer[idx].valid != 1)) {
+ idx = (idx+1) % Adapter->ulTotalTargetBuffersAvailable;
+ max_try--;
+ }
+
+ if (max_try == 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;
+ }
+
+ dsx_buf = Adapter->astTargetDsxBuffer[idx].ulTargetDsxBuffer;
+ Adapter->astTargetDsxBuffer[idx].valid = 0;
+ Adapter->astTargetDsxBuffer[idx].tid = tid;
+ Adapter->ulFreeTargetBufferCnt--;
+ idx = (idx+1)%Adapter->ulTotalTargetBuffersAvailable;
+ Adapter->ulCurrentTargetBuffer = idx;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0,
+ "GetNextTargetBufferLocation :Returning address %lx tid %d\n",
+ dsx_buf, tid);
+
+ return dsx_buf;
+}
+
+int AllocAdapterDsxBuffer(struct bcm_mini_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(struct bcm_add_indication_alt)
+ + LEADER_SIZE, GFP_KERNEL);
+ if (!Adapter->caDsxReqResp)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int FreeAdapterDsxBuffer(struct bcm_mini_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.
+ */
+bool CmControlResponseMessage(struct bcm_mini_adapter *Adapter, /* <Pointer to the Adapter structure */
+ PVOID pvBuffer /* Starting Address of the Buffer, that contains the AddIndication Data */)
+{
+ struct bcm_connect_mgr_params *psfLocalSet = NULL;
+ struct bcm_add_indication_alt *pstAddIndication = NULL;
+ struct bcm_change_indication *pstChangeIndication = NULL;
+ struct bcm_leader *pLeader = NULL;
+ INT uiSearchRuleIndex = 0;
+ ULONG ulSFID;
+
+ /*
+ * Otherwise the message contains a target address from where we need to
+ * read out the rest of the service flow param structure
+ */
+ pstAddIndication = RestoreCmControlResponseMessage(Adapter, pvBuffer);
+ if (pstAddIndication == NULL) {
+ ClearTargetDSXBuffer(Adapter, ((struct bcm_add_indication *)pvBuffer)->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 = (struct bcm_leader *)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(struct bcm_add_indication_alt);
+ 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);
+ *((struct bcm_add_indication_alt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))
+ = *pstAddIndication;
+ ((struct bcm_add_indication_alt *)&(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(struct bcm_add_indication_alt);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSA ACK TO MAC %d",
+ pLeader->PLength);
+ *((struct bcm_add_indication_alt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))
+ = *pstAddIndication;
+ ((struct bcm_add_indication_alt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSA_ACK;
+ /* FALLTHROUGH */
+ case DSA_ACK:
+ 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(struct bcm_change_indication);
+ pstChangeIndication = (struct bcm_change_indication *)pstAddIndication;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSC RESPONSE TO MAC %d", pLeader->PLength);
+
+ *((struct bcm_change_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *pstChangeIndication;
+ ((struct bcm_change_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSC_RSP;
+
+ CopyBufferToControlPacket(Adapter, (PVOID)Adapter->caDsxReqResp);
+ kfree(pstAddIndication);
+ break;
+ case DSC_RSP:
+ pLeader->PLength = sizeof(struct bcm_change_indication);
+ pstChangeIndication = (struct bcm_change_indication *)pstAddIndication;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSC ACK TO MAC %d", pLeader->PLength);
+ *((struct bcm_change_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *pstChangeIndication;
+ ((struct bcm_change_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSC_ACK;
+ /* FALLTHROUGH */
+ case DSC_ACK:
+ pstChangeIndication = (struct bcm_change_indication *)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:
+ pLeader->PLength = sizeof(struct bcm_del_indication);
+ *((struct bcm_del_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *((struct bcm_del_indication *)pstAddIndication);
+
+ ulSFID = ntohl(((struct bcm_del_indication *)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");
+ ((struct bcm_del_indication *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSD_RSP;
+ CopyBufferToControlPacket(Adapter, (PVOID)Adapter->caDsxReqResp);
+ /* FALLTHROUGH */
+ 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(struct bcm_mini_adapter *Adapter,
+ UINT uiSFId, void __user *user_buffer)
+{
+ int status = 0;
+ struct bcm_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(struct bcm_add_indication_alt))) {
+ 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(struct bcm_mini_adapter *Adapter,
+ PUINT puiBuffer)
+{
+ B_UINT32 u32NumofSFsinMsg = ntohl(*(puiBuffer + 1));
+ struct bcm_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 = (struct bcm_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 {
+ struct bcm_packet_info *packinfo =
+ &Adapter->PackInfo[uiSearchRuleIndex];
+
+ packinfo->usVCID_Value = ntohs(pHostInfo->VCID);
+ packinfo->usCID = ntohs(pHostInfo->newCID);
+ packinfo->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)
+ packinfo->bAuthorizedSet = TRUE;
+ if (pHostInfo->QoSParamSet & 0x2)
+ packinfo->bAdmittedSet = TRUE;
+ if (pHostInfo->QoSParamSet & 0x4) {
+ packinfo->bActiveSet = TRUE;
+ packinfo->bActive = TRUE;
+ }
+ }
+ }
+}
+
+static void restore_endianess_of_pstClassifierEntry(
+ struct bcm_classifier_rule *pstClassifierEntry,
+ enum bcm_ipaddr_context eIpAddrContext)
+{
+ int i;
+ union u_ip_address *stSrc = &pstClassifierEntry->stSrcIpAddress;
+ union u_ip_address *stDest = &pstClassifierEntry->stDestIpAddress;
+
+ for (i = 0; i < MAX_IP_RANGE_LENGTH * 4; i++) {
+ if (eIpAddrContext == eSrcIpAddress) {
+ stSrc->ulIpv6Addr[i] = ntohl(stSrc->ulIpv6Addr[i]);
+ stSrc->ulIpv6Mask[i] = ntohl(stSrc->ulIpv6Mask[i]);
+ } else if (eIpAddrContext == eDestIpAddress) {
+ stDest->ulIpv6Addr[i] = ntohl(stDest->ulIpv6Addr[i]);
+ stDest->ulIpv6Mask[i] = ntohl(stDest->ulIpv6Mask[i]);
+ }
+ }
+}
+
+static void apply_phs_rule_to_all_classifiers(
+ register struct bcm_mini_adapter *Adapter, /* <Pointer to the Adapter structure */
+ register UINT uiSearchRuleIndex, /* <Index of Queue, to which this data belongs */
+ USHORT uVCID,
+ struct bcm_phs_rule *sPhsRule,
+ struct bcm_phs_rules *cPhsRule,
+ struct bcm_add_indication_alt *pstAddIndication)
+{
+ unsigned int uiClassifierIndex = 0;
+ struct bcm_classifier_rule *curr_classifier = NULL;
+
+ if (pstAddIndication->u8Direction == UPLINK_DIR) {
+ for (uiClassifierIndex = 0; uiClassifierIndex < MAX_CLASSIFIERS; uiClassifierIndex++) {
+ curr_classifier =
+ &Adapter->astClassifierTable[uiClassifierIndex];
+ if ((curr_classifier->bUsed) &&
+ (curr_classifier->ulSFID == Adapter->PackInfo[uiSearchRuleIndex].ulSFID) &&
+ (curr_classifier->u8AssociatedPHSI == 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",
+ curr_classifier->uiClassifierRuleIndex,
+ cPhsRule->u8PHSI);
+ /* Update The PHS Rule for this classifier as Associated PHSI id defined */
+
+ /* Copy the PHS Rule */
+ sPhsRule->u8PHSI = cPhsRule->u8PHSI;
+ sPhsRule->u8PHSFLength = cPhsRule->u8PHSFLength;
+ sPhsRule->u8PHSMLength = cPhsRule->u8PHSMLength;
+ sPhsRule->u8PHSS = cPhsRule->u8PHSS;
+ sPhsRule->u8PHSV = cPhsRule->u8PHSV;
+ memcpy(sPhsRule->u8PHSF, cPhsRule->u8PHSF, MAX_PHS_LENGTHS);
+ memcpy(sPhsRule->u8PHSM, 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,
+ curr_classifier->uiClassifierRuleIndex,
+ sPhsRule,
+ curr_classifier->u8AssociatedPHSI);
+
+ /* Update PHS Rule For the Classifier */
+ if (sPhsRule->u8PHSI) {
+ curr_classifier->u32PHSRuleID = sPhsRule->u8PHSI;
+ memcpy(&curr_classifier->sPhsRule, sPhsRule, sizeof(struct bcm_phs_rule));
+ }
+ }
+ }
+ } else {
+ /* Error PHS Rule specified in signaling could not be applied to any classifier */
+
+ /* Copy the PHS Rule */
+ sPhsRule->u8PHSI = cPhsRule->u8PHSI;
+ sPhsRule->u8PHSFLength = cPhsRule->u8PHSFLength;
+ sPhsRule->u8PHSMLength = cPhsRule->u8PHSMLength;
+ sPhsRule->u8PHSS = cPhsRule->u8PHSS;
+ sPhsRule->u8PHSV = cPhsRule->u8PHSV;
+ memcpy(sPhsRule->u8PHSF, cPhsRule->u8PHSF, MAX_PHS_LENGTHS);
+ memcpy(sPhsRule->u8PHSM, 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);
+ }
+}
diff --git a/drivers/staging/bcm/CmHost.h b/drivers/staging/bcm/CmHost.h
new file mode 100644
index 00000000000..0887d3f49e2
--- /dev/null
+++ b/drivers/staging/bcm/CmHost.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ * (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 */
+
+struct bcm_add_indication_alt {
+ u8 u8Type;
+ u8 u8Direction;
+ u16 u16TID;
+ u16 u16CID;
+ u16 u16VCID;
+ struct bcm_connect_mgr_params sfAuthorizedSet;
+ struct bcm_connect_mgr_params sfAdmittedSet;
+ struct bcm_connect_mgr_params sfActiveSet;
+ u8 u8CC; /* < Confirmation Code */
+ u8 u8Padd;
+ u16 u16Padd;
+};
+
+struct bcm_change_indication {
+ u8 u8Type;
+ u8 u8Direction;
+ u16 u16TID;
+ u16 u16CID;
+ u16 u16VCID;
+ struct bcm_connect_mgr_params sfAuthorizedSet;
+ struct bcm_connect_mgr_params sfAdmittedSet;
+ struct bcm_connect_mgr_params sfActiveSet;
+ u8 u8CC; /* < Confirmation Code */
+ u8 u8Padd;
+ u16 u16Padd;
+};
+
+unsigned long StoreCmControlResponseMessage(struct bcm_mini_adapter *Adapter, void *pvBuffer, unsigned int *puBufferLength);
+int AllocAdapterDsxBuffer(struct bcm_mini_adapter *Adapter);
+int FreeAdapterDsxBuffer(struct bcm_mini_adapter *Adapter);
+unsigned long SetUpTargetDsxBuffers(struct bcm_mini_adapter *Adapter);
+bool CmControlResponseMessage(struct bcm_mini_adapter *Adapter, void *pvBuffer);
+
+#pragma pack(pop)
+
+#endif
diff --git a/drivers/staging/bcm/DDRInit.c b/drivers/staging/bcm/DDRInit.c
new file mode 100644
index 00000000000..f1d7cb82fd7
--- /dev/null
+++ b/drivers/staging/bcm/DDRInit.c
@@ -0,0 +1,1239 @@
+#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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04030107},
+ {0x0F007024, 0x02000007},
+ {0x0F007028, 0x02020202},
+ {0x0F00702c, 0x0206060a},
+ {0x0F007030, 0x05000000},
+ {0x0F007034, 0x00000003},
+ {0x0F007038, 0x110a0200},
+ {0x0F00703C, 0x02101010},
+ {0x0F007040, 0x45751200},
+ {0x0F007044, 0x110a0d00},
+ {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 struct bcm_ddr_setting 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 struct bcm_ddr_setting 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, 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},
+ {0x0F007020, 0x04020107},
+ {0x0F007024, 0x00000007},
+ {0x0F007028, 0x01020201},
+ {0x0F00702c, 0x0204040A},
+ {0x0F007030, 0x06000000},
+ {0x0F007034, 0x00000004},
+ {0x0F007038, 0x20080200},
+ {0x0F00703C, 0x02030320},
+ {0x0F007040, 0x6E7F1200},
+ {0x0F007044, 0x01190A00},
+ {0x0F007048, 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 struct bcm_ddr_setting 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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04030107},
+ {0x0F007024, 0x02000007},
+ {0x0F007028, 0x02020202},
+ {0x0F00702c, 0x0206060a},
+ {0x0F007030, 0x05000000},
+ {0x0F007034, 0x00000003},
+ {0x0F007038, 0x130a0200},
+ {0x0F00703C, 0x02101012},
+ {0x0F007040, 0x457D1200},
+ {0x0F007044, 0x11130d00},
+ {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 struct bcm_ddr_setting 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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04020107},
+ {0x0F007024, 0x00000007},
+ {0x0F007028, 0x01020201},
+ {0x0F00702c, 0x0204040A},
+ {0x0F007030, 0x06000000},
+ {0x0F007034, 0x02000004},
+ {0x0F007038, 0x20080200},
+ {0x0F00703C, 0x02030320},
+ {0x0F007040, 0x6E7F1200},
+ {0x0F007044, 0x01190A00},
+ {0x0F007048, 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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04030107},
+ {0x0F007024, 0x02000007},
+ {0x0F007028, 0x02020200},
+ {0x0F00702c, 0x0206060a},
+ {0x0F007030, 0x05000000},
+ {0x0F007034, 0x00000003},
+ {0x0F007038, 0x200a0200},
+ {0x0F00703C, 0x02101020},
+ {0x0F007040, 0x45711200},
+ {0x0F007044, 0x110D0D00},
+ {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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04020107},
+ {0x0F007024, 0x00000007},
+ {0x0F007028, 0x01020200},
+ {0x0F00702c, 0x0204040a},
+ {0x0F007030, 0x06000000},
+ {0x0F007034, 0x00000004},
+ {0x0F007038, 0x1F080200},
+ {0x0F00703C, 0x0203031F},
+ {0x0F007040, 0x6e001200},
+ {0x0F007044, 0x011a0a00},
+ {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 struct bcm_ddr_setting 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 struct bcm_ddr_setting 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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04030107},
+ {0x0F007024, 0x02000007},
+ {0x0F007028, 0x02020200},
+ {0x0F00702c, 0x0206060a},
+ {0x0F007030, 0x05000000},
+ {0x0F007034, 0x00000003},
+ {0x0F007038, 0x190a0200},
+ {0x0F00703C, 0x02101017},
+ {0x0F007040, 0x45171200},
+ {0x0F007044, 0x11290D00},
+ {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 struct bcm_ddr_setting 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},
+ {0x0F007020, 0x04020107},
+ {0x0F007024, 0x00000007},
+ {0x0F007028, 0x01020200},
+ {0x0F00702c, 0x0204040a},
+ {0x0F007030, 0x06000000},
+ {0x0F007034, 0x00000004},
+ {0x0F007038, 0x1F080200},
+ {0x0F00703C, 0x0203031F},
+ {0x0F007040, 0x6e001200},
+ {0x0F007044, 0x011a0a00},
+ {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 struct bcm_ddr_setting 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(struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ddr_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(struct bcm_ddr_setting));
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3LP_DDRSetting100MHz;
+ RegCount = (sizeof(asT3LP_DDRSetting100MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ case DDR_133_MHZ:
+ psDDRSetting = asT3LP_DDRSetting133MHz;
+ RegCount = (sizeof(asT3LP_DDRSetting133MHz)/
+ sizeof(struct bcm_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", __func__, __LINE__);
+ return retval;
+ }
+ uiResetValue |= 0x44;
+ retval = wrmalt(Adapter, (UINT)0x0f000830, &uiResetValue, sizeof(uiResetValue));
+ if (retval < 0) {
+ BCM_DEBUG_PRINT(Adapter, CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __func__, __LINE__);
+ return retval;
+ }
+ }
+ switch (Adapter->DDRSetting) {
+
+
+
+ case DDR_80_MHZ:
+ psDDRSetting = asT3LPB_DDRSetting80MHz;
+ RegCount = (sizeof(asT3B_DDRSetting80MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3LPB_DDRSetting100MHz;
+ RegCount = (sizeof(asT3B_DDRSetting100MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ case DDR_133_MHZ:
+ psDDRSetting = asT3LPB_DDRSetting133MHz;
+ RegCount = (sizeof(asT3B_DDRSetting133MHz)/
+ sizeof(struct bcm_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(struct bcm_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(struct bcm_ddr_setting));
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3_DDRSetting100MHz;
+ RegCount = (sizeof(asT3_DDRSetting100MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ case DDR_133_MHZ:
+ psDDRSetting = asT3_DDRSetting133MHz;
+ RegCount = (sizeof(asT3_DDRSetting133MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ default:
+ return -EINVAL;
+ }
+ case 0xbece0310:
+ {
+ switch (Adapter->DDRSetting) {
+ case DDR_80_MHZ:
+ psDDRSetting = asT3B_DDRSetting80MHz;
+ RegCount = (sizeof(asT3B_DDRSetting80MHz)/
+ sizeof(struct bcm_ddr_setting));
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3B_DDRSetting100MHz;
+ RegCount = (sizeof(asT3B_DDRSetting100MHz)/
+ sizeof(struct bcm_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(struct bcm_ddr_setting));
+ } else {
+ psDDRSetting = asT3B_DDRSetting133MHz;
+ RegCount = (sizeof(asT3B_DDRSetting133MHz)/
+ sizeof(struct bcm_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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __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", __func__, __LINE__);
+ return retval;
+ }
+ }
+
+ }
+ Adapter->bDDRInitDone = TRUE;
+ return retval;
+}
+
+int download_ddr_settings(struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_ddr_setting *psDDRSetting = NULL;
+ ULONG RegCount = 0;
+ unsigned long ul_ddr_setting_load_addr = DDR_DUMP_INTERNAL_DEVICE_MEMORY;
+ UINT value = 0;
+ int retval = STATUS_SUCCESS;
+ bool bOverrideSelfRefresh = false;
+
+ switch (Adapter->chip_id) {
+ case 0xbece3200:
+ switch (Adapter->DDRSetting) {
+ case DDR_80_MHZ:
+ psDDRSetting = asT3LP_DDRSetting80MHz;
+ RegCount = ARRAY_SIZE(asT3LP_DDRSetting80MHz);
+ RegCount -= T3LP_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ psDDRSetting += T3LP_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3LP_DDRSetting100MHz;
+ RegCount = ARRAY_SIZE(asT3LP_DDRSetting100MHz);
+ 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 = ARRAY_SIZE(asT3LP_DDRSetting133MHz);
+ 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 = ARRAY_SIZE(asT3LPB_DDRSetting80MHz);
+ RegCount -= T3LPB_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ psDDRSetting += T3LPB_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3LPB_DDRSetting100MHz;
+ RegCount = ARRAY_SIZE(asT3LPB_DDRSetting100MHz);
+ 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 = ARRAY_SIZE(asT3LPB_DDRSetting133MHz);
+ 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 = ARRAY_SIZE(asT3LPB_DDRSetting160MHz);
+ 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 = ARRAY_SIZE(asT3_DDRSetting80MHz);
+ RegCount -= T3_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ psDDRSetting += T3_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3_DDRSetting100MHz;
+ RegCount = ARRAY_SIZE(asT3_DDRSetting100MHz);
+ RegCount -= T3_SKIP_CLOCK_PROGRAM_DUMP_100MHZ;
+ psDDRSetting += T3_SKIP_CLOCK_PROGRAM_DUMP_100MHZ;
+ break;
+ case DDR_133_MHZ:
+ psDDRSetting = asT3_DDRSetting133MHz;
+ RegCount = ARRAY_SIZE(asT3_DDRSetting133MHz);
+ 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 = ARRAY_SIZE(asT3B_DDRSetting80MHz);
+ RegCount -= T3B_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ psDDRSetting += T3B_SKIP_CLOCK_PROGRAM_DUMP_80MHZ;
+ break;
+ case DDR_100_MHZ:
+ psDDRSetting = asT3B_DDRSetting100MHz;
+ RegCount = ARRAY_SIZE(asT3B_DDRSetting100MHz);
+ 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 = ARRAY_SIZE(asT3B_DDRSetting133MHz);
+ 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", __func__, __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", __func__, __LINE__);
+ return retval;
+ }
+
+ ul_ddr_setting_load_addr += sizeof(ULONG);
+ RegCount *= (sizeof(struct bcm_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", __func__, __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", __func__, __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 00000000000..b0196fce925
--- /dev/null
+++ b/drivers/staging/bcm/DDRInit.h
@@ -0,0 +1,9 @@
+#ifndef _DDR_INIT_H_
+#define _DDR_INIT_H_
+
+
+
+int ddr_init(struct bcm_mini_adapter *psAdapter);
+int download_ddr_settings(struct bcm_mini_adapter *psAdapter);
+
+#endif
diff --git a/drivers/staging/bcm/Debug.h b/drivers/staging/bcm/Debug.h
new file mode 100644
index 00000000000..7b331215c1a
--- /dev/null
+++ b/drivers/staging/bcm/Debug.h
@@ -0,0 +1,242 @@
+/*
+ * Debug.h
+ *
+ * Dynamic (runtime) debug framework implementation.
+ * -kaiwan.
+ */
+#ifndef _DEBUG_H
+#define _DEBUG_H
+#include <linux/string.h>
+#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.
+ */
+#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 */
+#define NUMTYPES 4
+
+/* -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)
+
+/* 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
+#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)
+
+/* 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.
+ */
+struct bcm_user_debug_state {
+ unsigned int Subtype, Type;
+ unsigned int OnOff;
+/* unsigned int debug_level; future expansion */
+} __packed;
+
+/* ---Kernel-space mapping of Debug State */
+struct bcm_debug_state {
+ unsigned int 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 !
+ */
+ unsigned int subtype[(NUMTYPES*2)+1];
+ unsigned int debug_level;
+};
+/* 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) \
+ pr_debug("%s:\n", string); \
+ else \
+ pr_debug("%s:\n" 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]))) { \
+ pr_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 00000000000..495fe3dc514
--- /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(struct bcm_mini_adapter *Adapter, struct sk_buff *skb)
+{
+ struct bcm_tarang_data *pTarang = NULL;
+ bool HighPriorityMessage = false;
+ struct sk_buff *newPacket = NULL;
+ CHAR cntrl_msg_mask_bit = 0;
+ bool 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(struct bcm_mini_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)
+{
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_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(struct bcm_mibs_dropped_cntrl_msg));
+
+ }
+ return STATUS_SUCCESS;
+}
+
+
diff --git a/drivers/staging/bcm/HostMIBSInterface.h b/drivers/staging/bcm/HostMIBSInterface.h
new file mode 100644
index 00000000000..f922ac49b70
--- /dev/null
+++ b/drivers/staging/bcm/HostMIBSInterface.h
@@ -0,0 +1,192 @@
+#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
+
+union bcm_mibs_ip_addr {
+ struct {
+ /* Source Ip Address Range */
+ unsigned long ulIpv4Addr[MIBS_MAX_IP_RANGE_LENGTH];
+ /* Source Ip Mask Address Range */
+ unsigned long ulIpv4Mask[MIBS_MAX_IP_RANGE_LENGTH];
+ };
+ struct {
+ /* Source Ip Address Range */
+ unsigned long ulIpv6Addr[MIBS_MAX_IP_RANGE_LENGTH * 4];
+ /* Source Ip Mask Address Range */
+ unsigned long ulIpv6Mask[MIBS_MAX_IP_RANGE_LENGTH * 4];
+ };
+ struct {
+ unsigned char ucIpv4Address[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IP_LENGTH_OF_ADDRESS];
+ unsigned char ucIpv4Mask[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IP_LENGTH_OF_ADDRESS];
+ };
+ struct {
+ unsigned char ucIpv6Address[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IPV6_ADDRESS_SIZEINBYTES];
+ unsigned char ucIpv6Mask[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IPV6_ADDRESS_SIZEINBYTES];
+ };
+};
+
+struct bcm_mibs_host_info {
+ u64 GoodTransmits;
+ u64 GoodReceives;
+ /* this to keep track of the Tx and Rx MailBox Registers. */
+ unsigned long NumDesUsed;
+ unsigned long CurrNumFreeDesc;
+ unsigned long PrevNumFreeDesc;
+ /* to keep track the no of byte received */
+ unsigned long PrevNumRcevBytes;
+ unsigned long CurrNumRcevBytes;
+ /* QOS Related */
+ unsigned long BEBucketSize;
+ unsigned long rtPSBucketSize;
+ unsigned long LastTxQueueIndex;
+ bool TxOutofDescriptors;
+ bool TimerActive;
+ u32 u32TotalDSD;
+ u32 aTxPktSizeHist[MIBS_MAX_HIST_ENTRIES];
+ u32 aRxPktSizeHist[MIBS_MAX_HIST_ENTRIES];
+};
+
+struct bcm_mibs_classifier_rule {
+ unsigned long ulSFID;
+ unsigned char ucReserved[2];
+ u16 uiClassifierRuleIndex;
+ bool bUsed;
+ unsigned short usVCID_Value;
+ u8 u8ClassifierRulePriority;
+ union bcm_mibs_ip_addr stSrcIpAddress;
+ /* IP Source Address Length */
+ unsigned char ucIPSourceAddressLength;
+ union bcm_mibs_ip_addr stDestIpAddress;
+ /* IP Destination Address Length */
+ unsigned char ucIPDestinationAddressLength;
+ unsigned char ucIPTypeOfServiceLength;
+ unsigned char ucTosLow;
+ unsigned char ucTosHigh;
+ unsigned char ucTosMask;
+ unsigned char ucProtocolLength;
+ unsigned char ucProtocol[MIBS_MAX_PROTOCOL_LENGTH];
+ unsigned short usSrcPortRangeLo[MIBS_MAX_PORT_RANGE];
+ unsigned short usSrcPortRangeHi[MIBS_MAX_PORT_RANGE];
+ unsigned char ucSrcPortRangeLength;
+ unsigned short usDestPortRangeLo[MIBS_MAX_PORT_RANGE];
+ unsigned short usDestPortRangeHi[MIBS_MAX_PORT_RANGE];
+ unsigned char ucDestPortRangeLength;
+ bool bProtocolValid;
+ bool bTOSValid;
+ bool bDestIpValid;
+ bool bSrcIpValid;
+ unsigned char ucDirection;
+ bool bIpv6Protocol;
+ u32 u32PHSRuleID;
+};
+
+struct bcm_mibs_phs_rule {
+ unsigned long ulSFID;
+ u8 u8PHSI;
+ u8 u8PHSFLength;
+ u8 u8PHSF[MIBS_MAX_PHS_LENGTHS];
+ u8 u8PHSMLength;
+ u8 u8PHSM[MIBS_MAX_PHS_LENGTHS];
+ u8 u8PHSS;
+ u8 u8PHSV;
+ u8 reserved[5];
+ long PHSModifiedBytes;
+ unsigned long PHSModifiedNumPackets;
+ unsigned long PHSErrorNumPackets;
+};
+
+struct bcm_mibs_parameters {
+ u32 wmanIfSfid;
+ u32 wmanIfCmnCpsSfState;
+ u32 wmanIfCmnCpsMaxSustainedRate;
+ u32 wmanIfCmnCpsMaxTrafficBurst;
+ u32 wmanIfCmnCpsMinReservedRate;
+ u32 wmanIfCmnCpsToleratedJitter;
+ u32 wmanIfCmnCpsMaxLatency;
+ u32 wmanIfCmnCpsFixedVsVariableSduInd;
+ u32 wmanIfCmnCpsSduSize;
+ u32 wmanIfCmnCpsSfSchedulingType;
+ u32 wmanIfCmnCpsArqEnable;
+ u32 wmanIfCmnCpsArqWindowSize;
+ u32 wmanIfCmnCpsArqBlockLifetime;
+ u32 wmanIfCmnCpsArqSyncLossTimeout;
+ u32 wmanIfCmnCpsArqDeliverInOrder;
+ u32 wmanIfCmnCpsArqRxPurgeTimeout;
+ u32 wmanIfCmnCpsArqBlockSize;
+ u32 wmanIfCmnCpsMinRsvdTolerableRate;
+ u32 wmanIfCmnCpsReqTxPolicy;
+ u32 wmanIfCmnSfCsSpecification;
+ u32 wmanIfCmnCpsTargetSaid;
+};
+
+struct bcm_mibs_table {
+ unsigned long ulSFID;
+ unsigned short usVCID_Value;
+ unsigned int uiThreshold;
+ u8 u8TrafficPriority;
+ bool bValid;
+ bool bActive;
+ bool bActivateRequestSent;
+ u8 u8QueueType;
+ unsigned int uiMaxBucketSize;
+ unsigned int uiCurrentQueueDepthOnTarget;
+ unsigned int uiCurrentBytesOnHost;
+ unsigned int uiCurrentPacketsOnHost;
+ unsigned int uiDroppedCountBytes;
+ unsigned int uiDroppedCountPackets;
+ unsigned int uiSentBytes;
+ unsigned int uiSentPackets;
+ unsigned int uiCurrentDrainRate;
+ unsigned int uiThisPeriodSentBytes;
+ u64 liDrainCalculated;
+ unsigned int uiCurrentTokenCount;
+ u64 liLastUpdateTokenAt;
+ unsigned int uiMaxAllowedRate;
+ unsigned int NumOfPacketsSent;
+ unsigned char ucDirection;
+ unsigned short usCID;
+ struct bcm_mibs_parameters stMibsExtServiceFlowTable;
+ unsigned int uiCurrentRxRate;
+ unsigned int uiThisPeriodRxBytes;
+ unsigned int uiTotalRxBytes;
+ unsigned int uiTotalTxBytes;
+};
+
+struct bcm_mibs_dropped_cntrl_msg {
+ unsigned long cm_responses;
+ unsigned long cm_control_newdsx_multiclassifier_resp;
+ unsigned long link_control_resp;
+ unsigned long status_rsp;
+ unsigned long stats_pointer_resp;
+ unsigned long idle_mode_status;
+ unsigned long auth_ss_host_msg;
+ unsigned long low_priority_message;
+};
+
+struct bcm_host_stats_mibs {
+ struct bcm_mibs_host_info stHostInfo;
+ struct bcm_mibs_classifier_rule astClassifierTable[MIBS_MAX_CLASSIFIERS];
+ struct bcm_mibs_table astSFtable[MIBS_MAX_SERVICEFLOWS];
+ struct bcm_mibs_phs_rule astPhsRulesTable[MIBS_MAX_PHSRULES];
+ struct bcm_mibs_dropped_cntrl_msg stDroppedAppCntrlMsgs;
+};
+
+#endif
diff --git a/drivers/staging/bcm/IPv6Protocol.c b/drivers/staging/bcm/IPv6Protocol.c
new file mode 100644
index 00000000000..cd160670e02
--- /dev/null
+++ b/drivers/staging/bcm/IPv6Protocol.c
@@ -0,0 +1,451 @@
+#include "headers.h"
+
+static bool MatchSrcIpv6Address(struct bcm_classifier_rule *pstClassifierRule,
+ struct bcm_ipv6_hdr *pstIpv6Header);
+static bool MatchDestIpv6Address(struct bcm_classifier_rule *pstClassifierRule,
+ struct bcm_ipv6_hdr *pstIpv6Header);
+static VOID DumpIpv6Header(struct bcm_ipv6_hdr *pstIpv6Header);
+
+static UCHAR *GetNextIPV6ChainedHeader(UCHAR **ppucPayload,
+ UCHAR *pucNextHeader, bool *bParseDone, USHORT *pusPayloadLength)
+{
+ UCHAR *pucRetHeaderPtr = NULL;
+ UCHAR *pucPayloadPtr = NULL;
+ USHORT usNextHeaderOffset = 0 ;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+ if ((ppucPayload == NULL) || (*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(struct bcm_ipv6_options_hdr);
+ }
+ break;
+
+ case IPV6HDR_TYPE_ROUTING:
+ {
+ struct bcm_ipv6_routing_hdr *pstIpv6RoutingHeader;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG,
+ DBG_LVL_ALL, "\nIPv6 Routing Header");
+ pstIpv6RoutingHeader = (struct bcm_ipv6_routing_hdr *)pucPayloadPtr;
+ usNextHeaderOffset += sizeof(struct bcm_ipv6_routing_hdr);
+ 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(struct bcm_ipv6_fragment_hdr);
+
+ }
+ break;
+ case IPV6HDR_TYPE_DESTOPTS:
+ {
+ struct bcm_ipv6_dest_options_hdr *pstIpv6DestOptsHdr = (struct bcm_ipv6_dest_options_hdr *)pucPayloadPtr;
+ int nTotalOptions = pstIpv6DestOptsHdr->ucHdrExtLen;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG,
+ DBG_LVL_ALL,
+ "\nIPv6 DestOpts Header Header");
+ usNextHeaderOffset += sizeof(struct bcm_ipv6_dest_options_hdr);
+ usNextHeaderOffset += nTotalOptions * IPV6_DESTOPTS_HDR_OPTIONSIZE ;
+
+ }
+ break;
+ case IPV6HDR_TYPE_AUTHENTICATION:
+ {
+ struct bcm_ipv6_authentication_hdr *pstIpv6AuthHdr = (struct bcm_ipv6_authentication_hdr *)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, "\nICMP 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;
+ bool bDone = false;
+ UCHAR ucHeaderType = 0;
+ UCHAR *pucNextHeader = NULL;
+ struct bcm_mini_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;
+}
+
+
+/*
+ * Arg 1 struct bcm_mini_adapter *Adapter is a pointer ot the driver contorl structure
+ * Arg 2 PVOID pcIpHeader is a pointer to the IP header of the packet
+ */
+USHORT IpVersion6(struct bcm_mini_adapter *Adapter, PVOID pcIpHeader,
+ struct bcm_classifier_rule *pstClassifierRule)
+{
+ USHORT ushDestPort = 0;
+ USHORT ushSrcPort = 0;
+ UCHAR ucNextProtocolAboveIP = 0;
+ struct bcm_ipv6_hdr *pstIpv6Header = NULL;
+ bool bClassificationSucceed = false;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG,
+ DBG_LVL_ALL, "IpVersion6 ==========>\n");
+
+ pstIpv6Header = pcIpHeader;
+
+ DumpIpv6Header(pstIpv6Header);
+
+ /*
+ * Try to get the next higher layer protocol
+ * and the Ports Nos if TCP or UDP
+ */
+ ucNextProtocolAboveIP = GetIpv6ProtocolPorts((UCHAR *)(pcIpHeader + sizeof(struct bcm_ipv6_hdr)),
+ &ushSrcPort,
+ &ushDestPort,
+ pstIpv6Header->usPayloadLength,
+ pstIpv6Header->ucNextHeader);
+
+ do {
+ if (pstClassifierRule->ucDirection == 0) {
+ /*
+ * 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 (bClassificationSucceed == TRUE) {
+ INT iMatchedSFQueueIndex = 0;
+ iMatchedSFQueueIndex = SearchSfid(Adapter, pstClassifierRule->ulSFID);
+ if (iMatchedSFQueueIndex >= NO_OF_QUEUES) {
+ bClassificationSucceed = false;
+ } else {
+ if (Adapter->PackInfo[iMatchedSFQueueIndex].bActive == false)
+ bClassificationSucceed = false;
+ }
+ }
+
+ return bClassificationSucceed;
+}
+
+
+static bool MatchSrcIpv6Address(struct bcm_classifier_rule *pstClassifierRule,
+ struct bcm_ipv6_hdr *pstIpv6Header)
+{
+ UINT uiLoopIndex = 0;
+ UINT uiIpv6AddIndex = 0;
+ UINT uiIpv6AddrNoLongWords = 4;
+ ULONG aulSrcIP[4];
+ struct bcm_mini_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 (uiCountIPSrcAddresses == 0)
+ return TRUE;
+
+
+ /* First Convert the Ip Address in the packet to Host Endian order */
+ for (uiIpv6AddIndex = 0; uiIpv6AddIndex < uiIpv6AddrNoLongWords; uiIpv6AddIndex++)
+ aulSrcIP[uiIpv6AddIndex] = ntohl(pstIpv6Header->ulSrcIpAddress[uiIpv6AddIndex]);
+
+ for (uiLoopIndex = 0; uiLoopIndex < uiCountIPSrcAddresses; uiLoopIndex += uiIpv6AddrNoLongWords) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ "\n Src Ipv6 Address In Received Packet :\n ");
+ DumpIpv6Address(aulSrcIP);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ "\n Src Ipv6 Mask In Classifier Rule:\n");
+ DumpIpv6Address(&pstClassifierRule->stSrcIpAddress.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; uiIpv6AddIndex < uiIpv6AddrNoLongWords; uiIpv6AddIndex++) {
+ if ((pstClassifierRule->stSrcIpAddress.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 bool MatchDestIpv6Address(struct bcm_classifier_rule *pstClassifierRule,
+ struct bcm_ipv6_hdr *pstIpv6Header)
+{
+ UINT uiLoopIndex = 0;
+ UINT uiIpv6AddIndex = 0;
+ UINT uiIpv6AddrNoLongWords = 4;
+ ULONG aulDestIP[4];
+ struct bcm_mini_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 (uiCountIPDestinationAddresses == 0)
+ return TRUE;
+
+
+ /* First Convert the Ip Address in the packet to Host Endian order */
+ for (uiIpv6AddIndex = 0; uiIpv6AddIndex < uiIpv6AddrNoLongWords; uiIpv6AddIndex++)
+ aulDestIP[uiIpv6AddIndex] = ntohl(pstIpv6Header->ulDestIpAddress[uiIpv6AddIndex]);
+
+ for (uiLoopIndex = 0; uiLoopIndex < uiCountIPDestinationAddresses; uiLoopIndex += uiIpv6AddrNoLongWords) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ "\n Destination Ipv6 Address In Received Packet :\n ");
+ DumpIpv6Address(aulDestIP);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ "\n Destination Ipv6 Mask In Classifier Rule :\n");
+ DumpIpv6Address(&pstClassifierRule->stDestIpAddress.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; uiIpv6AddIndex < uiIpv6AddrNoLongWords; uiIpv6AddIndex++) {
+ if ((pstClassifierRule->stDestIpAddress.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;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ for (uiIpv6AddIndex = 0; uiIpv6AddIndex < uiIpv6AddrNoLongWords; uiIpv6AddIndex++) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ ":%lx", puIpv6Address[uiIpv6AddIndex]);
+ }
+
+}
+
+static VOID DumpIpv6Header(struct bcm_ipv6_hdr *pstIpv6Header)
+{
+ UCHAR ucVersion;
+ UCHAR ucPrio;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL,
+ "----Ipv6 Header---");
+ ucVersion = pstIpv6Header->ucVersionPrio & 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 00000000000..96b36a579af
--- /dev/null
+++ b/drivers/staging/bcm/IPv6ProtocolHdr.h
@@ -0,0 +1,85 @@
+#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
+
+struct bcm_ipv6_hdr {
+ unsigned char ucVersionPrio;
+ unsigned char aucFlowLabel[3];
+ unsigned short usPayloadLength;
+ unsigned char ucNextHeader;
+ unsigned char ucHopLimit;
+ unsigned long ulSrcIpAddress[4];
+ unsigned long ulDestIpAddress[4];
+};
+
+struct bcm_ipv6_routing_hdr {
+ unsigned char ucNextHeader;
+ unsigned char ucRoutingType;
+ unsigned char ucNumAddresses;
+ unsigned char ucNextAddress;
+ unsigned long ulReserved;
+};
+
+struct bcm_ipv6_fragment_hdr {
+ unsigned char ucNextHeader;
+ unsigned char ucReserved;
+ unsigned short usFragmentOffset;
+ unsigned long ulIdentification;
+};
+
+struct bcm_ipv6_dest_options_hdr {
+ unsigned char ucNextHeader;
+ unsigned char ucHdrExtLen;
+ unsigned char ucDestOptions[6];
+};
+
+struct bcm_ipv6_options_hdr {
+ unsigned char ucNextHeader;
+ unsigned char ucMisc[3];
+ unsigned long ulJumboPayloadLen;
+};
+
+struct bcm_ipv6_authentication_hdr {
+ unsigned char ucNextHeader;
+ unsigned char ucLength;
+ unsigned short usReserved;
+ unsigned long ulSecurityParametersIndex;
+};
+
+enum bcm_ipaddr_context {
+ eSrcIpAddress,
+ eDestIpAddress
+};
+
+/* Function Prototypes */
+
+unsigned short IpVersion6(struct bcm_mini_adapter *Adapter, /* < Pointer to the driver control structure */
+ void *pcIpHeader, /* <Pointer to the IP Hdr of the packet */
+ struct bcm_classifier_rule *pstClassifierRule);
+
+void DumpIpv6Address(unsigned long *puIpv6Address);
+
+extern bool MatchSrcPort(struct bcm_classifier_rule *pstClassifierRule, unsigned short ushSrcPort);
+extern bool MatchDestPort(struct bcm_classifier_rule *pstClassifierRule, unsigned short ushSrcPort);
+extern bool MatchProtocol(struct bcm_classifier_rule *pstClassifierRule, unsigned char ucProtocol);
+
+#endif
diff --git a/drivers/staging/bcm/InterfaceAdapter.h b/drivers/staging/bcm/InterfaceAdapter.h
new file mode 100644
index 00000000000..06a6b18bca4
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceAdapter.h
@@ -0,0 +1,79 @@
+#ifndef _INTERFACE_ADAPTER_H
+#define _INTERFACE_ADAPTER_H
+
+struct bcm_bulk_endpoint_in {
+ char *bulk_in_buffer;
+ size_t bulk_in_size;
+ unsigned char bulk_in_endpointAddr;
+ unsigned int bulk_in_pipe;
+};
+
+struct bcm_bulk_endpoint_out {
+ unsigned char bulk_out_buffer;
+ size_t bulk_out_size;
+ unsigned char bulk_out_endpointAddr;
+ unsigned int bulk_out_pipe;
+ /* this is used when int out endpoint is used as bulk out end point */
+ unsigned char int_out_interval;
+};
+
+struct bcm_intr_endpoint_in {
+ char *int_in_buffer;
+ size_t int_in_size;
+ unsigned char int_in_endpointAddr;
+ unsigned char int_in_interval;
+ unsigned int int_in_pipe;
+};
+
+struct bcm_intr_endpoint_out {
+ char *int_out_buffer;
+ size_t int_out_size;
+ unsigned char int_out_endpointAddr;
+ unsigned char int_out_interval;
+ unsigned int int_out_pipe;
+};
+
+struct bcm_usb_tcb {
+ struct urb *urb;
+ void *psIntfAdapter;
+ bool bUsed;
+};
+
+struct bcm_usb_rcb {
+ struct urb *urb;
+ void *psIntfAdapter;
+ bool bUsed;
+};
+
+/*
+ * This is the interface specific Sub-Adapter
+ * Structure.
+ */
+struct bcm_interface_adapter {
+ struct usb_device *udev;
+ struct usb_interface *interface;
+ /* Bulk endpoint in info */
+ struct bcm_bulk_endpoint_in sBulkIn;
+ /* Bulk endpoint out info */
+ struct bcm_bulk_endpoint_out sBulkOut;
+ /* Interrupt endpoint in info */
+ struct bcm_intr_endpoint_in sIntrIn;
+ /* Interrupt endpoint out info */
+ struct bcm_intr_endpoint_out sIntrOut;
+ unsigned long ulInterruptData[2];
+ struct urb *psInterruptUrb;
+ struct bcm_usb_tcb asUsbTcb[MAXIMUM_USB_TCB];
+ struct bcm_usb_rcb asUsbRcb[MAXIMUM_USB_RCB];
+ atomic_t uNumTcbUsed;
+ atomic_t uCurrTcb;
+ atomic_t uNumRcbUsed;
+ atomic_t uCurrRcb;
+ struct bcm_mini_adapter *psAdapter;
+ bool bFlashBoot;
+ bool bHighSpeedDevice;
+ bool bSuspended;
+ bool bPreparingForBusSuspend;
+ struct work_struct usbSuspendWork;
+};
+
+#endif
diff --git a/drivers/staging/bcm/InterfaceDld.c b/drivers/staging/bcm/InterfaceDld.c
new file mode 100644
index 00000000000..e1925bdc127
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceDld.c
@@ -0,0 +1,316 @@
+#include "headers.h"
+
+int InterfaceFileDownload(PVOID arg, struct file *flp, unsigned int on_chip_loc)
+{
+ /* unsigned int reg = 0; */
+ mm_segment_t oldfs = {0};
+ int errno = 0, len = 0; /* ,is_config_file = 0 */
+ loff_t pos = 0;
+ struct bcm_interface_adapter *psIntfAdapter = arg;
+ /* struct bcm_mini_adapter *Adapter = psIntfAdapter->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)
+ errno = len;
+ else
+ errno = 0;
+ 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)
+ 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;
+ struct bcm_interface_adapter *psIntfAdapter = arg;
+ int bytes;
+
+ buff = kzalloc(MAX_TRANSFER_CTRL_BYTE_USB, GFP_DMA);
+ buff_readback = kzalloc(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;
+
+ 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)
+ errno = len;
+ else
+ errno = 0;
+ break;
+ }
+
+ bytes = InterfaceRDM(psIntfAdapter, on_chip_loc,
+ buff_readback, len);
+ if (bytes < 0) {
+ Status = bytes;
+ goto exit;
+ }
+ reg++;
+ if ((len-sizeof(unsigned int)) < 4) {
+ if (memcmp(buff_readback, buff, len)) {
+ Status = -EIO;
+ goto exit;
+ }
+ } else {
+ len -= 4;
+
+ while (len) {
+ if (*(unsigned int *)&buff_readback[len] !=
+ *(unsigned int *)&buff[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(struct bcm_mini_adapter *Adapter,
+ struct bcm_firmware_info *psFwInfo)
+{
+ int retval = STATUS_SUCCESS;
+ B_UINT32 value = 0;
+
+ if (Adapter->pstargetparams == NULL) {
+ Adapter->pstargetparams =
+ kmalloc(sizeof(struct bcm_target_params), GFP_KERNEL);
+ if (Adapter->pstargetparams == NULL)
+ return -ENOMEM;
+ }
+
+ if (psFwInfo->u32FirmwareLength != sizeof(struct bcm_target_params))
+ 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)
+ 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)
+ 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)
+ return retval;
+ }
+
+ retval = buffDnldVerify(Adapter, (PUCHAR)Adapter->pstargetparams,
+ sizeof(struct bcm_target_params), 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;
+}
+
+int bcm_ioctl_fw_download(struct bcm_mini_adapter *Adapter,
+ struct bcm_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. */
+ 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)
+ return -ENOMEM;
+
+ retval = copy_from_user(buff,
+ psFwInfo->pvMappedFirmwareAddress,
+ psFwInfo->u32FirmwareLength);
+ if (retval != STATUS_SUCCESS) {
+ retval = -EFAULT;
+ goto error;
+ }
+
+ retval = buffDnldVerify(Adapter,
+ buff,
+ psFwInfo->u32FirmwareLength,
+ psFwInfo->u32StartingAddress);
+
+ if (retval != STATUS_SUCCESS)
+ goto error;
+ }
+
+error:
+ kfree(buff);
+ return retval;
+}
+
+static INT buffDnld(struct bcm_mini_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)
+ break;
+ u32StartingAddress += len;
+ u32FirmwareLength -= len;
+ mappedbuffer += len;
+ }
+ return retval;
+}
+
+static INT buffRdbkVerify(struct bcm_mini_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)
+ 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;
+ break;
+ }
+
+ if (memcmp(readbackbuff, mappedbuffer, len) != 0) {
+ pr_err("%s() failed. The firmware doesn't match what was written",
+ __func__);
+ retval = -EIO;
+ }
+
+ u32StartingAddress += len;
+ u32FirmwareLength -= len;
+ mappedbuffer += len;
+
+ } /* end of while (u32FirmwareLength && !retval) */
+ kfree(readbackbuff);
+ return retval;
+}
+
+INT buffDnldVerify(struct bcm_mini_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)
+ goto error;
+
+ status = buffRdbkVerify(Adapter, mappedbuffer,
+ u32FirmwareLength, u32StartingAddress);
+ if (status != STATUS_SUCCESS)
+ goto error;
+error:
+ return status;
+}
diff --git a/drivers/staging/bcm/InterfaceIdleMode.c b/drivers/staging/bcm/InterfaceIdleMode.c
new file mode 100644
index 00000000000..c84ee494f55
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceIdleMode.c
@@ -0,0 +1,275 @@
+#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 struct bcm_mini_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 struct bcm_mini_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(struct bcm_mini_adapter *Adapter,
+ unsigned int *puiBuffer)
+{
+ int status = STATUS_SUCCESS;
+ unsigned int uiRegRead = 0;
+ int bytes;
+
+ if (ntohl(*puiBuffer) == GO_TO_IDLE_MODE_PAYLOAD) {
+ if (ntohl(*(puiBuffer+1)) == 0) {
+
+ status = wrmalt(Adapter, SW_ABORT_IDLEMODE_LOC,
+ &uiRegRead, sizeof(uiRegRead));
+ if (status)
+ 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)
+ 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;
+ return status;
+ }
+ /* clear on read Register */
+ bytes = rdmalt(Adapter, DEVICE_INT_OUT_EP_REG1,
+ &uiRegRead, sizeof(uiRegRead));
+ if (bytes < 0) {
+ status = bytes;
+ return status;
+ }
+ }
+
+ /* 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)
+ return status;
+
+ uiRegRead = 0;
+
+ 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;
+ return status;
+ }
+
+
+ uiRegRead |= (1<<17);
+
+ status = wrmalt(Adapter, HPM_CONFIG_MSW,
+ &uiRegRead, sizeof(uiRegRead));
+ if (status)
+ return status;
+ }
+ SendIdleModeResponse(Adapter);
+ }
+ } else if (ntohl(*puiBuffer) == IDLE_MODE_SF_UPDATE_MSG) {
+ OverrideServiceFlowParams(Adapter, puiBuffer);
+ }
+ return status;
+}
+
+static int InterfaceAbortIdlemode(struct bcm_mini_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};
+ struct bcm_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);
+
+ if ((Adapter->ulPowerSaveMode ==
+ DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING) ||
+ (Adapter->ulPowerSaveMode ==
+ DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE)) {
+ /* write the SW abort pattern. */
+ status = wrmalt(Adapter, SW_ABORT_IDLEMODE_LOC,
+ &Pattern, sizeof(Pattern));
+ if (status)
+ 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)
+ 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)
+ 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 (time_after(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 (time_before(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)
+ return status;
+ }
+ return status;
+}
+int InterfaceIdleModeWakeup(struct bcm_mini_adapter *Adapter)
+{
+ if (Adapter->bTriedToWakeUpFromlowPowerMode) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS,
+ IDLE_MODE, DBG_LVL_ALL,
+ "Wake up already attempted.. ignoring\n");
+ } else {
+ Adapter->bTriedToWakeUpFromlowPowerMode = TRUE;
+ InterfaceAbortIdlemode(Adapter, Adapter->usIdleModePattern);
+
+ }
+ return 0;
+}
+
+void InterfaceHandleShutdownModeWakeup(struct bcm_mini_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)
+ return;
+ }
+
+ else {
+
+/* clear Interrupt EP registers. */
+ bytes = rdmalt(Adapter,
+ DEVICE_INT_OUT_EP_REG0,
+ &uiRegVal, sizeof(uiRegVal));
+ if (bytes < 0) {
+ Status = bytes;
+ return;
+ }
+
+ bytes = rdmalt(Adapter,
+ DEVICE_INT_OUT_EP_REG1,
+ &uiRegVal, sizeof(uiRegVal));
+ if (bytes < 0) {
+ Status = bytes;
+ return;
+ }
+ }
+}
+
diff --git a/drivers/staging/bcm/InterfaceIdleMode.h b/drivers/staging/bcm/InterfaceIdleMode.h
new file mode 100644
index 00000000000..2ef64003aa8
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceIdleMode.h
@@ -0,0 +1,15 @@
+#ifndef _INTERFACE_IDLEMODE_H
+#define _INTERFACE_IDLEMODE_H
+
+INT InterfaceIdleModeWakeup(struct bcm_mini_adapter *Adapter);
+
+INT InterfaceIdleModeRespond(struct bcm_mini_adapter *Adapter,
+ unsigned int *puiBuffer);
+
+VOID InterfaceWriteIdleModeWakePattern(struct bcm_mini_adapter *Adapter);
+
+INT InterfaceWakeUp(struct bcm_mini_adapter *Adapter);
+
+VOID InterfaceHandleShutdownModeWakeup(struct bcm_mini_adapter *Adapter);
+#endif
+
diff --git a/drivers/staging/bcm/InterfaceInit.c b/drivers/staging/bcm/InterfaceInit.c
new file mode 100644
index 00000000000..7c04c73e3bc
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceInit.c
@@ -0,0 +1,717 @@
+#include "headers.h"
+#include <linux/usb/ch9.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_SYM) },
+ { 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) },
+ { USB_DEVICE(BCM_USB_VENDOR_ID_ZTE, BCM_USB_PRODUCT_ID_ZTE_226) },
+ { USB_DEVICE(BCM_USB_VENDOR_ID_ZTE, BCM_USB_PRODUCT_ID_ZTE_326) },
+ { }
+};
+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(struct bcm_interface_adapter *Adapter);
+
+static void InterfaceAdapterFree(struct bcm_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(struct bcm_mini_adapter *Adapter)
+{
+ u32 ulReg;
+ int bytes;
+ struct bcm_interface_adapter *interfaceAdapter;
+
+ /* 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);
+ interfaceAdapter =
+ (struct bcm_interface_adapter *)(Adapter->pvInterfaceAdapter);
+ if (interfaceAdapter->bHighSpeedDevice) {
+ 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, &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, &ulReg);
+ ulReg &= 0x0101FFFF;
+ BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1FC, 4, TRUE);
+
+ /* Update length field if required. Also make the string NULL terminated. */
+
+ ReadBeceemEEPROM(Adapter, 0xA8, &ulReg);
+ if ((ulReg&0x00FF0000)>>16 > 0x30) {
+ ulReg = (ulReg&0xFF00FFFF)|(0x30<<16);
+ BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0xA8, 4, TRUE);
+ }
+ ReadBeceemEEPROM(Adapter, 0x148, &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;
+ struct bcm_mini_adapter *psAdapter;
+ struct bcm_interface_adapter *psIntfAdapter;
+ struct net_device *ndev;
+
+ /* Reserve one extra queue for the bit-bucket */
+ ndev = alloc_etherdev_mq(sizeof(struct bcm_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(struct bcm_interface_adapter),
+ GFP_KERNEL);
+ if (psIntfAdapter == NULL) {
+ 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)
+{
+ struct bcm_interface_adapter *psIntfAdapter = usb_get_intfdata(intf);
+ struct bcm_mini_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(struct bcm_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(struct bcm_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 (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 int InterfaceAdapterInit(struct bcm_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;
+ bool 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) {
+ 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 &&
+ !usb_endpoint_is_int_out(endpoint)) ||
+ (!psIntfAdapter->bHighSpeedDevice &&
+ !usb_endpoint_is_bulk_out(endpoint))) {
+ 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 &&
+ 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 (!usb_endpoint_is_int_out(endpoint)) {
+ 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 &&
+ 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 &&
+ 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 &&
+ 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)
+ return -EINVAL;
+ }
+
+ if (!psIntfAdapter->sIntrOut.int_out_endpointAddr &&
+ 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)
+ 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)
+{
+ struct bcm_interface_adapter *psIntfAdapter = usb_get_intfdata(intf);
+
+ psIntfAdapter->bSuspended = TRUE;
+
+ if (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)
+{
+ struct bcm_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)
+{
+ int retval;
+
+ pr_info("%s: %s, %s\n", DRV_NAME, DRV_DESCRIPTION, DRV_VERSION);
+ pr_info("%s\n", DRV_COPYRIGHT);
+
+ bcm_class = class_create(THIS_MODULE, DRV_NAME);
+ if (IS_ERR(bcm_class)) {
+ pr_err(DRV_NAME ": could not create class\n");
+ return PTR_ERR(bcm_class);
+ }
+
+ retval = usb_register(&usbbcm_driver);
+ if (retval < 0) {
+ pr_err(DRV_NAME ": could not register usb driver\n");
+ class_destroy(bcm_class);
+ return retval;
+ }
+ return 0;
+}
+
+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 00000000000..ffa6e9667ec
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceInit.h
@@ -0,0 +1,26 @@
+#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_SYM 0x15E
+#define BCM_USB_PRODUCT_ID_1901 0xe017
+#define BCM_USB_PRODUCT_ID_226 0x0132 /* not sure if this is valid */
+#define BCM_USB_PRODUCT_ID_ZTE_226 0x172
+#define BCM_USB_PRODUCT_ID_ZTE_326 0x173 /* ZTE AX326 */
+#define BCM_USB_PRODUCT_ID_ZTE_TU25 0x0007
+
+#define BCM_USB_MINOR_BASE 192
+
+int InterfaceInitialize(void);
+
+int InterfaceExit(void);
+
+int usbbcm_worker_thread(struct bcm_interface_adapter *psIntfAdapter);
+
+#endif
diff --git a/drivers/staging/bcm/InterfaceIsr.c b/drivers/staging/bcm/InterfaceIsr.c
new file mode 100644
index 00000000000..b9f8a7aa24f
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceIsr.c
@@ -0,0 +1,190 @@
+#include "headers.h"
+
+
+static void read_int_callback(struct urb *urb/*, struct pt_regs *regs*/)
+{
+ int status = urb->status;
+ struct bcm_interface_adapter *psIntfAdapter =
+ (struct bcm_interface_adapter *)urb->context;
+ struct bcm_mini_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) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, INTF_INIT,
+ DBG_LVL_ALL, "Device has Got Removed.");
+ return;
+ }
+
+ if ((Adapter->bPreparingForLowPowerMode && Adapter->bDoSuspend) ||
+ 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;
+ }
+
+ 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 (!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(struct bcm_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(struct bcm_interface_adapter *psIntfAdapter)
+{
+ INT status = 0;
+
+ if (!(psIntfAdapter->psAdapter->device_removed ||
+ psIntfAdapter->psAdapter->bEndPointHalted ||
+ psIntfAdapter->bSuspended ||
+ psIntfAdapter->bPreparingForBusSuspend ||
+ 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 inturb %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 00000000000..3073bd71cfe
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceIsr.h
@@ -0,0 +1,15 @@
+#ifndef _INTERFACE_ISR_H
+#define _INTERFACE_ISR_H
+
+int CreateInterruptUrb(struct bcm_interface_adapter *psIntfAdapter);
+
+
+INT StartInterruptUrb(struct bcm_interface_adapter *psIntfAdapter);
+
+
+VOID InterfaceEnableInterrupt(struct bcm_mini_adapter *Adapter);
+
+VOID InterfaceDisableInterrupt(struct bcm_mini_adapter *Adapter);
+
+#endif
+
diff --git a/drivers/staging/bcm/InterfaceMacros.h b/drivers/staging/bcm/InterfaceMacros.h
new file mode 100644
index 00000000000..7001caff9e2
--- /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 00000000000..4173fd7d671
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceMisc.c
@@ -0,0 +1,217 @@
+#include "headers.h"
+
+int InterfaceRDM(struct bcm_interface_adapter *psIntfAdapter,
+ unsigned int addr,
+ void *buff,
+ int len)
+{
+ int bytes;
+
+ if (!psIntfAdapter)
+ 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;
+
+ bytes = usb_control_msg(psIntfAdapter->udev,
+ usb_rcvctrlpipe(psIntfAdapter->udev, 0),
+ 0x02,
+ 0xC2,
+ (addr & 0xFFFF),
+ ((addr >> 16) & 0xFFFF),
+ buff,
+ len,
+ 5000);
+
+ if (-ENODEV == bytes)
+ psIntfAdapter->psAdapter->device_removed = TRUE;
+
+ if (bytes < 0)
+ BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, RDM, DBG_LVL_ALL, "RDM failed status :%d", bytes);
+ 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(struct bcm_interface_adapter *psIntfAdapter,
+ unsigned int addr,
+ void *buff,
+ int len)
+{
+ int retval = 0;
+
+ if (!psIntfAdapter)
+ 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;
+
+ retval = usb_control_msg(psIntfAdapter->udev,
+ usb_sndctrlpipe(psIntfAdapter->udev, 0),
+ 0x01,
+ 0x42,
+ (addr & 0xFFFF),
+ ((addr >> 16) & 0xFFFF),
+ buff,
+ len,
+ 5000);
+
+ if (-ENODEV == retval)
+ psIntfAdapter->psAdapter->device_removed = TRUE;
+
+ if (retval < 0) {
+ BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, WRM, DBG_LVL_ALL, "WRM failed status :%d", retval);
+ 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(void *arg,
+ unsigned int addr,
+ void *buff,
+ int len)
+{
+ return InterfaceRDM((struct bcm_interface_adapter *)arg, addr, buff, len);
+}
+
+int BcmWRM(void *arg,
+ unsigned int addr,
+ void *buff,
+ int len)
+{
+ return InterfaceWRM((struct bcm_interface_adapter *)arg, addr, buff, len);
+}
+
+int Bcm_clear_halt_of_endpoints(struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_interface_adapter *psIntfAdapter = (struct bcm_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(struct bcm_interface_adapter *psIntfAdapter)
+{
+ struct urb *tempUrb = NULL;
+ unsigned int 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) {
+ 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)
+{
+ struct bcm_interface_adapter *psIntfAdapter = NULL;
+ struct usb_interface *intf = NULL;
+ psIntfAdapter = container_of(work, struct bcm_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 00000000000..efb68604615
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceMisc.h
@@ -0,0 +1,42 @@
+#ifndef __INTERFACE_MISC_H
+#define __INTERFACE_MISC_H
+
+INT
+InterfaceRDM(struct bcm_interface_adapter *psIntfAdapter,
+ UINT addr,
+ PVOID buff,
+ INT len);
+
+INT
+InterfaceWRM(struct bcm_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(struct bcm_mini_adapter *Adapter);
+
+VOID Bcm_kill_all_URBs(struct bcm_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 00000000000..11008173f91
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceRx.c
@@ -0,0 +1,233 @@
+#include "headers.h"
+
+static int SearchVcid(struct bcm_mini_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 struct bcm_usb_rcb *
+GetBulkInRcb(struct bcm_interface_adapter *psIntfAdapter)
+{
+ struct bcm_usb_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;
+ bool bHeaderSupressionEnabled = false;
+ int QueueIndex = NO_OF_QUEUES + 1;
+ UINT uiIndex = 0;
+ int process_done = 1;
+ struct bcm_usb_rcb *pRcb = (struct bcm_usb_rcb *)urb->context;
+ struct bcm_interface_adapter *psIntfAdapter = pRcb->psIntfAdapter;
+ struct bcm_mini_adapter *Adapter = psIntfAdapter->psAdapter;
+ struct bcm_leader *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);
+ 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(struct bcm_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(struct bcm_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(struct bcm_interface_adapter *psIntfAdapter, struct bcm_usb_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 struct bcm_mini_adapter *Adapter - Miniport Adapter Context
+
+
+
+Return: TRUE - If Rx was successful.
+ Other - If an error occurred.
+*/
+
+bool InterfaceRx(struct bcm_interface_adapter *psIntfAdapter)
+{
+ USHORT RxDescCount = NUM_RX_DESC - atomic_read(&psIntfAdapter->uNumRcbUsed);
+ struct bcm_usb_rcb *pRcb = NULL;
+
+ 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;
+ }
+ 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 00000000000..b4e858bcda3
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceRx.h
@@ -0,0 +1,7 @@
+#ifndef _INTERFACE_RX_H
+#define _INTERFACE_RX_H
+
+bool InterfaceRx(struct bcm_interface_adapter *Adapter);
+
+#endif
+
diff --git a/drivers/staging/bcm/InterfaceTx.c b/drivers/staging/bcm/InterfaceTx.c
new file mode 100644
index 00000000000..ea7707b8e60
--- /dev/null
+++ b/drivers/staging/bcm/InterfaceTx.c
@@ -0,0 +1,174 @@
+#include "headers.h"
+
+/*this is transmit call-back(BULK OUT)*/
+static void write_bulk_callback(struct urb *urb/*, struct pt_regs *regs*/)
+{
+ struct bcm_usb_tcb *pTcb = (struct bcm_usb_tcb *)urb->context;
+ struct bcm_interface_adapter *psIntfAdapter = pTcb->psIntfAdapter;
+ struct bcm_link_request *pControlMsg = (struct bcm_link_request *)urb->transfer_buffer;
+ struct bcm_mini_adapter *psAdapter = psIntfAdapter->psAdapter;
+ bool bpowerDownMsg = false;
+ struct bcm_mini_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 struct bcm_usb_tcb *GetBulkOutTcb(struct bcm_interface_adapter *psIntfAdapter)
+{
+ struct bcm_usb_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(struct bcm_interface_adapter *psIntfAdapter, struct bcm_usb_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)
+{
+ struct bcm_usb_tcb *pTcb = NULL;
+
+ struct bcm_interface_adapter *psIntfAdapter = 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 00000000000..273147577c1
--- /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 00000000000..797f862b90c
--- /dev/null
+++ b/drivers/staging/bcm/Ioctl.h
@@ -0,0 +1,226 @@
+#ifndef _IOCTL_H_
+#define _IOCTL_H_
+
+struct bcm_rdm_buffer {
+ unsigned long Register;
+ unsigned long Length;
+} __packed;
+
+struct bcm_wrm_buffer {
+ unsigned long Register;
+ unsigned long Length;
+ unsigned char Data[4];
+} __packed;
+
+struct bcm_ioctl_buffer {
+ void __user *InputBuffer;
+ unsigned long InputLength;
+ void __user *OutputBuffer;
+ unsigned long OutputLength;
+} __packed;
+
+struct bcm_gpio_info {
+ unsigned int uiGpioNumber; /* valid numbers 0-15 */
+ unsigned int uiGpioValue; /* 1 set ; 0 not set */
+} __packed;
+
+struct bcm_user_thread_req {
+ /* 0->Inactivate LED thread. */
+ /* 1->Activate the LED thread */
+ unsigned int ThreadState;
+} __packed;
+
+#define LED_THREAD_ACTIVATION_REQ 1
+#define BCM_IOCTL 'k'
+#define IOCTL_SEND_CONTROL_MESSAGE _IOW(BCM_IOCTL, 0x801, int)
+#define IOCTL_BCM_REGISTER_WRITE _IOW(BCM_IOCTL, 0x802, int)
+#define IOCTL_BCM_REGISTER_READ _IOR(BCM_IOCTL, 0x803, int)
+#define IOCTL_BCM_COMMON_MEMORY_WRITE _IOW(BCM_IOCTL, 0x804, int)
+#define IOCTL_BCM_COMMON_MEMORY_READ _IOR(BCM_IOCTL, 0x805, int)
+#define IOCTL_GET_CONTROL_MESSAGE _IOR(BCM_IOCTL, 0x806, int)
+#define IOCTL_BCM_FIRMWARE_DOWNLOAD _IOW(BCM_IOCTL, 0x807, int)
+#define IOCTL_BCM_SET_SEND_VCID _IOW(BCM_IOCTL, 0x808, int)
+#define IOCTL_BCM_SWITCH_TRANSFER_MODE _IOW(BCM_IOCTL, 0x809, int)
+#define IOCTL_LINK_REQ _IOW(BCM_IOCTL, 0x80A, int)
+#define IOCTL_RSSI_LEVEL_REQ _IOW(BCM_IOCTL, 0x80B, int)
+#define IOCTL_IDLE_REQ _IOW(BCM_IOCTL, 0x80C, int)
+#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, struct bcm_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, struct bcm_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, struct bcm_ioctl_buffer)
+#define IOCTL_BCM_GPIO_MODE_REQUEST _IOW(BCM_IOCTL, 0x82E, struct bcm_ioctl_buffer)
+
+enum bcm_interface_type {
+ BCM_MII,
+ BCM_CARDBUS,
+ BCM_USB,
+ BCM_SDIO,
+ BCM_PCMCIA
+};
+
+struct bcm_driver_info {
+ enum bcm_nvm_type u32NVMType;
+ unsigned int MaxRDMBufferSize;
+ enum bcm_interface_type u32InterfaceType;
+ unsigned int u32DSDStartOffset;
+ unsigned int u32RxAlignmentCorrection;
+ unsigned int u32Reserved[10];
+};
+
+struct bcm_nvm_readwrite {
+ void __user *pBuffer;
+ uint32_t uiOffset;
+ uint32_t uiNumBytes;
+ bool bVerify;
+};
+
+struct bcm_bulk_wrm_buffer {
+ unsigned long Register;
+ unsigned long SwapEndian;
+ unsigned long Values[1];
+};
+
+enum bcm_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
+};
+
+/*
+ * Structure used for READ/WRITE Flash Map2.x
+ */
+struct bcm_flash2x_readwrite {
+ enum bcm_flash2x_section_val Section; /* which section has to be read/written */
+ u32 offset; /* Offset within Section. */
+ u32 numOfBytes; /* NOB from the offset */
+ u32 bVerify;
+ void __user *pDataBuff; /* Buffer for reading/writing */
+};
+
+/*
+ * 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.
+ */
+
+struct bcm_flash2x_copy_section {
+ enum bcm_flash2x_section_val SrcSection;
+ enum bcm_flash2x_section_val DstSection;
+ u32 offset;
+ u32 numOfBytes;
+};
+
+/*
+ * This section provide the complete bitmap of the Flash.
+ * using this map lib/APP will issue 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
+ */
+
+struct bcm_flash2x_bitmap {
+ unsigned char ISO_IMAGE1;
+ unsigned char ISO_IMAGE2;
+ unsigned char DSD0;
+ unsigned char DSD1;
+ unsigned char DSD2;
+ unsigned char VSA0;
+ unsigned char VSA1;
+ unsigned char VSA2;
+ unsigned char SCSI;
+ unsigned char CONTROL_SECTION;
+ /* Reserved for future use */
+ unsigned char Reserved0;
+ unsigned char Reserved1;
+ unsigned char Reserved2;
+};
+
+struct bcm_time_elapsed {
+ u64 ul64TimeElapsedSinceNetEntry;
+ u32 uiReserved[4];
+};
+
+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
+};
+
+struct bcm_gpio_multi_info {
+ unsigned int uiGPIOCommand; /* 1 for set and 0 for get */
+ unsigned int uiGPIOMask; /* set the correspondig bit to 1 to access GPIO */
+ unsigned int uiGPIOValue; /* 0 or 1; value to be set when command is 1. */
+} __packed;
+
+struct bcm_gpio_multi_mode {
+ unsigned int uiGPIOMode; /* 1 for OUT mode, 0 for IN mode */
+ unsigned int uiGPIOMask; /* GPIO mask to set mode */
+} __packed;
+
+#endif
diff --git a/drivers/staging/bcm/Kconfig b/drivers/staging/bcm/Kconfig
new file mode 100644
index 00000000000..8acf4b24a7c
--- /dev/null
+++ b/drivers/staging/bcm/Kconfig
@@ -0,0 +1,6 @@
+config BCM_WIMAX
+ tristate "Beceem BCS200/BCS220-3 and BCSM250 wimax support"
+ depends on USB && NET
+ 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 00000000000..f95b06713a2
--- /dev/null
+++ b/drivers/staging/bcm/LeakyBucket.c
@@ -0,0 +1,317 @@
+/**********************************************************************
+* 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 struct bcm_mini_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(struct bcm_mini_adapter *Adapter, struct bcm_packet_info *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(struct bcm_mini_adapter *Adapter,/**<Logical Adapter*/
+ struct bcm_packet_info *psSF, /**<Queue identifier*/
+ struct sk_buff *Packet) /**<Pointer to the packet to be sent*/
+{
+ INT Status = STATUS_FAILURE;
+ UINT uiIndex = 0, PktLen = 0;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, SEND_QUEUE, DBG_LVL_ALL, "=====>");
+ 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(struct bcm_mini_adapter *Adapter, struct bcm_packet_info *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 += ((struct bcm_leader *)pControlPacket)->PLength;
+ psSF->uiSentPackets++;
+ atomic_dec(&Adapter->TotalPacketCount);
+ psSF->uiCurrentBytesOnHost -= ((struct bcm_leader *)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(struct bcm_mini_adapter *Adapter)
+{
+ UINT uiPrevTotalCount = 0;
+ int iIndex = 0;
+
+ bool 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 00000000000..dc01e3016d4
--- /dev/null
+++ b/drivers/staging/bcm/Macros.h
@@ -0,0 +1,352 @@
+/*************************************
+* Macros.h
+**************************************/
+#ifndef __MACROS_H__
+#define __MACROS_H__
+
+#define TX_TIMER_PERIOD 10 /*10 msec*/
+#define MAX_CLASSIFIERS 100
+#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(struct bcm_leader)
+#define MAC_ADDR_REQ_SIZE sizeof(struct bcm_packettosend)
+#define SS_INFO_REQ_SIZE sizeof(struct bcm_packettosend)
+#define CM_REQUEST_SIZE (LEADER_SIZE + sizeof(stLocalSFChangeRequest))
+#define IDLE_REQ_SIZE sizeof(struct bcm_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
+
+struct bcm_link_state {
+ unsigned char ucLinkStatus;
+ unsigned char bIdleMode;
+ unsigned char bShutdownMode;
+};
+
+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
+};
+
+enum bcm_phs_dsc_action {
+ eAddPHSRule = 0,
+ eSetPHSRule,
+ eDeletePHSRule,
+ eDeleteAllPHSRules
+};
+
+#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
+
+#define CONFIG_BEGIN_ADDR 0xBF60B000
+
+#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
+enum bcm_nvm_type {
+ NVM_AUTODETECT = 0,
+ NVM_EEPROM,
+ NVM_FLASH,
+ NVM_UNKNOWN
+};
+
+enum bcm_pmu_modes {
+ HYBRID_MODE_7C = 0,
+ INTERNAL_MODE_6 = 1,
+ HYBRID_MODE_6 = 2
+};
+
+#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 00000000000..652b7f87737
--- /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 00000000000..7b2fa0f4a2e
--- /dev/null
+++ b/drivers/staging/bcm/Misc.c
@@ -0,0 +1,1580 @@
+#include "headers.h"
+
+static int BcmFileDownload(struct bcm_mini_adapter *Adapter, const char *path, unsigned int loc);
+static void doPowerAutoCorrection(struct bcm_mini_adapter *psAdapter);
+static void HandleShutDownModeRequest(struct bcm_mini_adapter *Adapter, PUCHAR pucBuffer);
+static int bcm_parse_target_params(struct bcm_mini_adapter *Adapter);
+static void beceem_protocol_reset(struct bcm_mini_adapter *Adapter);
+
+static void default_wimax_protocol_initialize(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_adapter *Adapter, const char *path)
+{
+ struct file *flp = filp_open(path, O_RDONLY, S_IRWXU);
+ 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(struct bcm_mini_adapter *Adapter, const char *path, unsigned int loc)
+{
+ int errorno = 0;
+ struct file *flp = NULL;
+ struct timeval tv = {0};
+
+ flp = open_firmware_file(Adapter, path);
+ if (!flp) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Unable to Open %s\n", path);
+ return -ENOENT;
+ }
+ 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)file_inode(flp)->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;
+ }
+ vfs_llseek(flp, 0, 0);
+ 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:
+ filp_close(flp, NULL);
+ 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 spinlock, as it it already acquired.
+ * @return SUCCESS/FAILURE.
+ * Arguments:
+ * Logical Adapter
+ * Control Packet Buffer
+ */
+int CopyBufferToControlPacket(struct bcm_mini_adapter *Adapter, void *ioBuffer)
+{
+ struct bcm_leader *pLeader = NULL;
+ int Status = 0;
+ unsigned char *ctrl_buff;
+ unsigned int pktlen = 0;
+ struct bcm_link_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 = (struct bcm_link_request *)ioBuffer;
+ pLeader = (struct bcm_leader *)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 (Adapter->bShutStatus == TRUE) {
+ 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 (Adapter->IdleMode == TRUE) {
+ /* 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];
+
+ if (!ctrl_buff) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "mem allocation Failed");
+ return -ENOMEM;
+ }
+
+ 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 (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, ((struct bcm_add_indication_alt *)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);
+ *(struct bcm_leader *)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);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "<====");
+ return Status;
+}
+
+/******************************************************************
+* 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(struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_link_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(struct bcm_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(struct bcm_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(struct bcm_mini_adapter *Adapter, void *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", (unsigned int)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(struct bcm_mini_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(struct bcm_mini_adapter *Adapter)
+{
+ int status = 0, NVMAccess = 0, lowPwrAbortMsg = 0;
+ struct timeval tv;
+ struct bcm_link_request 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((struct bcm_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((struct bcm_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(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int uiLoopIndex = 0;
+ unsigned int uiIndex = 0;
+ unsigned int uiClsfrIndex = 0;
+ struct bcm_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: %*ph\n",
+ 4, Adapter->PackInfo[uiLoopIndex].
+ ucServiceClassName);
+/* 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(struct bcm_mini_adapter *ps_adapter)
+{
+ int retval = STATUS_SUCCESS;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_interface_adapter *psIntfAdapter = NULL;
+ unsigned int value = 0, uiResetValue = 0;
+ int bytes;
+
+ psIntfAdapter = ((struct bcm_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, "Resetting 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(struct bcm_mini_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(struct bcm_mini_adapter *ps_adapter)
+{
+ int status;
+ unsigned int 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(struct bcm_target_params),
+ 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(struct bcm_mini_adapter *Adapter)
+{
+ struct file *flp = NULL;
+ char *buff;
+ int len = 0;
+
+ buff = kmalloc(BUFFER_1K, GFP_KERNEL);
+ if (!buff)
+ return -ENOMEM;
+
+ Adapter->pstargetparams = kmalloc(sizeof(struct bcm_target_params), 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;
+ }
+ len = kernel_read(flp, 0, buff, BUFFER_1K);
+ filp_close(flp, NULL);
+
+ if (len != sizeof(struct bcm_target_params)) {
+ 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;
+ return -ENOENT;
+ }
+
+ /* Check for autolink in config params */
+ /*
+ * Values in Adapter->pstargetparams are in network byte order
+ */
+ memcpy(Adapter->pstargetparams, buff, sizeof(struct bcm_target_params));
+ kfree(buff);
+ beceem_parse_target_struct(Adapter);
+ return STATUS_SUCCESS;
+}
+
+void beceem_parse_target_struct(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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 = (enum bcm_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(struct bcm_mini_adapter *psAdapter)
+{
+ unsigned int 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);
+ }
+}
+
+static void convertEndian(unsigned char rwFlag, unsigned int *puiBuffer, unsigned int uiByteCount)
+{
+ unsigned int uiIndex = 0;
+
+ if (RWM_WRITE == rwFlag) {
+ for (uiIndex = 0; uiIndex < (uiByteCount/sizeof(unsigned int)); uiIndex++)
+ puiBuffer[uiIndex] = htonl(puiBuffer[uiIndex]);
+ } else {
+ for (uiIndex = 0; uiIndex < (uiByteCount/sizeof(unsigned int)); uiIndex++)
+ puiBuffer[uiIndex] = ntohl(puiBuffer[uiIndex]);
+ }
+}
+
+int rdm(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, PCHAR pucBuff, size_t sSize)
+{
+ return Adapter->interface_rdm(Adapter->pvInterfaceAdapter,
+ uiAddress, pucBuff, sSize);
+}
+
+int wrm(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, PCHAR pucBuff, size_t sSize)
+{
+ int iRetVal;
+
+ iRetVal = Adapter->interface_wrm(Adapter->pvInterfaceAdapter,
+ uiAddress, pucBuff, sSize);
+ return iRetVal;
+}
+
+int wrmalt(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, unsigned int *pucBuff, size_t size)
+{
+ convertEndian(RWM_WRITE, pucBuff, size);
+ return wrm(Adapter, uiAddress, (PUCHAR)pucBuff, size);
+}
+
+int rdmalt(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, unsigned int *pucBuff, size_t size)
+{
+ int uiRetVal = 0;
+
+ uiRetVal = rdm(Adapter, uiAddress, (PUCHAR)pucBuff, size);
+ convertEndian(RWM_READ, (unsigned int *)pucBuff, size);
+
+ return uiRetVal;
+}
+
+int wrmWithLock(struct bcm_mini_adapter *Adapter, unsigned int 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(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, unsigned int *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(struct bcm_mini_adapter *Adapter, unsigned int uiAddress, unsigned int *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(struct bcm_mini_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, (unsigned int *)&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(struct bcm_mini_adapter *Adapter)
+{
+ struct bcm_link_request stShutdownResponse;
+ unsigned int NVMAccess = 0, lowPwrAbortMsg = 0;
+ unsigned int Status = 0;
+
+ memset(&stShutdownResponse, 0, sizeof(struct bcm_link_request));
+ 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((struct bcm_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((struct bcm_interface_adapter *)(Adapter->pvInterfaceAdapter));
+ }
+}
+
+static void HandleShutDownModeRequest(struct bcm_mini_adapter *Adapter, PUCHAR pucBuffer)
+{
+ unsigned int 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(struct bcm_mini_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;
+}
+
+struct bcm_classifier_rule *GetFragIPClsEntry(struct bcm_mini_adapter *Adapter, USHORT usIpIdentification, ULONG SrcIP)
+{
+ unsigned int 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(struct bcm_mini_adapter *Adapter, struct bcm_fragmented_packet_info *psFragPktInfo)
+{
+ unsigned int uiIndex = 0;
+ for (uiIndex = 0; uiIndex < MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES; uiIndex++) {
+ if (!Adapter->astFragmentedPktClassifierTable[uiIndex].bUsed) {
+ memcpy(&Adapter->astFragmentedPktClassifierTable[uiIndex], psFragPktInfo, sizeof(struct bcm_fragmented_packet_info));
+ break;
+ }
+ }
+}
+
+void DelFragIPClsEntry(struct bcm_mini_adapter *Adapter, USHORT usIpIdentification, ULONG SrcIp)
+{
+ unsigned int 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(struct bcm_fragmented_packet_info));
+ }
+}
+
+void update_per_cid_rx(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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(struct bcm_mini_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, (unsigned int *)uibuff, sizeof(unsigned int) * 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(struct bcm_mini_adapter *Adapter, unsigned int 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(struct bcm_mini_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(struct bcm_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(struct bcm_mibs_parameters));
+ }
+}
diff --git a/drivers/staging/bcm/PHSDefines.h b/drivers/staging/bcm/PHSDefines.h
new file mode 100644
index 00000000000..cd78ee4ffa2
--- /dev/null
+++ b/drivers/staging/bcm/PHSDefines.h
@@ -0,0 +1,94 @@
+#ifndef BCM_PHS_DEFINES_H
+#define BCM_PHS_DEFINES_H
+
+#define PHS_INVALID_TABLE_INDEX 0xffffffff
+#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_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
+
+struct bcm_phs_rule {
+ u8 u8PHSI;
+ u8 u8PHSFLength;
+ u8 u8PHSF[MAX_PHS_LENGTHS];
+ u8 u8PHSMLength;
+ u8 u8PHSM[MAX_PHS_LENGTHS];
+ u8 u8PHSS;
+ u8 u8PHSV;
+ u8 u8RefCnt;
+ u8 bUnclassifiedPHSRule;
+ u8 u8Reserved[3];
+ long PHSModifiedBytes;
+ unsigned long PHSModifiedNumPackets;
+ unsigned long PHSErrorNumPackets;
+};
+
+enum bcm_phs_classifier_context {
+ eActiveClassifierRuleContext,
+ eOldClassifierRuleContext
+};
+
+struct bcm_phs_classifier_entry {
+ u8 bUsed;
+ u16 uiClassifierRuleId;
+ u8 u8PHSI;
+ struct bcm_phs_rule *pstPhsRule;
+ u8 bUnclassifiedPHSRule;
+};
+
+struct bcm_phs_classifier_table {
+ u16 uiTotalClassifiers;
+ struct bcm_phs_classifier_entry stActivePhsRulesList[MAX_PHSRULE_PER_SF];
+ struct bcm_phs_classifier_entry stOldPhsRulesList[MAX_PHSRULE_PER_SF];
+ u16 uiOldestPhsRuleIndex;
+};
+
+struct bcm_phs_entry {
+ u8 bUsed;
+ u16 uiVcid;
+ struct bcm_phs_classifier_table *pstClassifierTable;
+};
+
+struct bcm_phs_table {
+ u16 uiTotalServiceFlows;
+ struct bcm_phs_entry stSFList[MAX_SERVICEFLOWS];
+};
+
+struct bcm_phs_extension {
+ /* PHS Specific data */
+ struct bcm_phs_table *pstServiceFlowPhsRulesTable;
+ void *CompressedTxBuffer;
+ void *UnCompressedRxBuffer;
+};
+
+#endif
diff --git a/drivers/staging/bcm/PHSModule.c b/drivers/staging/bcm/PHSModule.c
new file mode 100644
index 00000000000..07c5a0bae1e
--- /dev/null
+++ b/drivers/staging/bcm/PHSModule.c
@@ -0,0 +1,1448 @@
+#include "headers.h"
+
+static UINT CreateSFToClassifierRuleMapping(B_UINT16 uiVcid, B_UINT16 uiClsId, struct bcm_phs_table *psServiceFlowTable, struct bcm_phs_rule *psPhsRule, B_UINT8 u8AssociatedPHSI);
+
+static UINT CreateClassiferToPHSRuleMapping(B_UINT16 uiVcid, B_UINT16 uiClsId, struct bcm_phs_entry *pstServiceFlowEntry, struct bcm_phs_rule *psPhsRule, B_UINT8 u8AssociatedPHSI);
+
+static UINT CreateClassifierPHSRule(B_UINT16 uiClsId, struct bcm_phs_classifier_table *psaClassifiertable, struct bcm_phs_rule *psPhsRule, enum bcm_phs_classifier_context eClsContext, B_UINT8 u8AssociatedPHSI);
+
+static UINT UpdateClassifierPHSRule(B_UINT16 uiClsId, struct bcm_phs_classifier_entry *pstClassifierEntry, struct bcm_phs_classifier_table *psaClassifiertable, struct bcm_phs_rule *psPhsRule, B_UINT8 u8AssociatedPHSI);
+
+static bool ValidatePHSRuleComplete(struct bcm_phs_rule *psPhsRule);
+
+static bool DerefPhsRule(B_UINT16 uiClsId, struct bcm_phs_classifier_table *psaClassifiertable, struct bcm_phs_rule *pstPhsRule);
+
+static UINT GetClassifierEntry(struct bcm_phs_classifier_table *pstClassifierTable, B_UINT32 uiClsid, enum bcm_phs_classifier_context eClsContext, struct bcm_phs_classifier_entry **ppstClassifierEntry);
+
+static UINT GetPhsRuleEntry(struct bcm_phs_classifier_table *pstClassifierTable, B_UINT32 uiPHSI, enum bcm_phs_classifier_context eClsContext, struct bcm_phs_rule **ppstPhsRule);
+
+static void free_phs_serviceflow_rules(struct bcm_phs_table *psServiceFlowRulesTable);
+
+static int phs_compress(struct bcm_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,
+ struct bcm_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 suppresses the header by invoking PHS exported compress routine.
+ * The header data after suppression is copied back to the NDIS_PACKET.
+ *
+ * Input parameters: IN struct bcm_mini_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 occurred.
+ */
+
+int PHSTransmit(struct bcm_mini_adapter *Adapter,
+ struct sk_buff **pPacket,
+ USHORT Vcid,
+ B_UINT16 uiClassifierRuleID,
+ bool 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.ucaHdrSuppressionInBuf;
+ /* Pointer to PHS OUT Hdr Buffer */
+ PUCHAR pucPHSPktHdrOutBuf = Adapter->stPhsTxContextInfo.ucaHdrSuppressionOutBuf;
+ UINT usPacketType;
+ UINT BytesToRemove = 0;
+ bool 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 suppression
+ * 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 Suppress 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(struct bcm_mini_adapter *Adapter,
+ USHORT usVcid,
+ struct sk_buff *packet,
+ UINT *punPacketLen,
+ UCHAR *pucEthernetHdr,
+ UINT bHeaderSuppressionEnabled)
+{
+ u32 nStandardPktHdrLen = 0;
+ u32 nTotalsuppressedPktHdrBytes = 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,
+ &nTotalsuppressedPktHdrBytes,
+ &nStandardPktHdrLen);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, PHS_RECEIVE, DBG_LVL_ALL, "\nSuppressed PktHdrLen : 0x%x Restored PktHdrLen : 0x%x",
+ nTotalsuppressedPktHdrBytes, nStandardPktHdrLen);
+
+ if (ulPhsStatus != STATUS_PHS_COMPRESSED) {
+ skb_pull(packet, 1);
+ return STATUS_SUCCESS;
+ } else {
+ TotalBytesAdded = nStandardPktHdrLen - nTotalsuppressedPktHdrBytes - 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)
+{
+ struct bcm_mini_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 successful.
+ * FALSE -If allocation of memory fails.
+ */
+int phs_init(struct bcm_phs_extension *pPhsdeviceExtension, struct bcm_mini_adapter *Adapter)
+{
+ int i;
+ struct bcm_phs_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(struct bcm_phs_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; i < MAX_SERVICEFLOWS; i++) {
+ struct bcm_phs_entry sServiceFlow = pstServiceFlowTable->stSFList[i];
+ sServiceFlow.pstClassifierTable = kzalloc(sizeof(struct bcm_phs_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 Successful");
+ return STATUS_SUCCESS;
+}
+
+int PhsCleanup(IN struct bcm_phs_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 struct bcm_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 struct bcm_phs_rule *psPhsRule,
+ IN B_UINT8 u8AssociatedPHSI)
+{
+ ULONG lStatus = 0;
+ UINT nSFIndex = 0;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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)
+{
+ UINT nSFIndex = 0, nClsidIndex = 0;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_classifier_table *pstClassifierRulesTable = NULL;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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; nClsidIndex < MAX_PHSRULE_PER_SF; nClsidIndex++) {
+ if (pstClassifierRulesTable->stActivePhsRulesList[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(struct bcm_phs_classifier_entry));
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * 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)
+{
+ UINT nSFIndex = 0, nClsidIndex = 0;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_classifier_entry *pstClassifierEntry = NULL;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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(struct bcm_phs_classifier_entry));
+ }
+
+ nClsidIndex = GetClassifierEntry(pstServiceFlowEntry->pstClassifierTable,
+ uiClsId, eOldClassifierRuleContext, &pstClassifierEntry);
+
+ if ((nClsidIndex != PHS_INVALID_TABLE_INDEX) && (!pstClassifierEntry->bUnclassifiedPHSRule)) {
+ kfree(pstClassifierEntry->pstPhsRule);
+ memset(pstClassifierEntry, 0, sizeof(struct bcm_phs_classifier_entry));
+ }
+ }
+ return 0;
+}
+
+/*
+ * 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)
+{
+ UINT nSFIndex = 0, nClsidIndex = 0;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_classifier_table *pstClassifierRulesTable = NULL;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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; nClsidIndex < MAX_PHSRULE_PER_SF; nClsidIndex++) {
+ if (pstClassifierRulesTable->stActivePhsRulesList[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(struct bcm_phs_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(struct bcm_phs_classifier_entry));
+ }
+ }
+ pstServiceFlowEntry->bUsed = false;
+ pstServiceFlowEntry->uiVcid = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * 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.
+ */
+static 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;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_classifier_entry *pstClassifierEntry = NULL;
+ struct bcm_phs_rule *pstPhsRule = NULL;
+ ULONG lStatus = 0;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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.
+ */
+static 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;
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_rule *pstPhsRule = NULL;
+ UINT phsi;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ struct bcm_phs_extension *pDeviceExtension = (struct bcm_phs_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(struct bcm_phs_table *psServiceFlowRulesTable)
+{
+ int i, j;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "=======>\n");
+
+ if (psServiceFlowRulesTable) {
+ for (i = 0; i < MAX_SERVICEFLOWS; i++) {
+ struct bcm_phs_entry stServiceFlowEntry = psServiceFlowRulesTable->stSFList[i];
+ struct bcm_phs_classifier_table *pstClassifierRulesTable = stServiceFlowEntry.pstClassifierTable;
+
+ if (pstClassifierRulesTable) {
+ for (j = 0; j < MAX_PHSRULE_PER_SF; j++) {
+ if (pstClassifierRulesTable->stActivePhsRulesList[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 bool ValidatePHSRuleComplete(IN struct bcm_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 struct bcm_phs_table *psServiceFlowTable,
+ IN B_UINT16 uiVcid,
+ struct bcm_phs_entry **ppstServiceFlowEntry)
+{
+ int i;
+
+ for (i = 0; i < MAX_SERVICEFLOWS; i++) {
+ if (psServiceFlowTable->stSFList[i].bUsed) {
+ if (psServiceFlowTable->stSFList[i].uiVcid == uiVcid) {
+ *ppstServiceFlowEntry = &psServiceFlowTable->stSFList[i];
+ return i;
+ }
+ }
+ }
+
+ *ppstServiceFlowEntry = NULL;
+ return PHS_INVALID_TABLE_INDEX;
+}
+
+static UINT GetClassifierEntry(IN struct bcm_phs_classifier_table *pstClassifierTable,
+ IN B_UINT32 uiClsid, enum bcm_phs_classifier_context eClsContext,
+ OUT struct bcm_phs_classifier_entry **ppstClassifierEntry)
+{
+ int i;
+ struct bcm_phs_classifier_entry *psClassifierRules = NULL;
+
+ for (i = 0; i < MAX_PHSRULE_PER_SF; i++) {
+
+ if (eClsContext == eActiveClassifierRuleContext)
+ psClassifierRules = &pstClassifierTable->stActivePhsRulesList[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 struct bcm_phs_classifier_table *pstClassifierTable,
+ IN B_UINT32 uiPHSI, enum bcm_phs_classifier_context eClsContext,
+ OUT struct bcm_phs_rule **ppstPhsRule)
+{
+ int i;
+ struct bcm_phs_classifier_entry *pstClassifierRule = NULL;
+
+ for (i = 0; i < MAX_PHSRULE_PER_SF; i++) {
+ if (eClsContext == eActiveClassifierRuleContext)
+ pstClassifierRule = &pstClassifierTable->stActivePhsRulesList[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;
+}
+
+static UINT CreateSFToClassifierRuleMapping(IN B_UINT16 uiVcid, IN B_UINT16 uiClsId,
+ IN struct bcm_phs_table *psServiceFlowTable,
+ struct bcm_phs_rule *psPhsRule,
+ B_UINT8 u8AssociatedPHSI)
+{
+ struct bcm_phs_classifier_table *psaClassifiertable = NULL;
+ UINT uiStatus = 0;
+ int iSfIndex;
+ bool 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;
+}
+
+static UINT CreateClassiferToPHSRuleMapping(IN B_UINT16 uiVcid,
+ IN B_UINT16 uiClsId,
+ IN struct bcm_phs_entry *pstServiceFlowEntry,
+ struct bcm_phs_rule *psPhsRule,
+ B_UINT8 u8AssociatedPHSI)
+{
+ struct bcm_phs_classifier_entry *pstClassifierEntry = NULL;
+ UINT uiStatus = PHS_SUCCESS;
+ UINT nClassifierIndex = 0;
+ struct bcm_phs_classifier_table *psaClassifiertable = NULL;
+ struct bcm_mini_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,
+ struct bcm_phs_classifier_table *psaClassifiertable,
+ struct bcm_phs_rule *psPhsRule,
+ enum bcm_phs_classifier_context eClsContext,
+ B_UINT8 u8AssociatedPHSI)
+{
+ UINT iClassifierIndex = 0;
+ bool bFreeEntryFound = false;
+ struct bcm_phs_classifier_entry *psClassifierRules = NULL;
+ UINT nStatus = PHS_SUCCESS;
+ struct bcm_mini_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(struct bcm_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(struct bcm_phs_rule));
+ } else
+ nStatus = UpdateClassifierPHSRule(uiClsId, psClassifierRules,
+ psaClassifiertable, psPhsRule, u8AssociatedPHSI);
+
+ return nStatus;
+}
+
+static UINT UpdateClassifierPHSRule(IN B_UINT16 uiClsId,
+ IN struct bcm_phs_classifier_entry *pstClassifierEntry,
+ struct bcm_phs_classifier_table *psaClassifiertable,
+ struct bcm_phs_rule *psPhsRule,
+ B_UINT8 u8AssociatedPHSI)
+{
+ struct bcm_phs_rule *pstAddPhsRule = NULL;
+ UINT nPhsRuleIndex = 0;
+ bool bPHSRuleOrphaned = false;
+ struct bcm_mini_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(struct bcm_phs_rule), GFP_KERNEL);
+ if (NULL == pstClassifierEntry->pstPhsRule)
+ return ERR_PHSRULE_MEMALLOC_FAIL;
+ }
+ memcpy(pstClassifierEntry->pstPhsRule, psPhsRule, sizeof(struct bcm_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 bool DerefPhsRule(IN B_UINT16 uiClsId, struct bcm_phs_classifier_table *psaClassifiertable, struct bcm_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(struct bcm_phs_extension *pDeviceExtension)
+{
+ int i, j, k, l;
+ struct bcm_mini_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; i < MAX_SERVICEFLOWS; i++) {
+
+ struct bcm_phs_entry stServFlowEntry =
+ pDeviceExtension->pstServiceFlowPhsRulesTable->stSFList[i];
+ if (stServFlowEntry.bUsed) {
+
+ for (j = 0; j < MAX_PHSRULE_PER_SF; j++) {
+
+ for (l = 0; l < 2; l++) {
+ struct bcm_phs_classifier_entry stClsEntry;
+
+ if (l == 0) {
+ stClsEntry = stServFlowEntry.pstClassifierTable->stActivePhsRulesList[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 ; k < stClsEntry.pstPhsRule->u8PHSFLength; 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; k < stClsEntry.pstPhsRule->u8PHSMLength; 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.
+ */
+static int phs_decompress(unsigned char *in_buf,
+ unsigned char *out_buf,
+ struct bcm_phs_rule *decomp_phs_rules,
+ UINT *header_size)
+{
+ int phss, size = 0;
+ struct bcm_phs_rule *tmp_memb;
+ int bit, i = 0;
+ unsigned char *phsf, *phsm;
+ int in_buf_len = *header_size - 1;
+ struct bcm_mini_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 output %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 output %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(struct bcm_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 suppress = 0;
+ struct bcm_mini_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++;
+ suppress = verify_suppress_phsf(in_buf, out_buf, phs_rule->u8PHSF,
+ phs_rule->u8PHSM, phs_rule->u8PHSS,
+ phs_rule->u8PHSV, new_header_size);
+
+ if (suppress == 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 suppress;
+}
+
+/*
+ * 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;
+ struct bcm_mini_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 00000000000..d697f9c860c
--- /dev/null
+++ b/drivers/staging/bcm/PHSModule.h
@@ -0,0 +1,50 @@
+#ifndef BCM_MINIPORT_PHSMODULE_H
+#define BCM_MINIPORT_PHSMODULE_H
+
+int PHSTransmit(struct bcm_mini_adapter *Adapter,
+ struct sk_buff **pPacket,
+ USHORT Vcid,
+ B_UINT16 uiClassifierRuleID,
+ bool bHeaderSuppressionEnabled,
+ PUINT PacketLen,
+ UCHAR bEthCSSupport);
+
+int PHSReceive(struct bcm_mini_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(struct bcm_phs_extension *pDeviceExtension);
+
+
+int phs_init(struct bcm_phs_extension *pPhsdeviceExtension,struct bcm_mini_adapter *Adapter);
+
+int PhsCleanup(struct bcm_phs_extension *pPHSDeviceExt);
+
+//Utility Functions
+ULONG PhsUpdateClassifierRule(void* pvContext,B_UINT16 uiVcid,B_UINT16 uiClsId, struct bcm_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) ;
+
+
+bool ValidatePHSRule(struct bcm_phs_rule *psPhsRule);
+
+UINT GetServiceFlowEntry(struct bcm_phs_table *psServiceFlowTable,B_UINT16 uiVcid, struct bcm_phs_entry **ppstServiceFlowEntry);
+
+
+void DumpPhsRules(struct bcm_phs_extension *pDeviceExtension);
+
+
+#endif
diff --git a/drivers/staging/bcm/Protocol.h b/drivers/staging/bcm/Protocol.h
new file mode 100644
index 00000000000..9818128d932
--- /dev/null
+++ b/drivers/staging/bcm/Protocol.h
@@ -0,0 +1,128 @@
+/************************************
+* 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 */
+};
+
+struct bcm_transport_header {
+ union {
+ struct udphdr uhdr;
+ struct tcphdr thdr;
+ };
+} __packed;
+
+enum bcm_ip_frame_type {
+ eNonIPPacket,
+ eIPv4Packet,
+ eIPv6Packet
+};
+
+enum bcm_eth_frame_type {
+ eEthUnsupportedFrame,
+ eEth802LLCFrame,
+ eEth802LLCSNAPFrame,
+ eEth802QVLANFrame,
+ eEthOtherFrame
+};
+
+struct bcm_eth_packet_info {
+ enum bcm_ip_frame_type eNwpktIPFrameType;
+ enum bcm_eth_frame_type eNwpktEthFrameType;
+ unsigned short usEtherType;
+ unsigned char ucDSAP;
+};
+
+struct bcm_eth_q_frame {
+ struct bcm_eth_header EThHdr;
+ unsigned short UserPriority:3;
+ unsigned short CFI:1;
+ unsigned short VLANID:12;
+ unsigned short EthType;
+} __packed;
+
+struct bcm_eth_llc_frame {
+ struct bcm_eth_header EThHdr;
+ unsigned char DSAP;
+ unsigned char SSAP;
+ unsigned char Control;
+} __packed;
+
+struct bcm_eth_llc_snap_frame {
+ struct bcm_eth_header EThHdr;
+ unsigned char DSAP;
+ unsigned char SSAP;
+ unsigned char Control;
+ unsigned char OUI[3];
+ unsigned short usEtherType;
+} __packed;
+
+struct bcm_ethernet2_frame {
+ struct bcm_eth_header EThHdr;
+} __packed;
+
+#define ETHERNET_FRAMETYPE_IPV4 ntohs(0x0800)
+#define ETHERNET_FRAMETYPE_IPV6 ntohs(0x86dd)
+#define ETHERNET_FRAMETYPE_802QVLAN ntohs(0x8100)
+
+/* Per SF CS Specification Encodings */
+enum bcm_spec_encoding {
+ eCSSpecUnspecified = 0,
+ eCSPacketIPV4,
+ eCSPacketIPV6,
+ eCS802_3PacketEthernet,
+ eCS802_1QPacketVLAN,
+ eCSPacketIPV4Over802_3Ethernet,
+ eCSPacketIPV6Over802_3Ethernet,
+ eCSPacketIPV4Over802_1QVLAN,
+ eCSPacketIPV6Over802_1QVLAN,
+ eCSPacketUnsupported
+};
+
+#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 */
+struct bcm_tcp_header {
+ unsigned short usSrcPort;
+ unsigned short usDestPort;
+ unsigned long ulSeqNumber;
+ unsigned long ulAckNumber;
+ unsigned char HeaderLength;
+ unsigned char ucFlags;
+ unsigned short usWindowsSize;
+ unsigned short usChkSum;
+ unsigned short usUrgetPtr;
+};
+
+#define TCP_HEADER_LEN sizeof(struct bcm_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 00000000000..fb53a00591e
--- /dev/null
+++ b/drivers/staging/bcm/Prototypes.h
@@ -0,0 +1,222 @@
+#ifndef _PROTOTYPES_H_
+#define _PROTOTYPES_H_
+
+VOID LinkControlResponseMessage(struct bcm_mini_adapter *Adapter, PUCHAR pucBuffer);
+
+VOID StatisticsResponse(struct bcm_mini_adapter *Adapter,PVOID pvBuffer);
+
+VOID IdleModeResponse(struct bcm_mini_adapter *Adapter,PUINT puiBuffer);
+
+int control_packet_handler (struct bcm_mini_adapter *Adapter);
+
+VOID DeleteAllClassifiersForSF(struct bcm_mini_adapter *Adapter,UINT uiSearchRuleIndex);
+
+VOID flush_all_queues(struct bcm_mini_adapter *Adapter);
+
+int register_control_device_interface(struct bcm_mini_adapter *ps_adapter);
+
+void unregister_control_device_interface(struct bcm_mini_adapter *Adapter);
+
+INT CopyBufferToControlPacket(struct bcm_mini_adapter *Adapter,/**<Logical Adapter*/
+ PVOID ioBuffer/**<Control Packet Buffer*/
+ );
+
+VOID SortPackInfo(struct bcm_mini_adapter *Adapter);
+
+VOID SortClassifiers(struct bcm_mini_adapter *Adapter);
+
+VOID flush_all_queues(struct bcm_mini_adapter *Adapter);
+
+VOID PruneQueueAllSF(struct bcm_mini_adapter *Adapter);
+
+INT SearchSfid(struct bcm_mini_adapter *Adapter,UINT uiSfid);
+
+USHORT ClassifyPacket(struct bcm_mini_adapter *Adapter,struct sk_buff* skb);
+
+bool MatchSrcPort(struct bcm_classifier_rule *pstClassifierRule,USHORT ushSrcPort);
+bool MatchDestPort(struct bcm_classifier_rule *pstClassifierRule,USHORT ushSrcPort);
+bool MatchProtocol(struct bcm_classifier_rule *pstClassifierRule,UCHAR ucProtocol);
+
+
+INT SetupNextSend(struct bcm_mini_adapter *Adapter, /**<Logical Adapter*/
+ struct sk_buff *Packet, /**<data buffer*/
+ USHORT Vcid) ;
+
+VOID LinkMessage(struct bcm_mini_adapter *Adapter);
+
+VOID transmit_packets(struct bcm_mini_adapter *Adapter);
+
+INT SendControlPacket(struct bcm_mini_adapter *Adapter, /**<Logical Adapter*/
+ char *pControlPacket/**<Control Packet*/
+ );
+
+
+int register_networkdev(struct bcm_mini_adapter *Adapter);
+void unregister_networkdev(struct bcm_mini_adapter *Adapter);
+
+INT AllocAdapterDsxBuffer(struct bcm_mini_adapter *Adapter);
+
+VOID AdapterFree(struct bcm_mini_adapter *Adapter);
+
+INT FreeAdapterDsxBuffer(struct bcm_mini_adapter *Adapter);
+
+int tx_pkt_handler(struct bcm_mini_adapter *Adapter);
+
+int reset_card_proc(struct bcm_mini_adapter *Adapter );
+
+int run_card_proc(struct bcm_mini_adapter *Adapter );
+
+int InitCardAndDownloadFirmware(struct bcm_mini_adapter *ps_adapter);
+
+
+INT ReadMacAddressFromNVM(struct bcm_mini_adapter *Adapter);
+
+int register_control_device_interface(struct bcm_mini_adapter *ps_adapter);
+
+void DumpPackInfo(struct bcm_mini_adapter *Adapter);
+
+int rdm(struct bcm_mini_adapter *Adapter, UINT uiAddress, PCHAR pucBuff, size_t size);
+
+int wrm(struct bcm_mini_adapter *Adapter, UINT uiAddress, PCHAR pucBuff, size_t size);
+
+int wrmalt (struct bcm_mini_adapter *Adapter, UINT uiAddress, unsigned int *pucBuff, size_t sSize);
+
+int rdmalt (struct bcm_mini_adapter *Adapter, UINT uiAddress, unsigned int *pucBuff, size_t sSize);
+
+int get_dsx_sf_data_to_application(struct bcm_mini_adapter *Adapter, UINT uiSFId, void __user * user_buffer);
+
+void SendIdleModeResponse(struct bcm_mini_adapter *Adapter);
+
+
+int ProcessGetHostMibs(struct bcm_mini_adapter *Adapter, struct bcm_host_stats_mibs *buf);
+void GetDroppedAppCntrlPktMibs(struct bcm_host_stats_mibs *ioBuffer, struct bcm_tarang_data *pTarang);
+void beceem_parse_target_struct(struct bcm_mini_adapter *Adapter);
+
+int bcm_ioctl_fw_download(struct bcm_mini_adapter *Adapter, struct bcm_firmware_info *psFwInfo);
+
+void CopyMIBSExtendedSFParameters(struct bcm_mini_adapter *Adapter,
+ struct bcm_connect_mgr_params *psfLocalSet, UINT uiSearchRuleIndex);
+
+VOID ResetCounters(struct bcm_mini_adapter *Adapter);
+
+int InitLedSettings(struct bcm_mini_adapter *Adapter);
+
+struct bcm_classifier_rule *GetFragIPClsEntry(struct bcm_mini_adapter *Adapter,USHORT usIpIdentification,ULONG SrcIP);
+
+void AddFragIPClsEntry(struct bcm_mini_adapter *Adapter, struct bcm_fragmented_packet_info *psFragPktInfo);
+
+void DelFragIPClsEntry(struct bcm_mini_adapter *Adapter,USHORT usIpIdentification,ULONG SrcIp);
+
+void update_per_cid_rx (struct bcm_mini_adapter *Adapter);
+
+void update_per_sf_desc_cnts( struct bcm_mini_adapter *Adapter);
+
+void ClearTargetDSXBuffer(struct bcm_mini_adapter *Adapter,B_UINT16 TID,bool bFreeAll);
+
+
+void flush_queue(struct bcm_mini_adapter *Adapter, UINT iQIndex);
+
+
+INT flushAllAppQ(VOID);
+
+
+INT BeceemEEPROMBulkRead(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ UINT uiOffset,
+ UINT uiNumBytes);
+
+
+
+INT WriteBeceemEEPROM(struct bcm_mini_adapter *Adapter,UINT uiEEPROMOffset, UINT uiData);
+
+INT PropagateCalParamsFromFlashToMemory(struct bcm_mini_adapter *Adapter);
+
+
+INT BeceemEEPROMBulkWrite(
+ struct bcm_mini_adapter *Adapter,
+ PUCHAR pBuffer,
+ UINT uiOffset,
+ UINT uiNumBytes,
+ bool bVerify);
+
+
+INT ReadBeceemEEPROM(struct bcm_mini_adapter *Adapter,UINT dwAddress, UINT *pdwData);
+
+
+INT BeceemNVMRead(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ UINT uiOffset,
+ UINT uiNumBytes);
+
+INT BeceemNVMWrite(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ UINT uiOffset,
+ UINT uiNumBytes,
+ bool bVerify);
+
+
+INT BcmInitNVM(struct bcm_mini_adapter *Adapter);
+
+INT BcmUpdateSectorSize(struct bcm_mini_adapter *Adapter,UINT uiSectorSize);
+bool IsSectionExistInFlash(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val section);
+
+INT BcmGetFlash2xSectionalBitMap(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_bitmap *psFlash2xBitMap);
+
+INT BcmFlash2xBulkWrite(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ enum bcm_flash2x_section_val eFlashSectionVal,
+ UINT uiOffset,
+ UINT uiNumBytes,
+ UINT bVerify);
+
+INT BcmFlash2xBulkRead(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ enum bcm_flash2x_section_val eFlashSectionVal,
+ UINT uiOffsetWithinSectionVal,
+ UINT uiNumBytes);
+
+INT BcmGetSectionValStartOffset(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlashSectionVal);
+
+INT BcmSetActiveSection(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectVal);
+INT BcmAllocFlashCSStructure(struct bcm_mini_adapter *psAdapter);
+INT BcmDeAllocFlashCSStructure(struct bcm_mini_adapter *psAdapter);
+
+INT BcmCopyISO(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_copy_section sCopySectStrut);
+INT BcmFlash2xCorruptSig(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal);
+INT BcmFlash2xWriteSig(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlashSectionVal);
+INT validateFlash2xReadWrite(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_readwrite *psFlash2xReadWrite);
+INT IsFlash2x(struct bcm_mini_adapter *Adapter);
+INT BcmCopySection(struct bcm_mini_adapter *Adapter,
+ enum bcm_flash2x_section_val SrcSection,
+ enum bcm_flash2x_section_val DstSection,
+ UINT offset,
+ UINT numOfBytes);
+
+
+bool IsNonCDLessDevice(struct bcm_mini_adapter *Adapter);
+
+
+VOID OverrideServiceFlowParams(struct bcm_mini_adapter *Adapter,PUINT puiBuffer);
+
+int wrmaltWithLock (struct bcm_mini_adapter *Adapter, UINT uiAddress, unsigned int *pucBuff, size_t sSize);
+int rdmaltWithLock (struct bcm_mini_adapter *Adapter, UINT uiAddress, unsigned int *pucBuff, size_t sSize);
+
+int wrmWithLock(struct bcm_mini_adapter *Adapter, UINT uiAddress, PCHAR pucBuff, size_t size);
+INT buffDnldVerify(struct bcm_mini_adapter *Adapter, unsigned char *mappedbuffer, unsigned int u32FirmwareLength,
+ unsigned long u32StartingAddress);
+
+
+VOID putUsbSuspend(struct work_struct *work);
+bool IsReqGpioIsLedInNVM(struct bcm_mini_adapter *Adapter, UINT gpios);
+
+
+#endif
+
+
+
+
diff --git a/drivers/staging/bcm/Qos.c b/drivers/staging/bcm/Qos.c
new file mode 100644
index 00000000000..0c742da8c6b
--- /dev/null
+++ b/drivers/staging/bcm/Qos.c
@@ -0,0 +1,777 @@
+/**
+@file Qos.C
+This file contains the routines related to Quality of Service.
+*/
+#include "headers.h"
+
+static void EThCSGetPktInfo(struct bcm_mini_adapter *Adapter,
+ PVOID pvEthPayload,
+ struct bcm_eth_packet_info *pstEthCsPktInfo);
+
+static bool EThCSClassifyPkt(struct bcm_mini_adapter *Adapter,
+ struct sk_buff *skb,
+ struct bcm_eth_packet_info *pstEthCsPktInfo,
+ struct bcm_classifier_rule *pstClassifierRule,
+ B_UINT8 EthCSCupport);
+
+static USHORT IpVersion4(struct bcm_mini_adapter *Adapter, struct iphdr *iphd,
+ struct bcm_classifier_rule *pstClassifierRule);
+
+static VOID PruneQueue(struct bcm_mini_adapter *Adapter, INT iIndex);
+
+
+/*******************************************************************
+* Function - MatchSrcIpAddress()
+*
+* Description - Checks whether the Source IP address from the packet
+* matches with that of Queue.
+*
+* Parameters - pstClassifierRule: Pointer to the packet info structure.
+* - ulSrcIP : Source IP address from the packet.
+*
+* Returns - TRUE(If address matches) else FAIL .
+*********************************************************************/
+static bool MatchSrcIpAddress(struct bcm_classifier_rule *pstClassifierRule, ULONG ulSrcIP)
+{
+ UCHAR ucLoopIndex = 0;
+
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+ ulSrcIP = ntohl(ulSrcIP);
+ if (0 == pstClassifierRule->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 .
+*********************************************************************/
+static bool MatchDestIpAddress(struct bcm_classifier_rule *pstClassifierRule, ULONG ulDestIP)
+{
+ UCHAR ucLoopIndex = 0;
+ struct bcm_mini_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.
+**************************************************************************/
+static bool MatchTos(struct bcm_classifier_rule *pstClassifierRule, UCHAR ucTypeOfService)
+{
+
+ struct bcm_mini_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.
+****************************************************************************/
+bool MatchProtocol(struct bcm_classifier_rule *pstClassifierRule, UCHAR ucProtocol)
+{
+ UCHAR ucLoopIndex = 0;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+ if (0 == pstClassifierRule->ucProtocolLength)
+ return TRUE;
+ for (ucLoopIndex = 0; ucLoopIndex < pstClassifierRule->ucProtocolLength; 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.
+***************************************************************************/
+bool MatchSrcPort(struct bcm_classifier_rule *pstClassifierRule, USHORT ushSrcPort)
+{
+ UCHAR ucLoopIndex = 0;
+
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+
+ if (0 == pstClassifierRule->ucSrcPortRangeLength)
+ return TRUE;
+ for (ucLoopIndex = 0; ucLoopIndex < pstClassifierRule->ucSrcPortRangeLength; 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.
+***************************************************************************/
+bool MatchDestPort(struct bcm_classifier_rule *pstClassifierRule, USHORT ushDestPort)
+{
+ UCHAR ucLoopIndex = 0;
+ struct bcm_mini_adapter *Adapter = GET_BCM_ADAPTER(gblpnetdev);
+
+ if (0 == pstClassifierRule->ucDestPortRangeLength)
+ return TRUE;
+
+ for (ucLoopIndex = 0; ucLoopIndex < pstClassifierRule->ucDestPortRangeLength; 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(struct bcm_mini_adapter *Adapter,
+ struct iphdr *iphd,
+ struct bcm_classifier_rule *pstClassifierRule)
+{
+ struct bcm_transport_header *xprt_hdr = NULL;
+ bool bClassificationSucceed = false;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "========>");
+
+ xprt_hdr = (struct bcm_transport_header *)((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)
+ 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 (!MatchSrcIpAddress(pstClassifierRule, iphd->saddr))
+ break;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Source IP Address Matched");
+
+ if (!MatchDestIpAddress(pstClassifierRule, iphd->daddr))
+ break;
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Destination IP Address Matched");
+
+ if (!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 (!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) {
+ bClassificationSucceed = TRUE;
+ 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 (!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 (!MatchDestPort(pstClassifierRule,
+ ntohs((iphd->protocol == UDP) ?
+ xprt_hdr->uhdr.dest : xprt_hdr->thdr.dest)))
+ break;
+ bClassificationSucceed = TRUE;
+ } 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(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_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; iQIndex < HiPriority; iQIndex++) {
+ struct net_device_stats *netstats = &Adapter->dev->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(struct bcm_mini_adapter *Adapter, struct sk_buff* skb)
+{
+ INT uiLoopIndex = 0;
+ struct bcm_classifier_rule *pstClassifierRule = NULL;
+ struct bcm_eth_packet_info stEthCsPktInfo;
+ PVOID pvEThPayload = NULL;
+ struct iphdr *pIpHeader = NULL;
+ INT uiSfIndex = 0;
+ USHORT usIndex = Adapter->usBestEffortQueueIndex;
+ bool bFragmentedPkt = false, bClassificationSucceed = false;
+ USHORT usCurrFragment = 0;
+
+ struct bcm_tcp_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(struct bcm_eth_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(struct bcm_eth_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(struct bcm_eth_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(struct bcm_ethernet2_frame);
+ break;
+ default:
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : Unrecognized ETH Frame\n");
+ pIpHeader = pvEThPayload + sizeof(struct bcm_ethernet2_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 = (struct bcm_tcp_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
+ struct bcm_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 bool EthCSMatchSrcMACAddress(struct bcm_classifier_rule *pstClassifierRule, PUCHAR Mac)
+{
+ UINT i = 0;
+ struct bcm_mini_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", __func__);
+ for (i = 0; i < MAC_ADDRESS_SIZE; i++) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "SRC MAC[%x] = %x ClassifierRuleSrcMAC = %x Mask : %x\n", i, Mac[i], pstClassifierRule->au8EThCSSrcMAC[i], pstClassifierRule->au8EThCSSrcMACMask[i]);
+ if ((pstClassifierRule->au8EThCSSrcMAC[i] & pstClassifierRule->au8EThCSSrcMACMask[i]) !=
+ (Mac[i] & pstClassifierRule->au8EThCSSrcMACMask[i]))
+ return false;
+ }
+ return TRUE;
+}
+
+static bool EthCSMatchDestMACAddress(struct bcm_classifier_rule *pstClassifierRule, PUCHAR Mac)
+{
+ UINT i = 0;
+ struct bcm_mini_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", __func__);
+ for (i = 0; i < MAC_ADDRESS_SIZE; i++) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "SRC MAC[%x] = %x ClassifierRuleSrcMAC = %x Mask : %x\n", i, Mac[i], pstClassifierRule->au8EThCSDestMAC[i], pstClassifierRule->au8EThCSDestMACMask[i]);
+ if ((pstClassifierRule->au8EThCSDestMAC[i] & pstClassifierRule->au8EThCSDestMACMask[i]) !=
+ (Mac[i] & pstClassifierRule->au8EThCSDestMACMask[i]))
+ return false;
+ }
+ return TRUE;
+}
+
+static bool EthCSMatchEThTypeSAP(struct bcm_classifier_rule *pstClassifierRule, struct sk_buff* skb, struct bcm_eth_packet_info *pstEthCsPktInfo)
+{
+ struct bcm_mini_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", __func__, 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", __func__, 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", __func__, pstEthCsPktInfo->ucDSAP, pstClassifierRule->au8EthCSEtherType[2]);
+ if (pstEthCsPktInfo->ucDSAP == pstClassifierRule->au8EthCSEtherType[2])
+ return TRUE;
+ else
+ return false;
+
+ }
+
+ return false;
+
+}
+
+static bool EthCSMatchVLANRules(struct bcm_classifier_rule *pstClassifierRule, struct sk_buff* skb, struct bcm_eth_packet_info *pstEthCsPktInfo)
+{
+ bool bClassificationSucceed = false;
+ USHORT usVLANID;
+ B_UINT8 uPriority = 0;
+ struct bcm_mini_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", __func__, ntohs(*((USHORT *)pstClassifierRule->usUserPriority)), pstClassifierRule->usVLANID);
+
+ /* In case FW didn't receive the TLV, the priority field should be ignored */
+ if (pstClassifierRule->usValidityBitMap & (1<<PKT_CLASSIFICATION_USER_PRIORITY_VALID)) {
+ if (pstEthCsPktInfo->eNwpktEthFrameType != eEth802QVLANFrame)
+ return false;
+
+ uPriority = (ntohs(*(USHORT *)(skb->data + sizeof(struct bcm_eth_header))) & 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<<PKT_CLASSIFICATION_VLANID_VALID)) {
+ if (pstEthCsPktInfo->eNwpktEthFrameType != eEth802QVLANFrame)
+ return false;
+
+ usVLANID = ntohs(*(USHORT *)(skb->data + sizeof(struct bcm_eth_header))) & 0xFFF;
+
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s Pkt VLANID %x Priority: %d\n", __func__, 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 bool EThCSClassifyPkt(struct bcm_mini_adapter *Adapter, struct sk_buff* skb,
+ struct bcm_eth_packet_info *pstEthCsPktInfo,
+ struct bcm_classifier_rule *pstClassifierRule,
+ B_UINT8 EthCSCupport)
+{
+ bool bClassificationSucceed = false;
+ bClassificationSucceed = EthCSMatchSrcMACAddress(pstClassifierRule, ((struct bcm_eth_header *)(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, ((struct bcm_eth_header *)(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(struct bcm_mini_adapter *Adapter, PVOID pvEthPayload,
+ struct bcm_eth_packet_info *pstEthCsPktInfo)
+{
+ USHORT u16Etype = ntohs(((struct bcm_eth_header *)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 = ((struct bcm_eth_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 = ((struct bcm_eth_llc_frame *)pvEthPayload)->DSAP;
+ if (pstEthCsPktInfo->ucDSAP == 0xAA && ((struct bcm_eth_llc_frame *)pvEthPayload)->SSAP == 0xAA) {
+ //SNAP Frame
+ pstEthCsPktInfo->eNwpktEthFrameType = eEth802LLCSNAPFrame;
+ u16Etype = ((struct bcm_eth_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 = ((struct bcm_eth_header *)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 00000000000..e1f1da2bb6d
--- /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 00000000000..cd3e9f2ed87
--- /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 00000000000..49767468ac2
--- /dev/null
+++ b/drivers/staging/bcm/Transmit.c
@@ -0,0 +1,245 @@
+/**
+ * @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(struct bcm_mini_adapter *Adapter, char *pControlPacket)
+{
+ struct bcm_leader *PLeader = (struct bcm_leader *)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(struct bcm_mini_adapter *Adapter, struct sk_buff *Packet, USHORT Vcid)
+{
+ int status = 0;
+ bool bHeaderSupressionEnabled = false;
+ B_UINT16 uiClassifierRuleID;
+ u16 QueueIndex = skb_get_queue_mapping(Packet);
+ struct bcm_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) {
+ status = skb_cow(Packet, LEADER_SIZE);
+ if (status) {
+ 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((struct bcm_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(struct bcm_mini_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(struct bcm_mini_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((struct bcm_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 00000000000..90b3b25dd60
--- /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 TRUE 1
+
+
+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/cntrl_SignalingInterface.h b/drivers/staging/bcm/cntrl_SignalingInterface.h
new file mode 100644
index 00000000000..8683c2d4276
--- /dev/null
+++ b/drivers/staging/bcm/cntrl_SignalingInterface.h
@@ -0,0 +1,311 @@
+#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
+
+struct bcm_packet_class_rules {
+ /* 16bit UserPriority Of The Service Flow */
+ u16 u16UserPriority;
+ /* 16bit VLANID Of The Service Flow */
+ u16 u16VLANID;
+ /* 16bit Packet Classification RuleIndex Of The Service Flow */
+ u16 u16PacketClassificationRuleIndex;
+ /* 8bit Classifier Rule Priority Of The Service Flow */
+ u8 u8ClassifierRulePriority;
+ /* Length of IP TypeOfService field */
+ u8 u8IPTypeOfServiceLength;
+ /* 3bytes IP TypeOfService */
+ u8 u8IPTypeOfService[TYPE_OF_SERVICE_LENGTH];
+ /* Protocol used in classification of Service Flow */
+ u8 u8Protocol;
+ /* Length of IP Masked Source Address */
+ u8 u8IPMaskedSourceAddressLength;
+ /* IP Masked Source Address used in classification for the Service Flow */
+ u8 u8IPMaskedSourceAddress[IP_MASKED_SRC_ADDRESS_LENGTH];
+ /* Length of IP Destination Address */
+ u8 u8IPDestinationAddressLength;
+ /* IP Destination Address used in classification for the Service Flow */
+ u8 u8IPDestinationAddress[IP_MASKED_DEST_ADDRESS_LENGTH];
+ /* Length of Protocol Source Port Range */
+ u8 u8ProtocolSourcePortRangeLength;
+ /* Protocol Source Port Range used in the Service Flow */
+ u8 u8ProtocolSourcePortRange[PROTOCOL_SRC_PORT_RANGE_LENGTH];
+ /* Length of Protocol Dest Port Range */
+ u8 u8ProtocolDestPortRangeLength;
+ /* Protocol Dest Port Range used in the Service Flow */
+ u8 u8ProtocolDestPortRange[PROTOCOL_DEST_PORT_RANGE_LENGTH];
+ /* Length of Ethernet Destination MAC Address */
+ u8 u8EthernetDestMacAddressLength;
+ /* Ethernet Destination MAC Address used in classification of the Service Flow */
+ u8 u8EthernetDestMacAddress[ETHERNET_DEST_MAC_ADDR_LENGTH];
+ /* Length of Ethernet Source MAC Address */
+ u8 u8EthernetSourceMACAddressLength;
+ /* Ethernet Source MAC Address used in classification of the Service Flow */
+ u8 u8EthernetSourceMACAddress[ETHERNET_SRC_MAC_ADDR_LENGTH];
+ /* Length of Ethertype */
+ u8 u8EthertypeLength;
+ /* 3bytes Ethertype Of The Service Flow */
+ u8 u8Ethertype[NUM_ETHERTYPE_BYTES];
+ /* 8bit Associated PHSI Of The Service Flow */
+ u8 u8AssociatedPHSI;
+ /* Length of Vendor Specific Classifier Param length Of The Service Flow */
+ u8 u8VendorSpecificClassifierParamLength;
+ /* Vendor Specific Classifier Param Of The Service Flow */
+ u8 u8VendorSpecificClassifierParam[VENDOR_CLASSIFIER_PARAM_LENGTH];
+ /* Length Of IPv6 Flow Lable of the Service Flow */
+ u8 u8IPv6FlowLableLength;
+ /* IPv6 Flow Lable Of The Service Flow */
+ u8 u8IPv6FlowLable[NUM_IPV6_FLOWLABLE_BYTES];
+ /* Action associated with the classifier rule */
+ u8 u8ClassifierActionRule;
+ u16 u16ValidityBitMap;
+};
+
+struct bcm_phs_rules {
+ /* 8bit PHS Index Of The Service Flow */
+ u8 u8PHSI;
+ /* PHSF Length Of The Service Flow */
+ u8 u8PHSFLength;
+ /* String of bytes containing header information to be suppressed by the sending CS and reconstructed by the receiving CS */
+ u8 u8PHSF[MAX_PHS_LENGTHS];
+ /* PHSM Length Of The Service Flow */
+ u8 u8PHSMLength;
+ /* PHS Mask for the SF */
+ u8 u8PHSM[MAX_PHS_LENGTHS];
+ /* 8bit Total number of bytes to be suppressed for the Service Flow */
+ u8 u8PHSS;
+ /* 8bit Indicates whether or not Packet Header contents need to be verified prior to suppression */
+ u8 u8PHSV;
+ /* Vendor Specific PHS param Length Of The Service Flow */
+ u8 u8VendorSpecificPHSParamsLength;
+ /* Vendor Specific PHS param Of The Service Flow */
+ u8 u8VendorSpecificPHSParams[VENDOR_PHS_PARAM_LENGTH];
+ u8 u8Padding[2];
+};
+
+struct bcm_convergence_types {
+ /* 8bit Phs Classfier Action Of The Service Flow */
+ u8 u8ClassfierDSCAction;
+ /* 8bit Phs DSC Action Of The Service Flow */
+ u8 u8PhsDSCAction;
+ /* 16bit Padding */
+ u8 u8Padding[2];
+ /* Packet classification rules structure */
+ struct bcm_packet_class_rules cCPacketClassificationRule;
+ /* Payload header suppression rules structure */
+ struct bcm_phs_rules cPhsRule;
+};
+
+struct bcm_connect_mgr_params {
+ /* 32bitSFID Of The Service Flow */
+ u32 u32SFID;
+ /* 32bit Maximum Sustained Traffic Rate of the Service Flow */
+ u32 u32MaxSustainedTrafficRate;
+ /* 32bit Maximum Traffic Burst allowed for the Service Flow */
+ u32 u32MaxTrafficBurst;
+ /* 32bit Minimum Reserved Traffic Rate of the Service Flow */
+ u32 u32MinReservedTrafficRate;
+ /* 32bit Tolerated Jitter of the Service Flow */
+ u32 u32ToleratedJitter;
+ /* 32bit Maximum Latency of the Service Flow */
+ u32 u32MaximumLatency;
+ /* 16bitCID Of The Service Flow */
+ u16 u16CID;
+ /* 16bit SAID on which the service flow being set up shall be mapped */
+ u16 u16TargetSAID;
+ /* 16bit ARQ window size negotiated */
+ u16 u16ARQWindowSize;
+ /* 16bit Total Tx delay incl sending, receiving & processing delays */
+ u16 u16ARQRetryTxTimeOut;
+ /* 16bit Total Rx delay incl sending, receiving & processing delays */
+ u16 u16ARQRetryRxTimeOut;
+ /* 16bit ARQ block lifetime */
+ u16 u16ARQBlockLifeTime;
+ /* 16bit ARQ Sync loss timeout */
+ u16 u16ARQSyncLossTimeOut;
+ /* 16bit ARQ Purge timeout */
+ u16 u16ARQRxPurgeTimeOut;
+ /* TODO::Remove this once we move to a new CORR2 driver
+ * brief Size of an ARQ block
+ */
+ u16 u16ARQBlockSize;
+ /* #endif */
+ /* 16bit Nominal interval b/w consecutive SDU arrivals at MAC SAP */
+ u16 u16SDUInterArrivalTime;
+ /* 16bit Specifies the time base for rate measurement */
+ u16 u16TimeBase;
+ /* 16bit Interval b/w Successive Grant oppurtunities */
+ u16 u16UnsolicitedGrantInterval;
+ /* 16bit Interval b/w Successive Polling grant oppurtunities */
+ u16 u16UnsolicitedPollingInterval;
+ /* internal var to get the overhead */
+ u16 u16MacOverhead;
+ /* MBS contents Identifier */
+ u16 u16MBSContentsID[MBS_CONTENTS_ID_LENGTH];
+ /* MBS contents Identifier length */
+ u8 u8MBSContentsIDLength;
+ /* ServiceClassName Length Of The Service Flow */
+ u8 u8ServiceClassNameLength;
+ /* 32bytes ServiceClassName Of The Service Flow */
+ u8 u8ServiceClassName[32];
+ /* 8bit Indicates whether or not MBS service is requested for this Serivce Flow */
+ u8 u8MBSService;
+ /* 8bit QOS Parameter Set specifies proper application of QoS parameters to Provisioned, Admitted and Active sets */
+ u8 u8QosParamSet;
+ /* 8bit Traffic Priority Of the Service Flow */
+ u8 u8TrafficPriority;
+ /* 8bit Uplink Grant Scheduling Type of The Service Flow */
+ u8 u8ServiceFlowSchedulingType;
+ /* 8bit Request transmission Policy of the Service Flow */
+ u8 u8RequesttransmissionPolicy;
+ /* 8bit Specifies whether SDUs for this Service flow are of FixedLength or Variable length */
+ u8 u8FixedLengthVSVariableLengthSDUIndicator;
+ /* 8bit Length of the SDU for a fixed length SDU service flow */
+ u8 u8SDUSize;
+ /* 8bit Indicates whether or not ARQ is requested for this connection */
+ u8 u8ARQEnable;
+ /* < 8bit Indicates whether or not data has tobe delivered in order to higher layer */
+ u8 u8ARQDeliverInOrder;
+ /* 8bit Receiver ARQ ACK processing time */
+ u8 u8RxARQAckProcessingTime;
+ /* 8bit Convergence Sublayer Specification Of The Service Flow */
+ u8 u8CSSpecification;
+ /* 8 bit Type of data delivery service */
+ u8 u8TypeOfDataDeliveryService;
+ /* 8bit Specifies whether a service flow may generate Paging */
+ u8 u8PagingPreference;
+ /* 8bit Indicates the MBS Zone through which the connection or virtual connection is valid */
+ u8 u8MBSZoneIdentifierassignment;
+ /* 8bit Specifies whether traffic on SF should generate MOB_TRF_IND to MS in sleep mode */
+ u8 u8TrafficIndicationPreference;
+ /* 8bit Speciifes the length of predefined Global QoS parameter set encoding for this SF */
+ u8 u8GlobalServicesClassNameLength;
+ /* 6 byte Speciifes the predefined Global QoS parameter set encoding for this SF */
+ u8 u8GlobalServicesClassName[GLOBAL_SF_CLASSNAME_LENGTH];
+ /* 8bit Indicates whether or not SN feedback is enabled for the conn */
+ u8 u8SNFeedbackEnabled;
+ /* Indicates the size of the Fragment Sequence Number for the connection */
+ u8 u8FSNSize;
+ /* 8bit Number of CIDs in active BS list */
+ u8 u8CIDAllocation4activeBSsLength;
+ /* CIDs of BS in the active list */
+ u8 u8CIDAllocation4activeBSs[MAX_NUM_ACTIVE_BS];
+ /* Specifies if PDU extended subheader should be applied on every PDU on this conn */
+ u8 u8PDUSNExtendedSubheader4HarqReordering;
+ /* 8bit Specifies whether the connection uses HARQ or not */
+ u8 u8HARQServiceFlows;
+ /* Specifies the length of Authorization token */
+ u8 u8AuthTokenLength;
+ /* Specifies the Authorization token */
+ u8 u8AuthToken[AUTH_TOKEN_LENGTH];
+ /* specifes Number of HARQ channels used to carry data length */
+ u8 u8HarqChannelMappingLength;
+ /* specifes HARQ channels used to carry data */
+ u8 u8HARQChannelMapping[NUM_HARQ_CHANNELS];
+ /* 8bit Length of Vendor Specific QoS Params */
+ u8 u8VendorSpecificQoSParamLength;
+ /* 1byte Vendor Specific QoS Param Of The Service Flow */
+ u8 u8VendorSpecificQoSParam[VENDOR_SPECIF_QOS_PARAM];
+ /* indicates total classifiers in the SF */
+ u8 u8TotalClassifiers; /* < Total number of valid classifiers */
+ u8 bValid; /* < Validity flag */
+ u8 u8Padding; /* < Padding byte */
+ /*
+ * Structure for Convergence SubLayer Types with a maximum of 4 classifiers
+ */
+ struct bcm_convergence_types cConvergenceSLTypes[MAX_CLASSIFIERS_IN_SF];
+};
+
+struct bcm_add_request {
+ u8 u8Type; /* < Type */
+ u8 eConnectionDir; /* < Connection direction */
+ /* brief 16 bit TID */
+ u16 u16TID; /* < 16bit TID */
+ /* brief 16bitCID */
+ u16 u16CID; /* < 16bit CID */
+ /* brief 16bitVCID */
+ u16 u16VCID; /* < 16bit VCID */
+ struct bcm_connect_mgr_params *psfParameterSet; /* < connection manager parameters */
+};
+
+struct bcm_add_indication {
+ u8 u8Type; /* < Type */
+ u8 eConnectionDir; /* < Connection Direction */
+ /* brief 16 bit TID */
+ u16 u16TID; /* < TID */
+ /* brief 16bitCID */
+ u16 u16CID; /* < 16bitCID */
+ /* brief 16bitVCID */
+ u16 u16VCID; /* < 16bitVCID */
+ struct bcm_connect_mgr_params *psfAuthorizedSet; /* Authorized set of connection manager parameters */
+ struct bcm_connect_mgr_params *psfAdmittedSet; /* Admitted set of connection manager parameters */
+ struct bcm_connect_mgr_params *psfActiveSet; /* Activeset of connection manager parameters */
+ u8 u8CC; /* <Confirmation Code */
+ u8 u8Padd; /* < 8-bit Padding */
+ u16 u16Padd; /* < 16 bit Padding */
+};
+
+struct bcm_del_request {
+ u8 u8Type; /* < Type */
+ u8 u8Padding; /* < Padding byte */
+ u16 u16TID; /* < TID */
+ /* brief 32bitSFID */
+ u32 u32SFID; /* < SFID */
+};
+
+struct bcm_del_indication {
+ u8 u8Type; /* < Type */
+ u8 u8Padding; /* < Padding */
+ u16 u16TID; /* < TID */
+ /* brief 16bitCID */
+ u16 u16CID; /* < CID */
+ /* brief 16bitVCID */
+ u16 u16VCID; /* < VCID */
+ /* brief 32bitSFID */
+ u32 u32SFID; /* < SFID */
+ /* brief 8bit Confirmation code */
+ u8 u8ConfirmationCode; /* < Confirmation code */
+ u8 u8Padding1[3]; /* < 3 byte Padding */
+};
+
+struct bcm_stim_sfhostnotify {
+ u32 SFID; /* SFID of the service flow */
+ u16 newCID; /* the new/changed CID */
+ u16 VCID; /* Get new Vcid if the flow has been made active in CID update TLV, but was inactive earlier or the orig vcid */
+ u8 RetainSF; /* Indication to Host if the SF is to be retained or deleted; if TRUE-retain else delete */
+ u8 QoSParamSet; /* QoS paramset of the retained SF */
+ u16 u16reserved; /* For byte alignment */
+};
+
+#endif
diff --git a/drivers/staging/bcm/headers.h b/drivers/staging/bcm/headers.h
new file mode 100644
index 00000000000..6f3270cc417
--- /dev/null
+++ b/drivers/staging/bcm/headers.h
@@ -0,0 +1,78 @@
+
+/*******************************************************************
+* Headers.h
+*******************************************************************/
+#ifndef __HEADERS_H__
+#define __HEADERS_H__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/string.h>
+#include <linux/etherdevice.h>
+#include <linux/wait.h>
+#include <linux/proc_fs.h>
+#include <linux/interrupt.h>
+#include <linux/stddef.h>
+#include <linux/stat.h>
+#include <linux/fcntl.h>
+#include <linux/unistd.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/kthread.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/usb.h>
+#include <linux/uaccess.h>
+#include <net/ip.h>
+
+#include "Typedefs.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 "5.2.45"
+#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 00000000000..42d9004e357
--- /dev/null
+++ b/drivers/staging/bcm/hostmibs.c
@@ -0,0 +1,150 @@
+/*
+ * 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(struct bcm_mini_adapter *Adapter,
+ struct bcm_host_stats_mibs *pstHostMibs)
+{
+ struct bcm_phs_entry *pstServiceFlowEntry = NULL;
+ struct bcm_phs_rule *pstPhsRule = NULL;
+ struct bcm_phs_classifier_table *pstClassifierTable = NULL;
+ struct bcm_phs_classifier_entry *pstClassifierRule = NULL;
+ struct bcm_phs_extension *pDeviceExtension = &Adapter->stBCMPhsContext;
+ UINT nClassifierIndex = 0;
+ UINT nPhsTableIndex = 0;
+ UINT nSfIndex = 0;
+ UINT 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(&pstHostMibs->astClassifierTable[nClassifierIndex],
+ &Adapter->astClassifierTable[nClassifierIndex],
+ sizeof(struct bcm_mibs_classifier_rule));
+ }
+
+ /* Copy the SF Table */
+ for (nSfIndex = 0; nSfIndex < NO_OF_QUEUES; nSfIndex++) {
+ if (Adapter->PackInfo[nSfIndex].bValid) {
+ memcpy(&pstHostMibs->astSFtable[nSfIndex],
+ &Adapter->PackInfo[nSfIndex],
+ sizeof(struct bcm_mibs_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(struct bcm_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(struct bcm_host_stats_mibs *pstHostMibs,
+ struct bcm_tarang_data *pTarang)
+{
+ memcpy(&(pstHostMibs->stDroppedAppCntrlMsgs),
+ &(pTarang->stDroppedAppCntrlMsgs),
+ sizeof(struct bcm_mibs_dropped_cntrl_msg));
+}
+
+VOID CopyMIBSExtendedSFParameters(struct bcm_mini_adapter *Adapter,
+ struct bcm_connect_mgr_params *psfLocalSet,
+ UINT uiSearchRuleIndex)
+{
+ struct bcm_mibs_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 00000000000..eee4f4795a7
--- /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;
+}
+
+bool IsReqGpioIsLedInNVM(struct bcm_mini_adapter *Adapter, UINT gpios)
+{
+ INT Status;
+ Status = (Adapter->gpioBitMap & gpios) ^ gpios;
+ if (Status)
+ return false;
+ else
+ return TRUE;
+}
+
+static INT LED_Blink(struct bcm_mini_adapter *Adapter, UINT GPIO_Num, UCHAR uiLedIndex,
+ ULONG timeout, INT num_of_time, enum bcm_led_events currdriverstate)
+{
+ int Status = STATUS_SUCCESS;
+ bool 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(struct bcm_mini_adapter *Adapter, UCHAR GPIO_Num_tx,
+ UCHAR uiTxLedIndex, UCHAR GPIO_Num_rx, UCHAR uiRxLedIndex,
+ enum bcm_led_events 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;
+ bool 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:
+ * <OSAL_STATUS_CODE>
+ * -----------------------------------------------------------------------------
+ */
+static INT ValidateDSDParamsChecksum(struct bcm_mini_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:
+ * <OSAL_STATUS_CODE>
+ * -----------------------------------------------------------------------------
+ */
+static INT ValidateHWParmStructure(struct bcm_mini_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(struct bcm_mini_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(struct bcm_mini_adapter *Adapter,
+ bool *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(struct bcm_mini_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(struct bcm_mini_adapter *Adapter, UCHAR *GPIO_num_tx,
+ UCHAR *GPIO_num_rx, UCHAR *uiLedTxIndex, UCHAR *uiLedRxIndex,
+ enum bcm_led_events 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(struct bcm_mini_adapter *Adapter)
+{
+ UINT uiIndex = 0;
+ UCHAR GPIO_num = 0;
+ UCHAR uiLedIndex = 0;
+ UINT uiResetValue = 0;
+ enum bcm_led_events 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(struct bcm_mini_adapter *Adapter)
+{
+ int Status = STATUS_SUCCESS;
+ bool 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 00000000000..bae40e22e11
--- /dev/null
+++ b/drivers/staging/bcm/led_control.h
@@ -0,0 +1,84 @@
+#ifndef _LED_CONTROL_H
+#define _LED_CONTROL_H
+
+#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 { \
+ unsigned int 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 { \
+ unsigned int 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)
+
+enum bcm_led_colors {
+ RED_LED = 1,
+ BLUE_LED = 2,
+ YELLOW_LED = 3,
+ GREEN_LED = 4
+};
+
+enum bcm_led_events {
+ 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
+}; /* Enumerated values of different driver states */
+
+/*
+ * Structure which stores the information of different LED types
+ * and corresponding LED state information of driver states
+ */
+struct bcm_led_state_info {
+ unsigned char LED_Type; /* specify GPIO number - use 0xFF if not used */
+ unsigned char LED_On_State; /* Bits set or reset for different states */
+ unsigned char LED_Blink_State; /* Bits set or reset for blinking LEDs for different states */
+ unsigned char GPIO_Num;
+ unsigned char BitPolarity; /* To represent whether H/W is normal polarity or reverse polarity */
+};
+
+struct bcm_led_info {
+ struct bcm_led_state_info LEDState[NUM_OF_LEDS];
+ bool bIdleMode_tx_from_host; /* Variable to notify whether driver came out from idlemode due to Host or target */
+ bool 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;
+ bool bLedInitDone;
+};
+
+/* 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 00000000000..63be3be62eb
--- /dev/null
+++ b/drivers/staging/bcm/nvm.c
@@ -0,0 +1,4613 @@
+#include "headers.h"
+
+#define DWORD unsigned int
+
+static int BcmDoChipSelect(struct bcm_mini_adapter *Adapter, unsigned int offset);
+static int BcmGetActiveDSD(struct bcm_mini_adapter *Adapter);
+static int BcmGetActiveISO(struct bcm_mini_adapter *Adapter);
+static unsigned int BcmGetEEPROMSize(struct bcm_mini_adapter *Adapter);
+static int BcmGetFlashCSInfo(struct bcm_mini_adapter *Adapter);
+static unsigned int BcmGetFlashSectorSize(struct bcm_mini_adapter *Adapter, unsigned int FlashSectorSizeSig, unsigned int FlashSectorSize);
+
+static VOID BcmValidateNvmType(struct bcm_mini_adapter *Adapter);
+static int BcmGetNvmSize(struct bcm_mini_adapter *Adapter);
+static unsigned int BcmGetFlashSize(struct bcm_mini_adapter *Adapter);
+static enum bcm_nvm_type BcmGetNvmType(struct bcm_mini_adapter *Adapter);
+
+static int BcmGetSectionValEndOffset(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal);
+
+static B_UINT8 IsOffsetWritable(struct bcm_mini_adapter *Adapter, unsigned int uiOffset);
+static int IsSectionWritable(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val Section);
+static int IsSectionExistInVendorInfo(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val section);
+
+static int ReadDSDPriority(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val dsd);
+static int ReadDSDSignature(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val dsd);
+static int ReadISOPriority(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val iso);
+static int ReadISOSignature(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val iso);
+
+static int CorruptDSDSig(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal);
+static int CorruptISOSig(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal);
+static int SaveHeaderIfPresent(struct bcm_mini_adapter *Adapter, PUCHAR pBuff, unsigned int uiSectAlignAddr);
+static int WriteToFlashWithoutSectorErase(struct bcm_mini_adapter *Adapter, PUINT pBuff,
+ enum bcm_flash2x_section_val eFlash2xSectionVal,
+ unsigned int uiOffset, unsigned int uiNumBytes);
+static enum bcm_flash2x_section_val getHighestPriDSD(struct bcm_mini_adapter *Adapter);
+static enum bcm_flash2x_section_val getHighestPriISO(struct bcm_mini_adapter *Adapter);
+
+static int BeceemFlashBulkRead(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes);
+
+static int BeceemFlashBulkWrite(
+ struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ bool bVerify);
+
+static int GetFlashBaseAddr(struct bcm_mini_adapter *Adapter);
+
+static int ReadBeceemEEPROMBulk(struct bcm_mini_adapter *Adapter, unsigned int dwAddress, unsigned int *pdwData, unsigned int dwNumData);
+
+/* Procedure: ReadEEPROMStatusRegister
+ *
+ * Description: Reads the standard EEPROM Status Register.
+ *
+ * Arguments:
+ * Adapter - ptr to Adapter object instance
+ * Returns:
+ * OSAL_STATUS_CODE
+ */
+static UCHAR ReadEEPROMStatusRegister(struct bcm_mini_adapter *Adapter)
+{
+ UCHAR uiData = 0;
+ DWORD dwRetries = MAX_EEPROM_RETRIES * RETRIES_PER_DELAY;
+ unsigned int uiStatus = 0;
+ unsigned int value = 0;
+ unsigned int 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))
+ udelay(1000);
+ 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:
+ */
+
+static int ReadBeceemEEPROMBulk(struct bcm_mini_adapter *Adapter,
+ DWORD dwAddress,
+ DWORD *pdwData,
+ DWORD dwNumWords)
+{
+ DWORD dwIndex = 0;
+ DWORD dwRetries = MAX_EEPROM_RETRIES * RETRIES_PER_DELAY;
+ unsigned int uiStatus = 0;
+ unsigned int value = 0;
+ unsigned int 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))
+ udelay(1000);
+ }
+
+ 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(struct bcm_mini_adapter *Adapter,
+ DWORD uiOffset,
+ DWORD *pBuffer)
+{
+ unsigned int uiData[8] = {0};
+ unsigned int uiByteOffset = 0;
+ unsigned int 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(struct bcm_mini_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.
+ * <FAILURE> - if failed.
+ */
+
+int BeceemEEPROMBulkRead(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes)
+{
+ unsigned int uiData[4] = {0};
+ /* unsigned int uiAddress = 0; */
+ unsigned int uiBytesRemaining = uiNumBytes;
+ unsigned int uiIndex = 0;
+ unsigned int uiTempOffset = 0;
+ unsigned int uiExtraBytes = 0;
+ unsigned int 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 (ReadBeceemEEPROMBulk(Adapter, uiOffset, &uiData[0], 4) == 0) {
+ 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 (ReadBeceemEEPROM(Adapter, uiOffset, &uiData[0]) == 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 (ReadBeceemEEPROM(Adapter, uiOffset, &uiData[0]) == 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.
+ * <FAILURE> - if failed.
+ */
+
+static int BeceemFlashBulkRead(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes)
+{
+ unsigned int uiIndex = 0;
+ unsigned int uiBytesToRead = uiNumBytes;
+ int Status = 0;
+ unsigned int 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:
+ * unsigned int - size of the FLASH Storage.
+ *
+ */
+
+static unsigned int BcmGetFlashSize(struct bcm_mini_adapter *Adapter)
+{
+ if (IsFlash2x(Adapter))
+ return Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(struct bcm_dsd_header);
+ else
+ return 32 * 1024;
+}
+
+/*
+ * Procedure: BcmGetEEPROMSize
+ *
+ * Description: Finds the size of EEPROM.
+ *
+ * Arguments:
+ * Adapter - ptr to Adapter object instance
+ *
+ * Returns:
+ * unsigned int - size of the EEPROM Storage.
+ *
+ */
+
+static unsigned int BcmGetEEPROMSize(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int uiData = 0;
+ unsigned int 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 (BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&uiData, 0, 4, TRUE) == 0) {
+ 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(struct bcm_mini_adapter *Adapter,
+ unsigned int addr,
+ unsigned int numOfSectors)
+{
+ unsigned int iIndex = 0, iRetries = 0;
+ unsigned int uiStatus = 0;
+ unsigned int value;
+ int bytes;
+
+ for (iIndex = 0; iIndex < numOfSectors; iIndex++) {
+ value = 0x06000000;
+ wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value));
+
+ value = (0xd8000000 | (addr & 0xFFFFFF));
+ wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value));
+ iRetries = 0;
+
+ 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;
+ }
+
+ 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++;
+ /* After every try lets make the CPU free for 10 ms. generally time taken by the
+ * the sector erase cycle is 500 ms to 40000 msec. hence sleeping 10 ms
+ * won't hamper performance in any case.
+ */
+ mdelay(10);
+ } while ((uiStatus & 0x1) && (iRetries < 400));
+
+ if (uiStatus & 0x1) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "iRetries crossing the limit of 80000\n");
+ return STATUS_FAILURE;
+ }
+
+ addr += Adapter->uiSectorSize;
+ }
+ 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(struct bcm_mini_adapter *Adapter,
+ unsigned int uiOffset,
+ PVOID pData)
+{
+ unsigned int uiStatus = 0;
+ int iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; /* 3 */
+ unsigned int 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))
+ udelay(1000);
+
+ } 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(struct bcm_mini_adapter *Adapter,
+ unsigned int uiOffset,
+ PVOID pData)
+{
+ /* unsigned int uiStatus = 0;
+ * int iRetries = 0;
+ * unsigned int uiReadBack = 0;
+ */
+ unsigned int uiStatus = 0;
+ int iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; /* 3 */
+ unsigned int value;
+ unsigned int 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))
+ udelay(1000);
+ } 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(struct bcm_mini_adapter *Adapter,
+ unsigned int uiOffset,
+ PVOID pData)
+{
+ unsigned int uiStatus = 0;
+ int iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; /* 3 */
+ ULONG ulData = *(PUCHAR)pData;
+ unsigned int 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))
+ udelay(1000);
+
+ } 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(struct bcm_mini_adapter *Adapter,
+ unsigned int uiOffset,
+ PVOID pData)
+{
+ unsigned int uiStatus = 0;
+ int iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; /* 3 */
+ /* unsigned int uiReadBack = 0; */
+ unsigned int value;
+ unsigned int 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))
+ udelay(1000);
+
+ } 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:
+ * <VOID>
+ *
+ */
+
+static VOID BcmRestoreBlockProtectStatus(struct bcm_mini_adapter *Adapter, ULONG ulWriteStatus)
+{
+ unsigned int 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(struct bcm_mini_adapter *Adapter, unsigned int uiOffset, unsigned int uiLength)
+{
+ ULONG ulStatus = 0;
+ ULONG ulWriteStatus = 0;
+ unsigned int 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(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ bool bVerify)
+{
+ PCHAR pTempBuff = NULL;
+ PUCHAR pcBuffer = (PUCHAR)pBuffer;
+ unsigned int uiIndex = 0;
+ unsigned int uiOffsetFromSectStart = 0;
+ unsigned int uiSectAlignAddr = 0;
+ unsigned int uiCurrSectOffsetAddr = 0;
+ unsigned int uiSectBoundary = 0;
+ unsigned int uiNumSectTobeRead = 0;
+ UCHAR ucReadBk[16] = {0};
+ ULONG ulStatus = 0;
+ int Status = STATUS_SUCCESS;
+ unsigned int uiTemp = 0;
+ unsigned int index = 0;
+ unsigned int 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 (!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) {
+ unsigned int 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(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ bool bVerify)
+{
+ PCHAR pTempBuff = NULL;
+ PUCHAR pcBuffer = (PUCHAR)pBuffer;
+ unsigned int uiIndex = 0;
+ unsigned int uiOffsetFromSectStart = 0;
+ unsigned int uiSectAlignAddr = 0;
+ unsigned int uiCurrSectOffsetAddr = 0;
+ unsigned int uiSectBoundary = 0;
+ unsigned int uiNumSectTobeRead = 0;
+ UCHAR ucReadBk[16] = {0};
+ ULONG ulStatus = 0;
+ unsigned int Status = STATUS_SUCCESS;
+ unsigned int uiTemp = 0;
+ unsigned int index = 0;
+ unsigned int 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 (!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: PropagateCalParamsFromFlashToMemory
+ *
+ * Description: Dumps the calibration section of EEPROM to DDR.
+ *
+ * Arguments:
+ * Adapter - ptr to Adapter object instance
+ * Returns:
+ * OSAL_STATUS_CODE
+ *
+ */
+
+int PropagateCalParamsFromFlashToMemory(struct bcm_mini_adapter *Adapter)
+{
+ PCHAR pBuff, pPtr;
+ unsigned int uiEepromSize = 0;
+ unsigned int uiBytesToCopy = 0;
+ /* unsigned int uiIndex = 0; */
+ unsigned int uiCalStartAddr = EEPROM_CALPARAM_START;
+ unsigned int uiMemoryLoc = EEPROM_CAL_DATA_INTERNAL_LOC;
+ unsigned int 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 -ENOMEM;
+
+ 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(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes)
+{
+ unsigned int uiRdbk = 0;
+ unsigned int uiIndex = 0;
+ unsigned int uiData = 0;
+ unsigned int 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(unsigned int)), uiNumBytes);
+ BeceemEEPROMBulkRead(Adapter, &uiRdbk, uiOffset, 4);
+
+ if (memcmp(&uiData, &uiRdbk, uiNumBytes))
+ return -1;
+
+ uiNumBytes = 0;
+ }
+ }
+
+ return 0;
+}
+
+static VOID BcmSwapWord(unsigned int *ptr1)
+{
+ unsigned int tempval = (unsigned int)*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(struct bcm_mini_adapter *Adapter, unsigned int uiData[], unsigned int uiOffset)
+{
+ unsigned int uiRetries = MAX_EEPROM_RETRIES * RETRIES_PER_DELAY;
+ unsigned int uiStatus = 0;
+ UCHAR uiEpromStatus = 0;
+ unsigned int 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))
+ udelay(1000);
+
+ 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))
+ udelay(1000);
+ }
+
+ 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(struct bcm_mini_adapter *Adapter,
+ PUCHAR pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ bool bVerify)
+{
+ unsigned int uiBytesToCopy = uiNumBytes;
+ /* unsigned int uiRdbk = 0; */
+ unsigned int uiData[4] = {0};
+ unsigned int uiIndex = 0;
+ unsigned int uiTempOffset = 0;
+ unsigned int 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.
+ * <FAILURE> - if failed.
+ */
+
+int BeceemNVMRead(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes)
+{
+ int Status = 0;
+
+ #if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS)
+ unsigned int 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.
+ * <FAILURE> - if failed.
+ */
+
+int BeceemNVMWrite(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ bool bVerify)
+{
+ int Status = 0;
+ unsigned int uiTemp = 0;
+ unsigned int uiMemoryLoc = EEPROM_CAL_DATA_INTERNAL_LOC;
+ unsigned int uiIndex = 0;
+
+ #if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS)
+ unsigned int value;
+ #endif
+
+ unsigned int 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.
+ * <FAILURE> - if failed.
+ */
+
+int BcmUpdateSectorSize(struct bcm_mini_adapter *Adapter, unsigned int uiSectorSize)
+{
+ int Status = -1;
+ struct bcm_flash_cs_info sFlashCsInfo = {0};
+ unsigned int uiTemp = 0;
+ unsigned int uiSectorSig = 0;
+ unsigned int uiCurrentSectorSize = 0;
+ unsigned int 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:
+ * unsigned int - sector size.
+ *
+ */
+
+static unsigned int BcmGetFlashSectorSize(struct bcm_mini_adapter *Adapter, unsigned int FlashSectorSizeSig, unsigned int FlashSectorSize)
+{
+ unsigned int uiSectorSize = 0;
+ unsigned int 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;
+ } else if (Adapter->uiSectorSizeInCFG <= MAX_SECTOR_SIZE &&
+ Adapter->uiSectorSizeInCFG >= MIN_SECTOR_SIZE) {
+ /* No valid size in FLASH, check if Config file has it. */
+ Adapter->uiSectorSize = Adapter->uiSectorSizeInCFG;
+ } else {
+ /* Init to Default, if none of the above works. */
+ 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:
+ * <OSAL_STATUS_CODE>
+ */
+
+static int BcmInitEEPROMQueues(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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:
+ * <OSAL_STATUS_CODE>
+ */
+
+int BcmInitNVM(struct bcm_mini_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(struct bcm_mini_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:
+ * <VOID>
+ */
+
+static VOID BcmValidateNvmType(struct bcm_mini_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(struct bcm_mini_adapter *Adapter)
+{
+ ULONG ulRDID = 0;
+ unsigned int 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(struct bcm_mini_adapter *psAdapter)
+{
+ if (!psAdapter) {
+ BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_PRINTK, 0, 0, "Adapter structure point is NULL");
+ return -EINVAL;
+ }
+ psAdapter->psFlashCSInfo = kzalloc(sizeof(struct bcm_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 = kzalloc(sizeof(struct bcm_flash2x_cs_info), GFP_KERNEL);
+ if (!psAdapter->psFlash2xCSInfo) {
+ BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_PRINTK, 0, 0, "Can't Allocate memory for Flash 2.x");
+ kfree(psAdapter->psFlashCSInfo);
+ return -ENOMEM;
+ }
+
+ psAdapter->psFlash2xVendorInfo = kzalloc(sizeof(struct bcm_flash2x_vendor_info), GFP_KERNEL);
+ if (!psAdapter->psFlash2xVendorInfo) {
+ 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(struct bcm_mini_adapter *psAdapter)
+{
+ if (!psAdapter) {
+ 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(struct bcm_flash2x_cs_info *psFlash2xCSInfo, struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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(struct bcm_flash2x_cs_info *psFlash2xCSInfo)
+{
+ unsigned int 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(struct bcm_flash_cs_info *psFlashCSInfo)
+{
+ /* unsigned int 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(struct bcm_mini_adapter *Adapter, enum bcm_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(struct bcm_mini_adapter *Adapter)
+{
+ B_UINT32 i = 0;
+ unsigned int 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(struct bcm_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(struct bcm_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(struct bcm_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:
+ * <VOID>
+ */
+
+static int BcmGetFlashCSInfo(struct bcm_mini_adapter *Adapter)
+{
+ /* struct bcm_flash_cs_info sFlashCsInfo = {0}; */
+
+ #if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS)
+ unsigned int value;
+ #endif
+
+ unsigned int 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(struct bcm_flash_cs_info));
+ memset(Adapter->psFlash2xCSInfo, 0 , sizeof(struct bcm_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(struct bcm_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(struct bcm_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 enum bcm_nvm_type BcmGetNvmType(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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 bcm_flash2x_section_val
+ *
+ * Return value:-
+ * On success it return the start offset of the provided section val
+ * On Failure -returns STATUS_FAILURE
+ */
+
+int BcmGetSectionValStartOffset(struct bcm_mini_adapter *Adapter, enum bcm_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 bcm_flash2x_section_val
+ *
+ * Return value:-
+ * On success it return the end offset of the provided section val
+ * On Failure -returns STATUS_FAILURE
+ */
+
+static int BcmGetSectionValEndOffset(struct bcm_mini_adapter *Adapter, enum bcm_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;
+ break;
+ 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 enum bcm_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(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ enum bcm_flash2x_section_val eFlash2xSectionVal,
+ unsigned int uiOffsetWithinSectionVal,
+ unsigned int uiNumBytes)
+{
+ int Status = STATUS_SUCCESS;
+ int SectionStartOffset = 0;
+ unsigned int uiAbsoluteOffset = 0;
+ unsigned int uiTemp = 0, value = 0;
+
+ if (!Adapter) {
+ 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 exist 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 enum bcm_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(struct bcm_mini_adapter *Adapter,
+ PUINT pBuffer,
+ enum bcm_flash2x_section_val eFlash2xSectVal,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes,
+ unsigned int bVerify)
+{
+ int Status = STATUS_SUCCESS;
+ unsigned int FlashSectValStartOffset = 0;
+ unsigned int uiTemp = 0, value = 0;
+
+ if (!Adapter) {
+ 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 exist 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 negative error code
+ *
+ */
+
+static int BcmGetActiveDSD(struct bcm_mini_adapter *Adapter)
+{
+ enum bcm_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(struct bcm_mini_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
+ *
+ */
+
+static B_UINT8 IsOffsetWritable(struct bcm_mini_adapter *Adapter, unsigned int uiOffset)
+{
+ unsigned int uiSectorNum = 0;
+ unsigned int uiWordOfSectorPermission = 0;
+ unsigned int 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(struct bcm_flash2x_bitmap *psFlash2xBitMap)
+{
+ struct bcm_mini_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(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_bitmap *psFlash2xBitMap)
+{
+ struct bcm_flash2x_cs_info *psFlash2xCSInfo = Adapter->psFlash2xCSInfo;
+ enum bcm_flash2x_section_val uiHighestPriDSD = 0;
+ enum bcm_flash2x_section_val uiHighestPriISO = 0;
+ bool SetActiveDSDDone = false;
+ bool 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(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectVal)
+{
+ unsigned int SectImagePriority = 0;
+ int Status = STATUS_SUCCESS;
+
+ /* struct bcm_dsd_header sDSD = {0};
+ * struct bcm_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(struct bcm_iso_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(struct bcm_iso_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(struct bcm_dsd_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(struct bcm_dsd_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(struct bcm_dsd_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(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_copy_section sCopySectStrut)
+{
+ PCHAR Buff = NULL;
+ enum bcm_flash2x_section_val eISOReadPart = 0, eISOWritePart = 0;
+ unsigned int uiReadOffsetWithinPart = 0, uiWriteOffsetWithinPart = 0;
+ unsigned int uiTotalDataToCopy = 0;
+ bool IsThisHeaderSector = false;
+ unsigned int sigOffset = 0;
+ unsigned int ISOLength = 0;
+ unsigned int Status = STATUS_SUCCESS;
+ unsigned int SigBuff[MAX_RW_SIZE];
+ unsigned int 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(struct bcm_iso_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(struct bcm_iso_header *, ISOImageMagicNumber);
+
+ Buff = kzalloc(Adapter->uiSectorSize, GFP_KERNEL);
+
+ if (!Buff) {
+ 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(struct bcm_mini_adapter *Adapter, enum bcm_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(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlashSectionVal)
+{
+ unsigned int uiSignature = 0;
+ unsigned int uiOffset = 0;
+
+ /* struct bcm_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(struct bcm_dsd_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(struct bcm_iso_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(struct bcm_mini_adapter *Adapter, struct bcm_flash2x_readwrite *psFlash2xReadWrite)
+{
+ unsigned int uiNumOfBytes = 0;
+ unsigned int uiSectStartOffset = 0;
+ unsigned int uiSectEndOffset = 0;
+
+ uiNumOfBytes = psFlash2xReadWrite->numOfBytes;
+
+ if (IsSectionExistInFlash(Adapter, psFlash2xReadWrite->Section) != TRUE) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Section<%x> does not exist 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 virtual 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);
+
+ /* psFlash2xReadWrite->offset and uiNumOfBytes are user controlled and can lead to integer overflows */
+ if (psFlash2xReadWrite->offset > uiSectEndOffset) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Invalid Request....");
+ return false;
+ }
+ if (uiNumOfBytes > uiSectEndOffset) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Invalid Request....");
+ return false;
+ }
+ /* 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(struct bcm_mini_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(struct bcm_mini_adapter *Adapter)
+{
+ unsigned int 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(struct bcm_mini_adapter *Adapter,
+ enum bcm_flash2x_section_val SrcSection,
+ enum bcm_flash2x_section_val DstSection,
+ unsigned int offset,
+ unsigned int numOfBytes)
+{
+ unsigned int BuffSize = 0;
+ unsigned int 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 = kzalloc(BuffSize, GFP_KERNEL);
+ if (!pBuff) {
+ 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
+ */
+
+static int SaveHeaderIfPresent(struct bcm_mini_adapter *Adapter, PUCHAR pBuff, unsigned int uiOffset)
+{
+ unsigned int offsetToProtect = 0, HeaderSizeToProtect = 0;
+ bool bHasHeader = false;
+ PUCHAR pTempBuff = NULL;
+ unsigned int uiSectAlignAddr = 0;
+ unsigned int 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(struct bcm_dsd_header);
+ bHasHeader = TRUE;
+ }
+
+ if (uiSectAlignAddr == BcmGetSectionValStartOffset(Adapter, ISO_IMAGE1) ||
+ uiSectAlignAddr == BcmGetSectionValStartOffset(Adapter, ISO_IMAGE2)) {
+ offsetToProtect = 0;
+ HeaderSizeToProtect = sizeof(struct bcm_iso_header);
+ bHasHeader = TRUE;
+ }
+ /* If Header is present overwrite passed buffer with this */
+ if (bHasHeader && (Adapter->bHeaderChangeAllowed == false)) {
+ pTempBuff = kzalloc(HeaderSizeToProtect, GFP_KERNEL);
+ if (!pTempBuff) {
+ 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(struct bcm_dsd_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(struct bcm_dsd_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(struct bcm_mini_adapter *Adapter, unsigned int offset)
+{
+ unsigned int FlashConfig = 0;
+ int ChipNum = 0;
+ unsigned int GPIOConfig = 0;
+ unsigned int 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<<CHIP_SELECT_BIT12)); /* 00 */
+
+ wrmalt(Adapter, FLASH_GPIO_CONFIG_REG, &GPIOConfig, 4);
+ udelay(100);
+
+ wrmalt(Adapter, FLASH_CONFIG_REG, &FlashConfig, 4);
+ udelay(100);
+
+ return STATUS_SUCCESS;
+}
+
+static int ReadDSDSignature(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val dsd)
+{
+ unsigned int uiDSDsig = 0;
+ /* unsigned int sigoffsetInMap = 0;
+ * struct bcm_dsd_header dsdHeader = {0};
+ */
+
+ /* sigoffsetInMap =(PUCHAR)&(dsdHeader.DSDImageMagicNumber) -(PUCHAR)&dsdHeader; */
+
+ if (dsd != DSD0 && dsd != DSD1 && dsd != DSD2) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "passed section value is not for DSDs");
+ return STATUS_FAILURE;
+ }
+ BcmFlash2xBulkRead(Adapter,
+ &uiDSDsig,
+ dsd,
+ Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + FIELD_OFFSET_IN_HEADER(struct bcm_dsd_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;
+}
+
+static int ReadDSDPriority(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val dsd)
+{
+ /* unsigned int priOffsetInMap = 0 ; */
+ unsigned int uiDSDPri = STATUS_FAILURE;
+ /* struct bcm_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(struct bcm_dsd_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;
+}
+
+static enum bcm_flash2x_section_val getHighestPriDSD(struct bcm_mini_adapter *Adapter)
+{
+ int DSDHighestPri = STATUS_FAILURE;
+ int DsdPri = 0;
+ enum bcm_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;
+}
+
+static int ReadISOSignature(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val iso)
+{
+ unsigned int uiISOsig = 0;
+ /* unsigned int sigoffsetInMap = 0;
+ * struct bcm_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(struct bcm_iso_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;
+}
+
+static int ReadISOPriority(struct bcm_mini_adapter *Adapter, enum bcm_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(struct bcm_iso_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;
+}
+
+static enum bcm_flash2x_section_val getHighestPriISO(struct bcm_mini_adapter *Adapter)
+{
+ int ISOHighestPri = STATUS_FAILURE;
+ int ISOPri = 0;
+ enum bcm_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;
+}
+
+static int WriteToFlashWithoutSectorErase(struct bcm_mini_adapter *Adapter,
+ PUINT pBuff,
+ enum bcm_flash2x_section_val eFlash2xSectionVal,
+ unsigned int uiOffset,
+ unsigned int uiNumBytes)
+{
+ #if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS)
+ unsigned int uiTemp = 0, value = 0;
+ unsigned int i = 0;
+ unsigned int uiPartOffset = 0;
+ #endif
+ unsigned int 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;
+}
+
+bool IsSectionExistInFlash(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val section)
+{
+ bool 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;
+}
+
+static int IsSectionWritable(struct bcm_mini_adapter *Adapter, enum bcm_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 exist", Section);
+ return false;
+ }
+
+ offset = BcmGetSectionValStartOffset(Adapter, Section);
+ if (offset == INVALID_OFFSET) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Section<%d> does not exist", 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(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal)
+{
+ PUCHAR pBuff = NULL;
+ unsigned int sig = 0;
+ unsigned int uiOffset = 0;
+ unsigned int BlockStatus = 0;
+ unsigned int 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 = kzalloc(MAX_RW_SIZE, GFP_KERNEL);
+ if (!pBuff) {
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Can't allocate memorey");
+ return -ENOMEM;
+ }
+
+ uiOffset = Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(struct bcm_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(struct bcm_mini_adapter *Adapter, enum bcm_flash2x_section_val eFlash2xSectionVal)
+{
+ PUCHAR pBuff = NULL;
+ unsigned int sig = 0;
+ unsigned int 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 = kzalloc(MAX_RW_SIZE, GFP_KERNEL);
+ if (!pBuff) {
+ 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;
+}
+
+bool IsNonCDLessDevice(struct bcm_mini_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 00000000000..e765cca5d96
--- /dev/null
+++ b/drivers/staging/bcm/nvm.h
@@ -0,0 +1,286 @@
+/***************************************************************************************
+ *
+ * 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_
+
+struct bcm_flash_cs_info {
+ u32 MagicNumber;
+ /* let the magic number be 0xBECE-F1A5 - F1A5 for "flas-h" */
+ u32 FlashLayoutVersion;
+ u32 ISOImageVersion;
+ u32 SCSIFirmwareVersion;
+ u32 OffsetFromZeroForPart1ISOImage;
+ u32 OffsetFromZeroForScsiFirmware;
+ u32 SizeOfScsiFirmware;
+ u32 OffsetFromZeroForPart2ISOImage;
+ u32 OffsetFromZeroForCalibrationStart;
+ u32 OffsetFromZeroForCalibrationEnd;
+ u32 OffsetFromZeroForVSAStart;
+ u32 OffsetFromZeroForVSAEnd;
+ u32 OffsetFromZeroForControlSectionStart;
+ u32 OffsetFromZeroForControlSectionData;
+ u32 CDLessInactivityTimeout;
+ u32 NewImageSignature;
+ u32 FlashSectorSizeSig;
+ u32 FlashSectorSize;
+ u32 FlashWriteSupportSize;
+ u32 TotalFlashSize;
+ u32 FlashBaseAddr;
+ u32 FlashPartMaxSize;
+ u32 IsCDLessDeviceBootSig;
+ /* MSC Timeout after reset to switch from MSC to NW Mode */
+ u32 MassStorageTimeout;
+};
+
+#define FLASH2X_TOTAL_SIZE (64 * 1024 * 1024)
+#define DEFAULT_SECTOR_SIZE (64 * 1024)
+
+struct bcm_flash2x_cs_info {
+ /* magic number as 0xBECE-F1A5 - F1A5 for "flas-h" */
+ u32 MagicNumber;
+ u32 FlashLayoutVersion;
+ u32 ISOImageVersion;
+ u32 SCSIFirmwareVersion;
+ u32 OffsetFromZeroForPart1ISOImage;
+ u32 OffsetFromZeroForScsiFirmware;
+ u32 SizeOfScsiFirmware;
+ u32 OffsetFromZeroForPart2ISOImage;
+ u32 OffsetFromZeroForDSDStart;
+ u32 OffsetFromZeroForDSDEnd;
+ u32 OffsetFromZeroForVSAStart;
+ u32 OffsetFromZeroForVSAEnd;
+ u32 OffsetFromZeroForControlSectionStart;
+ u32 OffsetFromZeroForControlSectionData;
+ /* NO Data Activity timeout to switch from MSC to NW Mode */
+ u32 CDLessInactivityTimeout;
+ u32 NewImageSignature;
+ u32 FlashSectorSizeSig;
+ u32 FlashSectorSize;
+ u32 FlashWriteSupportSize;
+ u32 TotalFlashSize;
+ u32 FlashBaseAddr;
+ u32 FlashPartMaxSize;
+ u32 IsCDLessDeviceBootSig;
+ /* MSC Timeout after reset to switch from MSC to NW Mode */
+ u32 MassStorageTimeout;
+ /* Flash Map 2.0 Field */
+ u32 OffsetISOImage1Part1Start;
+ u32 OffsetISOImage1Part1End;
+ u32 OffsetISOImage1Part2Start;
+ u32 OffsetISOImage1Part2End;
+ u32 OffsetISOImage1Part3Start;
+ u32 OffsetISOImage1Part3End;
+ u32 OffsetISOImage2Part1Start;
+ u32 OffsetISOImage2Part1End;
+ u32 OffsetISOImage2Part2Start;
+ u32 OffsetISOImage2Part2End;
+ u32 OffsetISOImage2Part3Start;
+ u32 OffsetISOImage2Part3End;
+ /* DSD Header offset from start of DSD */
+ u32 OffsetFromDSDStartForDSDHeader;
+ u32 OffsetFromZeroForDSD1Start;
+ u32 OffsetFromZeroForDSD1End;
+ u32 OffsetFromZeroForDSD2Start;
+ u32 OffsetFromZeroForDSD2End;
+ u32 OffsetFromZeroForVSA1Start;
+ u32 OffsetFromZeroForVSA1End;
+ u32 OffsetFromZeroForVSA2Start;
+ u32 OffsetFromZeroForVSA2End;
+ /*
+ * ACCESS_BITS_PER_SECTOR 2
+ * ACCESS_RW 0
+ * ACCESS_RO 1
+ * ACCESS_RESVD 2
+ * ACCESS_RESVD 3
+ */
+ u32 SectorAccessBitMap[FLASH2X_TOTAL_SIZE / (DEFAULT_SECTOR_SIZE * 16)];
+ /* All expansions to the control data structure should add here */
+};
+
+struct bcm_vendor_section_info {
+ u32 OffsetFromZeroForSectionStart;
+ u32 OffsetFromZeroForSectionEnd;
+ u32 AccessFlags;
+ u32 Reserved[16];
+};
+
+struct bcm_flash2x_vendor_info {
+ struct bcm_vendor_section_info VendorSection[TOTAL_SECTIONS];
+ u32 Reserved[16];
+};
+
+struct bcm_dsd_header {
+ u32 DSDImageSize;
+ u32 DSDImageCRC;
+ u32 DSDImagePriority;
+ /* We should not consider right now. Reading reserve is worthless. */
+ u32 Reserved[252]; /* Resvd for DSD Header */
+ u32 DSDImageMagicNumber;
+};
+
+struct bcm_iso_header {
+ u32 ISOImageMagicNumber;
+ u32 ISOImageSize;
+ u32 ISOImageCRC;
+ u32 ISOImagePriority;
+ /* We should not consider right now. Reading reserve is worthless. */
+ u32 Reserved[60]; /* Resvd for ISO Header extension */
+};
+
+#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) ((u8 *)&((HeaderPointer)(NULL))->Field - (u8 *)(NULL))
+
+#endif
+
diff --git a/drivers/staging/bcm/sort.c b/drivers/staging/bcm/sort.c
new file mode 100644
index 00000000000..d518c4217f1
--- /dev/null
+++ b/drivers/staging/bcm/sort.c
@@ -0,0 +1,52 @@
+#include "headers.h"
+#include <linux/sort.h>
+
+/*
+ * 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)
+{
+ struct bcm_packet_info const *pa = a;
+ struct bcm_packet_info const *pb = b;
+
+ if (!pa->bValid || !pb->bValid)
+ return 0;
+
+ return pa->u8TrafficPriority - pb->u8TrafficPriority;
+}
+
+VOID SortPackInfo(struct bcm_mini_adapter *Adapter)
+{
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG,
+ DBG_LVL_ALL, "<=======");
+
+ sort(Adapter->PackInfo, NO_OF_QUEUES, sizeof(struct bcm_packet_info),
+ compare_packet_info, NULL);
+}
+
+static int compare_classifiers(void const *a, void const *b)
+{
+ struct bcm_classifier_rule const *pa = a;
+ struct bcm_classifier_rule const *pb = b;
+
+ if (!pa->bUsed || !pb->bUsed)
+ return 0;
+
+ return pa->u8ClassifierRulePriority - pb->u8ClassifierRulePriority;
+}
+
+VOID SortClassifiers(struct bcm_mini_adapter *Adapter)
+{
+ BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG,
+ DBG_LVL_ALL, "<=======");
+
+ sort(Adapter->astClassifierTable, MAX_CLASSIFIERS,
+ sizeof(struct bcm_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 00000000000..dc45f9ab854
--- /dev/null
+++ b/drivers/staging/bcm/target_params.h
@@ -0,0 +1,57 @@
+#ifndef TARGET_PARAMS_H
+#define TARGET_PARAMS_H
+
+struct bcm_target_params {
+ u32 m_u32CfgVersion;
+ u32 m_u32CenterFrequency;
+ u32 m_u32BandAScan;
+ u32 m_u32BandBScan;
+ u32 m_u32BandCScan;
+ u32 m_u32ErtpsOptions;
+ u32 m_u32PHSEnable;
+ u32 m_u32HoEnable;
+ u32 m_u32HoReserved1;
+ u32 m_u32HoReserved2;
+ u32 m_u32MimoEnable;
+ u32 m_u32SecurityEnable;
+ u32 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 reporting in Idlemode Msg
+ * bit 2 = 1: Default PSC Enable in sleepmode
+ */
+ u32 m_u32PowerSavingModeOptions;
+ u32 m_u32ArqEnable;
+ /* From Version #3, the HARQ section renamed as general */
+ u32 m_u32HarqEnable;
+ u32 m_u32EEPROMFlag;
+ /* BINARY TYPE - 4th MSByte: Interface Type - 3rd MSByte: Vendor Type - 2nd MSByte
+ * Unused - LSByte
+ */
+ u32 m_u32Customize;
+ u32 m_u32ConfigBW; /* In Hz */
+ u32 m_u32ShutDownInitThresholdTimer;
+ u32 m_u32RadioParameter;
+ u32 m_u32PhyParameter1;
+ u32 m_u32PhyParameter2;
+ u32 m_u32PhyParameter3;
+ u32 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 */
+ u32 m_u32MaxMACDataperDLFrame;
+ u32 m_u32MaxMACDataperULFrame;
+ u32 m_u32Corr2MacFlags;
+ u32 HostDrvrConfig1;
+ u32 HostDrvrConfig2;
+ u32 HostDrvrConfig3;
+ u32 HostDrvrConfig4;
+ u32 HostDrvrConfig5;
+ u32 HostDrvrConfig6;
+ u32 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.
+ */
+ u32 m_u32BandAMCEnable;
+};
+
+#endif
diff --git a/drivers/staging/bcm/vendorspecificextn.c b/drivers/staging/bcm/vendorspecificextn.c
new file mode 100644
index 00000000000..2c57a16788c
--- /dev/null
+++ b/drivers/staging/bcm/vendorspecificextn.c
@@ -0,0 +1,142 @@
+#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, struct bcm_flash2x_vendor_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(struct bcm_mini_adapter *Adapter)
+{
+ return STATUS_SUCCESS;
+}
+
+/*
+ * Procedure: vendorextnExit
+ *
+ * Description: Free the resource associated with vendor extension NVM interface
+ *
+ * Arguments:
+ *
+ * Returns:
+ * STATUS_SUCCESS/STATUS_FAILURE
+ *
+ *
+ */
+INT vendorextnExit(struct bcm_mini_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(struct bcm_mini_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, enum bcm_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, enum bcm_flash2x_section_val SectionVal,
+ UINT offset, UINT numOfBytes, bool 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, enum bcm_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 00000000000..ff57f057045
--- /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, struct bcm_flash2x_vendor_info *pVendorInfo);
+INT vendorextnExit(struct bcm_mini_adapter *Adapter);
+INT vendorextnInit(struct bcm_mini_adapter *Adapter);
+INT vendorextnIoctl(struct bcm_mini_adapter *Adapter, UINT cmd, ULONG arg);
+INT vendorextnReadSection(PVOID pContext, PUCHAR pBuffer, enum bcm_flash2x_section_val SectionVal,
+ UINT offset, UINT numOfBytes);
+INT vendorextnWriteSection(PVOID pContext, PUCHAR pBuffer, enum bcm_flash2x_section_val SectionVal,
+ UINT offset, UINT numOfBytes, bool bVerify);
+INT vendorextnWriteSectionWithoutErase(PVOID pContext, PUCHAR pBuffer, enum bcm_flash2x_section_val SectionVal,
+ UINT offset, UINT numOfBytes);
+
+#endif /* */
diff --git a/drivers/staging/ced1401/Kconfig b/drivers/staging/ced1401/Kconfig
new file mode 100644
index 00000000000..ae36d1b2ba9
--- /dev/null
+++ b/drivers/staging/ced1401/Kconfig
@@ -0,0 +1,6 @@
+config CED1401
+ tristate "Cambridge Electronic Design 1401 USB support"
+ depends on USB
+ help
+ This driver supports the Cambridge Electronic Design 1401 USB device
+ (whatever that is.)
diff --git a/drivers/staging/ced1401/Makefile b/drivers/staging/ced1401/Makefile
new file mode 100644
index 00000000000..f0c114b2b4b
--- /dev/null
+++ b/drivers/staging/ced1401/Makefile
@@ -0,0 +1,3 @@
+
+obj-$(CONFIG_CED1401) := cedusb.o
+cedusb-objs := usb1401.o ced_ioc.o
diff --git a/drivers/staging/ced1401/TODO b/drivers/staging/ced1401/TODO
new file mode 100644
index 00000000000..9fd5630bdf4
--- /dev/null
+++ b/drivers/staging/ced1401/TODO
@@ -0,0 +1,10 @@
+TODO:
+ - coding syle fixes
+ - build warning fixups
+ - ioctl auditing
+ - usb api auditing
+ - proper USB minor number (it's stomping on an existing one right now.)
+
+Please send patches to Greg Kroah-Hartman <gregkh@linuxfoundation.org> and Cc:
+Alois Schlögl <alois.schloegl@ist.ac.at>
+
diff --git a/drivers/staging/ced1401/ced_ioc.c b/drivers/staging/ced1401/ced_ioc.c
new file mode 100644
index 00000000000..ebbc5090f21
--- /dev/null
+++ b/drivers/staging/ced1401/ced_ioc.c
@@ -0,0 +1,1494 @@
+/* ced_ioc.c
+ ioctl part of the 1401 usb device driver for linux.
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute 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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/page-flags.h>
+#include <linux/pagemap.h>
+#include <linux/jiffies.h>
+
+#include "usb1401.h"
+
+/****************************************************************************
+** FlushOutBuff
+**
+** Empties the Output buffer and sets int lines. Used from user level only
+****************************************************************************/
+static void FlushOutBuff(DEVICE_EXTENSION *pdx)
+{
+ dev_dbg(&pdx->interface->dev, "%s: currentState=%d\n",
+ __func__, pdx->sCurrentState);
+ if (pdx->sCurrentState == U14ERR_TIME) /* Do nothing if hardware in trouble */
+ return;
+ /* Kill off any pending I/O */
+ /* CharSend_Cancel(pdx); */
+ spin_lock_irq(&pdx->charOutLock);
+ pdx->dwNumOutput = 0;
+ pdx->dwOutBuffGet = 0;
+ pdx->dwOutBuffPut = 0;
+ spin_unlock_irq(&pdx->charOutLock);
+}
+
+/****************************************************************************
+**
+** FlushInBuff
+**
+** Empties the input buffer and sets int lines
+****************************************************************************/
+static void FlushInBuff(DEVICE_EXTENSION *pdx)
+{
+ dev_dbg(&pdx->interface->dev, "%s: currentState=%d\n",
+ __func__, pdx->sCurrentState);
+ if (pdx->sCurrentState == U14ERR_TIME) /* Do nothing if hardware in trouble */
+ return;
+ /* Kill off any pending I/O */
+ /* CharRead_Cancel(pDevObject); */
+ spin_lock_irq(&pdx->charInLock);
+ pdx->dwNumInput = 0;
+ pdx->dwInBuffGet = 0;
+ pdx->dwInBuffPut = 0;
+ spin_unlock_irq(&pdx->charInLock);
+}
+
+/****************************************************************************
+** PutChars
+**
+** Utility routine to copy chars into the output buffer and fire them off.
+** called from user mode, holds charOutLock.
+****************************************************************************/
+static int PutChars(DEVICE_EXTENSION *pdx, const char *pCh,
+ unsigned int uCount)
+{
+ int iReturn;
+ spin_lock_irq(&pdx->charOutLock); /* get the output spin lock */
+ if ((OUTBUF_SZ - pdx->dwNumOutput) >= uCount) {
+ unsigned int u;
+ for (u = 0; u < uCount; u++) {
+ pdx->outputBuffer[pdx->dwOutBuffPut++] = pCh[u];
+ if (pdx->dwOutBuffPut >= OUTBUF_SZ)
+ pdx->dwOutBuffPut = 0;
+ }
+ pdx->dwNumOutput += uCount;
+ spin_unlock_irq(&pdx->charOutLock);
+ iReturn = SendChars(pdx); /* ...give a chance to transmit data */
+ } else {
+ iReturn = U14ERR_NOOUT; /* no room at the out (ha-ha) */
+ spin_unlock_irq(&pdx->charOutLock);
+ }
+ return iReturn;
+}
+
+/*****************************************************************************
+** Add the data in pData (local pointer) of length n to the output buffer, and
+** trigger an output transfer if this is appropriate. User mode.
+** Holds the io_mutex
+*****************************************************************************/
+int SendString(DEVICE_EXTENSION *pdx, const char __user *pData,
+ unsigned int n)
+{
+ int iReturn = U14ERR_NOERROR; /* assume all will be well */
+ char buffer[OUTBUF_SZ + 1]; /* space in our address space for characters */
+ if (n > OUTBUF_SZ) /* check space in local buffer... */
+ return U14ERR_NOOUT; /* ...too many characters */
+ if (copy_from_user(buffer, pData, n))
+ return -EFAULT;
+ buffer[n] = 0; /* terminate for debug purposes */
+
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ if (n > 0) { /* do nothing if nowt to do! */
+ dev_dbg(&pdx->interface->dev, "%s: n=%d>%s<\n",
+ __func__, n, buffer);
+ iReturn = PutChars(pdx, buffer, n);
+ }
+
+ Allowi(pdx); /* make sure we have input int */
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** SendChar
+**
+** Sends a single character to the 1401. User mode, holds io_mutex.
+****************************************************************************/
+int SendChar(DEVICE_EXTENSION *pdx, char c)
+{
+ int iReturn;
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ iReturn = PutChars(pdx, &c, 1);
+ dev_dbg(&pdx->interface->dev, "SendChar >%c< (0x%02x)\n", c, c);
+ Allowi(pdx); /* Make sure char reads are running */
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/***************************************************************************
+**
+** Get1401State
+**
+** Retrieves state information from the 1401, adjusts the 1401 state held
+** in the device extension to indicate the current 1401 type.
+**
+** *state is updated with information about the 1401 state as returned by the
+** 1401. The low byte is a code for what 1401 is doing:
+**
+** 0 normal 1401 operation
+** 1 sending chars to host
+** 2 sending block data to host
+** 3 reading block data from host
+** 4 sending an escape sequence to the host
+** 0x80 1401 is executing self-test, in which case the upper word
+** is the last error code seen (or zero for no new error).
+**
+** *error is updated with error information if a self-test error code
+** is returned in the upper word of state.
+**
+** both state and error are set to -1 if there are comms problems, and
+** to zero if there is a simple failure.
+**
+** return error code (U14ERR_NOERROR for OK)
+*/
+int Get1401State(DEVICE_EXTENSION *pdx, __u32 *state, __u32 *error)
+{
+ int nGot;
+ dev_dbg(&pdx->interface->dev, "%s: entry\n", __func__);
+
+ *state = 0xFFFFFFFF; /* Start off with invalid state */
+ nGot = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+ GET_STATUS, (D_TO_H | VENDOR | DEVREQ), 0, 0,
+ pdx->statBuf, sizeof(pdx->statBuf), HZ);
+ if (nGot != sizeof(pdx->statBuf)) {
+ dev_err(&pdx->interface->dev,
+ "%s: FAILED, return code %d\n", __func__, nGot);
+ pdx->sCurrentState = U14ERR_TIME; /* Indicate that things are very wrong indeed */
+ *state = 0; /* Force status values to a known state */
+ *error = 0;
+ } else {
+ int nDevice;
+ dev_dbg(&pdx->interface->dev,
+ "%s: Success, state: 0x%x, 0x%x\n",
+ __func__, pdx->statBuf[0], pdx->statBuf[1]);
+
+ *state = pdx->statBuf[0]; /* Return the state values to the calling code */
+ *error = pdx->statBuf[1];
+
+ nDevice = pdx->udev->descriptor.bcdDevice >> 8; /* 1401 type code value */
+ switch (nDevice) { /* so we can clean up current state */
+ case 0:
+ pdx->sCurrentState = U14ERR_U1401;
+ break;
+
+ default: /* allow lots of device codes for future 1401s */
+ if ((nDevice >= 1) && (nDevice <= 23))
+ pdx->sCurrentState = (short)(nDevice + 6);
+ else
+ pdx->sCurrentState = U14ERR_ILL;
+ break;
+ }
+ }
+
+ return pdx->sCurrentState >= 0 ? U14ERR_NOERROR : pdx->sCurrentState;
+}
+
+/****************************************************************************
+** ReadWrite_Cancel
+**
+** Kills off staged read\write request from the USB if one is pending.
+****************************************************************************/
+int ReadWrite_Cancel(DEVICE_EXTENSION *pdx)
+{
+ dev_dbg(&pdx->interface->dev, "%s: entry %d\n",
+ __func__, pdx->bStagedUrbPending);
+#ifdef NOT_WRITTEN_YET
+ int ntStatus = STATUS_SUCCESS;
+ bool bResult = false;
+ unsigned int i;
+ /* We can fill this in when we know how we will implement the staged transfer stuff */
+ spin_lock_irq(&pdx->stagedLock);
+
+ if (pdx->bStagedUrbPending) { /* anything to be cancelled? May need more... */
+ dev_info(&pdx->interface - dev,
+ "ReadWrite_Cancel about to cancel Urb\n");
+ /* Clear the staging done flag */
+ /* KeClearEvent(&pdx->StagingDoneEvent); */
+ USB_ASSERT(pdx->pStagedIrp != NULL);
+
+ /* Release the spinlock first otherwise the completion routine may hang */
+ /* on the spinlock while this function hands waiting for the event. */
+ spin_unlock_irq(&pdx->stagedLock);
+ bResult = IoCancelIrp(pdx->pStagedIrp); /* Actually do the cancel */
+ if (bResult) {
+ LARGE_INTEGER timeout;
+ timeout.QuadPart = -10000000; /* Use a timeout of 1 second */
+ dev_info(&pdx->interface - dev,
+ "%s: about to wait till done\n", __func__);
+ ntStatus =
+ KeWaitForSingleObject(&pdx->StagingDoneEvent,
+ Executive, KernelMode, FALSE,
+ &timeout);
+ } else {
+ dev_info(&pdx->interface - dev,
+ "%s: cancellation failed\n", __func__);
+ ntStatus = U14ERR_FAIL;
+ }
+ USB_KdPrint(DBGLVL_DEFAULT,
+ ("ReadWrite_Cancel ntStatus = 0x%x decimal %d\n",
+ ntStatus, ntStatus));
+ } else
+ spin_unlock_irq(&pdx->stagedLock);
+
+ dev_info(&pdx->interface - dev, "%s: done\n", __func__);
+ return ntStatus;
+#else
+ return U14ERR_NOERROR;
+#endif
+
+}
+
+/***************************************************************************
+** InSelfTest - utility to check in self test. Return 1 for ST, 0 for not or
+** a -ve error code if we failed for some reason.
+***************************************************************************/
+static int InSelfTest(DEVICE_EXTENSION *pdx, unsigned int *pState)
+{
+ unsigned int state, error;
+ int iReturn = Get1401State(pdx, &state, &error); /* see if in self-test */
+ if (iReturn == U14ERR_NOERROR) /* if all still OK */
+ iReturn = (state == (unsigned int)-1) || /* TX problem or... */
+ ((state & 0xff) == 0x80); /* ...self test */
+ *pState = state; /* return actual state */
+ return iReturn;
+}
+
+/***************************************************************************
+** Is1401 - ALWAYS CALLED HOLDING THE io_mutex
+**
+** Tests for the current state of the 1401. Sets sCurrentState:
+**
+** U14ERR_NOIF 1401 i/f card not installed (not done here)
+** U14ERR_OFF 1401 apparently not switched on
+** U14ERR_NC 1401 appears to be not connected
+** U14ERR_ILL 1401 if it is there its not very well at all
+** U14ERR_TIME 1401 appears OK, but doesn't communicate - very bad
+** U14ERR_STD 1401 OK and ready for use
+** U14ERR_PLUS 1401+ OK and ready for use
+** U14ERR_U1401 Micro1401 OK and ready for use
+** U14ERR_POWER Power1401 OK and ready for use
+** U14ERR_U14012 Micro1401 mkII OK and ready for use
+**
+** Returns TRUE if a 1401 detected and OK, else FALSE
+****************************************************************************/
+bool Is1401(DEVICE_EXTENSION *pdx)
+{
+ int iReturn;
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ ced_draw_down(pdx); /* wait for, then kill outstanding Urbs */
+ FlushInBuff(pdx); /* Clear out input buffer & pipe */
+ FlushOutBuff(pdx); /* Clear output buffer & pipe */
+
+ /* The next call returns 0 if OK, but has returned 1 in the past, meaning that */
+ /* usb_unlock_device() is needed... now it always is */
+ iReturn = usb_lock_device_for_reset(pdx->udev, pdx->interface);
+
+ /* release the io_mutex because if we don't, we will deadlock due to system */
+ /* calls back into the driver. */
+ mutex_unlock(&pdx->io_mutex); /* locked, so we will not get system calls */
+ if (iReturn >= 0) { /* if we failed */
+ iReturn = usb_reset_device(pdx->udev); /* try to do the reset */
+ usb_unlock_device(pdx->udev); /* undo the lock */
+ }
+
+ mutex_lock(&pdx->io_mutex); /* hold stuff off while we wait */
+ pdx->dwDMAFlag = MODE_CHAR; /* Clear DMA mode flag regardless! */
+ if (iReturn == 0) { /* if all is OK still */
+ unsigned int state;
+ iReturn = InSelfTest(pdx, &state); /* see if likely in self test */
+ if (iReturn > 0) { /* do we need to wait for self-test? */
+ unsigned long ulTimeOut = jiffies + 30 * HZ; /* when to give up */
+ while ((iReturn > 0) && time_before(jiffies, ulTimeOut)) {
+ schedule(); /* let other stuff run */
+ iReturn = InSelfTest(pdx, &state); /* see if done yet */
+ }
+ }
+
+ if (iReturn == 0) /* if all is OK... */
+ iReturn = state == 0; /* then success is that the state is 0 */
+ } else
+ iReturn = 0; /* we failed */
+ pdx->bForceReset = false; /* Clear forced reset flag now */
+
+ return iReturn > 0;
+}
+
+/****************************************************************************
+** QuickCheck - ALWAYS CALLED HOLDING THE io_mutex
+** This is used to test for a 1401. It will try to do a quick check if all is
+** OK, that is the 1401 was OK the last time it was asked, and there is no DMA
+** in progress, and if the bTestBuff flag is set, the character buffers must be
+** empty too. If the quick check shows that the state is still the same, then
+** all is OK.
+**
+** If any of the above conditions are not met, or if the state or type of the
+** 1401 has changed since the previous test, the full Is1401 test is done, but
+** only if bCanReset is also TRUE.
+**
+** The return value is TRUE if a useable 1401 is found, FALSE if not
+*/
+bool QuickCheck(DEVICE_EXTENSION *pdx, bool bTestBuff, bool bCanReset)
+{
+ bool bRet = false; /* assume it will fail and we will reset */
+ bool bShortTest;
+
+ bShortTest = ((pdx->dwDMAFlag == MODE_CHAR) && /* no DMA running */
+ (!pdx->bForceReset) && /* Not had a real reset forced */
+ (pdx->sCurrentState >= U14ERR_STD)); /* No 1401 errors stored */
+
+ dev_dbg(&pdx->interface->dev,
+ "%s: DMAFlag:%d, state:%d, force:%d, testBuff:%d, short:%d\n",
+ __func__, pdx->dwDMAFlag, pdx->sCurrentState, pdx->bForceReset,
+ bTestBuff, bShortTest);
+
+ if ((bTestBuff) && /* Buffer check requested, and... */
+ (pdx->dwNumInput || pdx->dwNumOutput)) { /* ...characters were in the buffer? */
+ bShortTest = false; /* Then do the full test */
+ dev_dbg(&pdx->interface->dev,
+ "%s: will reset as buffers not empty\n", __func__);
+ }
+
+ if (bShortTest || !bCanReset) { /* Still OK to try the short test? */
+ /* Always test if no reset - we want state update */
+ unsigned int state, error;
+ dev_dbg(&pdx->interface->dev, "%s: Get1401State\n", __func__);
+ if (Get1401State(pdx, &state, &error) == U14ERR_NOERROR) { /* Check on the 1401 state */
+ if ((state & 0xFF) == 0) /* If call worked, check the status value */
+ bRet = true; /* If that was zero, all is OK, no reset needed */
+ }
+ }
+
+ if (!bRet && bCanReset) { /* If all not OK, then */
+ dev_info(&pdx->interface->dev, "%s: Is1401 %d %d %d %d\n",
+ __func__, bShortTest, pdx->sCurrentState, bTestBuff,
+ pdx->bForceReset);
+ bRet = Is1401(pdx); /* do full test */
+ }
+
+ return bRet;
+}
+
+/****************************************************************************
+** Reset1401
+**
+** Resets the 1401 and empties the i/o buffers
+*****************************************************************************/
+int Reset1401(DEVICE_EXTENSION *pdx)
+{
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ dev_dbg(&pdx->interface->dev, "%s: About to call QuickCheck\n",
+ __func__);
+ QuickCheck(pdx, true, true); /* Check 1401, reset if not OK */
+ mutex_unlock(&pdx->io_mutex);
+ return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** GetChar
+**
+** Gets a single character from the 1401
+****************************************************************************/
+int GetChar(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = U14ERR_NOIN; /* assume we will get nothing */
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ Allowi(pdx); /* Make sure char reads are running */
+ SendChars(pdx); /* and send any buffered chars */
+
+ spin_lock_irq(&pdx->charInLock);
+ if (pdx->dwNumInput > 0) { /* worth looking */
+ iReturn = pdx->inputBuffer[pdx->dwInBuffGet++];
+ if (pdx->dwInBuffGet >= INBUF_SZ)
+ pdx->dwInBuffGet = 0;
+ pdx->dwNumInput--;
+ } else
+ iReturn = U14ERR_NOIN; /* no input data to read */
+ spin_unlock_irq(&pdx->charInLock);
+
+ Allowi(pdx); /* Make sure char reads are running */
+
+ mutex_unlock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ return iReturn;
+}
+
+/****************************************************************************
+** GetString
+**
+** Gets a string from the 1401. Returns chars up to the next CR or when
+** there are no more to read or nowhere to put them. CR is translated to
+** 0 and counted as a character. If the string does not end in a 0, we will
+** add one, if there is room, but it is not counted as a character.
+**
+** returns the count of characters (including the terminator, or 0 if none
+** or a negative error code.
+****************************************************************************/
+int GetString(DEVICE_EXTENSION *pdx, char __user *pUser, int n)
+{
+ int nAvailable; /* character in the buffer */
+ int iReturn = U14ERR_NOIN;
+ if (n <= 0)
+ return -ENOMEM;
+
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ Allowi(pdx); /* Make sure char reads are running */
+ SendChars(pdx); /* and send any buffered chars */
+
+ spin_lock_irq(&pdx->charInLock);
+ nAvailable = pdx->dwNumInput; /* characters available now */
+ if (nAvailable > n) /* read max of space in pUser... */
+ nAvailable = n; /* ...or input characters */
+
+ if (nAvailable > 0) { /* worth looking? */
+ char buffer[INBUF_SZ + 1]; /* space for a linear copy of data */
+ int nGot = 0;
+ int nCopyToUser; /* number to copy to user */
+ char cData;
+ do {
+ cData = pdx->inputBuffer[pdx->dwInBuffGet++];
+ if (cData == CR_CHAR) /* replace CR with zero */
+ cData = (char)0;
+
+ if (pdx->dwInBuffGet >= INBUF_SZ)
+ pdx->dwInBuffGet = 0; /* wrap buffer pointer */
+
+ buffer[nGot++] = cData; /* save the output */
+ } while ((nGot < nAvailable) && cData);
+
+ nCopyToUser = nGot; /* what to copy... */
+ if (cData) { /* do we need null */
+ buffer[nGot] = (char)0; /* make it tidy */
+ if (nGot < n) /* if space in user buffer... */
+ ++nCopyToUser; /* ...copy the 0 as well. */
+ }
+
+ pdx->dwNumInput -= nGot;
+ spin_unlock_irq(&pdx->charInLock);
+
+ dev_dbg(&pdx->interface->dev, "%s: read %d characters >%s<\n",
+ __func__, nGot, buffer);
+ if (copy_to_user(pUser, buffer, nCopyToUser))
+ iReturn = -EFAULT;
+ else
+ iReturn = nGot; /* report characters read */
+ } else
+ spin_unlock_irq(&pdx->charInLock);
+
+ Allowi(pdx); /* Make sure char reads are running */
+ mutex_unlock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+
+ return iReturn;
+}
+
+/*******************************************************************************
+** Get count of characters in the inout buffer.
+*******************************************************************************/
+int Stat1401(DEVICE_EXTENSION *pdx)
+{
+ int iReturn;
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ Allowi(pdx); /* make sure we allow pending chars */
+ SendChars(pdx); /* in both directions */
+ iReturn = pdx->dwNumInput; /* no lock as single read */
+ mutex_unlock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ return iReturn;
+}
+
+/****************************************************************************
+** LineCount
+**
+** Returns the number of newline chars in the buffer. There is no need for
+** any fancy interlocks as we only read the interrupt routine data, and the
+** system is arranged so nothing can be destroyed.
+****************************************************************************/
+int LineCount(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = 0; /* will be count of line ends */
+
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ Allowi(pdx); /* Make sure char reads are running */
+ SendChars(pdx); /* and send any buffered chars */
+ spin_lock_irq(&pdx->charInLock); /* Get protection */
+
+ if (pdx->dwNumInput > 0) { /* worth looking? */
+ unsigned int dwIndex = pdx->dwInBuffGet; /* start at first available */
+ unsigned int dwEnd = pdx->dwInBuffPut; /* Position for search end */
+ do {
+ if (pdx->inputBuffer[dwIndex++] == CR_CHAR)
+ ++iReturn; /* inc count if CR */
+
+ if (dwIndex >= INBUF_SZ) /* see if we fall off buff */
+ dwIndex = 0;
+ } while (dwIndex != dwEnd); /* go to last available */
+ }
+
+ spin_unlock_irq(&pdx->charInLock);
+ dev_dbg(&pdx->interface->dev, "%s: returned %d\n", __func__, iReturn);
+ mutex_unlock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ return iReturn;
+}
+
+/****************************************************************************
+** GetOutBufSpace
+**
+** Gets the space in the output buffer. Called from user code.
+*****************************************************************************/
+int GetOutBufSpace(DEVICE_EXTENSION *pdx)
+{
+ int iReturn;
+ mutex_lock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ SendChars(pdx); /* send any buffered chars */
+ iReturn = (int)(OUTBUF_SZ - pdx->dwNumOutput); /* no lock needed for single read */
+ dev_dbg(&pdx->interface->dev, "%s: %d\n", __func__, iReturn);
+ mutex_unlock(&pdx->io_mutex); /* Protect disconnect from new i/o */
+ return iReturn;
+}
+
+/****************************************************************************
+**
+** ClearArea
+**
+** Clears up a transfer area. This is always called in the context of a user
+** request, never from a call-back.
+****************************************************************************/
+int ClearArea(DEVICE_EXTENSION *pdx, int nArea)
+{
+ int iReturn = U14ERR_NOERROR;
+
+ if ((nArea < 0) || (nArea >= MAX_TRANSAREAS)) {
+ iReturn = U14ERR_BADAREA;
+ dev_err(&pdx->interface->dev, "%s: Attempt to clear area %d\n",
+ __func__, nArea);
+ } else {
+ TRANSAREA *pTA = &pdx->rTransDef[nArea]; /* to save typing */
+ if (!pTA->bUsed) /* if not used... */
+ iReturn = U14ERR_NOTSET; /* ...nothing to be done */
+ else {
+ /* We must save the memory we return as we shouldn't mess with memory while */
+ /* holding a spin lock. */
+ struct page **pPages = NULL; /*save page address list*/
+ int nPages = 0; /* and number of pages */
+ int np;
+
+ dev_dbg(&pdx->interface->dev, "%s: area %d\n",
+ __func__, nArea);
+ spin_lock_irq(&pdx->stagedLock);
+ if ((pdx->StagedId == nArea)
+ && (pdx->dwDMAFlag > MODE_CHAR)) {
+ iReturn = U14ERR_UNLOCKFAIL; /* cannot delete as in use */
+ dev_err(&pdx->interface->dev,
+ "%s: call on area %d while active\n",
+ __func__, nArea);
+ } else {
+ pPages = pTA->pPages; /* save page address list */
+ nPages = pTA->nPages; /* and page count */
+ if (pTA->dwEventSz) /* if events flagging in use */
+ wake_up_interruptible(&pTA->wqEvent); /* release anything that was waiting */
+
+ if (pdx->bXFerWaiting
+ && (pdx->rDMAInfo.wIdent == nArea))
+ pdx->bXFerWaiting = false; /* Cannot have pending xfer if area cleared */
+
+ /* Clean out the TRANSAREA except for the wait queue, which is at the end */
+ /* This sets bUsed to false and dwEventSz to 0 to say area not used and no events. */
+ memset(pTA, 0,
+ sizeof(TRANSAREA) -
+ sizeof(wait_queue_head_t));
+ }
+ spin_unlock_irq(&pdx->stagedLock);
+
+ if (pPages) { /* if we decided to release the memory */
+ /* Now we must undo the pinning down of the pages. We will assume the worst and mark */
+ /* all the pages as dirty. Don't be tempted to move this up above as you must not be */
+ /* holding a spin lock to do this stuff as it is not atomic. */
+ dev_dbg(&pdx->interface->dev, "%s: nPages=%d\n",
+ __func__, nPages);
+
+ for (np = 0; np < nPages; ++np) {
+ if (pPages[np]) {
+ SetPageDirty(pPages[np]);
+ page_cache_release(pPages[np]);
+ }
+ }
+
+ kfree(pPages);
+ dev_dbg(&pdx->interface->dev,
+ "%s: kfree(pPages) done\n", __func__);
+ }
+ }
+ }
+
+ return iReturn;
+}
+
+/****************************************************************************
+** SetArea
+**
+** Sets up a transfer area - the functional part. Called by both
+** SetTransfer and SetCircular.
+****************************************************************************/
+static int SetArea(DEVICE_EXTENSION *pdx, int nArea, char __user *puBuf,
+ unsigned int dwLength, bool bCircular, bool bCircToHost)
+{
+ /* Start by working out the page aligned start of the area and the size */
+ /* of the area in pages, allowing for the start not being aligned and the */
+ /* end needing to be rounded up to a page boundary. */
+ unsigned long ulStart = ((unsigned long)puBuf) & PAGE_MASK;
+ unsigned int ulOffset = ((unsigned long)puBuf) & (PAGE_SIZE - 1);
+ int len = (dwLength + ulOffset + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+ TRANSAREA *pTA = &pdx->rTransDef[nArea]; /* to save typing */
+ struct page **pPages = NULL; /* space for page tables */
+ int nPages = 0; /* and number of pages */
+
+ int iReturn = ClearArea(pdx, nArea); /* see if OK to use this area */
+ if ((iReturn != U14ERR_NOTSET) && /* if not area unused and... */
+ (iReturn != U14ERR_NOERROR)) /* ...not all OK, then... */
+ return iReturn; /* ...we cannot use this area */
+
+ if (!access_ok(VERIFY_WRITE, puBuf, dwLength)) /* if we cannot access the memory... */
+ return -EFAULT; /* ...then we are done */
+
+ /* Now allocate space to hold the page pointer and virtual address pointer tables */
+ pPages = kmalloc(len * sizeof(struct page *), GFP_KERNEL);
+ if (!pPages) {
+ iReturn = U14ERR_NOMEMORY;
+ goto error;
+ }
+ dev_dbg(&pdx->interface->dev, "%s: %p, length=%06x, circular %d\n",
+ __func__, puBuf, dwLength, bCircular);
+
+ /* To pin down user pages we must first acquire the mapping semaphore. */
+ nPages = get_user_pages_fast(ulStart, len, 1, pPages);
+ dev_dbg(&pdx->interface->dev, "%s: nPages = %d\n", __func__, nPages);
+
+ if (nPages > 0) { /* if we succeeded */
+ /* If you are tempted to use page_address (form LDD3), forget it. You MUST use */
+ /* kmap() or kmap_atomic() to get a virtual address. page_address will give you */
+ /* (null) or at least it does in this context with an x86 machine. */
+ spin_lock_irq(&pdx->stagedLock);
+ pTA->lpvBuff = puBuf; /* keep start of region (user address) */
+ pTA->dwBaseOffset = ulOffset; /* save offset in first page to start of xfer */
+ pTA->dwLength = dwLength; /* Size if the region in bytes */
+ pTA->pPages = pPages; /* list of pages that are used by buffer */
+ pTA->nPages = nPages; /* number of pages */
+
+ pTA->bCircular = bCircular;
+ pTA->bCircToHost = bCircToHost;
+
+ pTA->aBlocks[0].dwOffset = 0;
+ pTA->aBlocks[0].dwSize = 0;
+ pTA->aBlocks[1].dwOffset = 0;
+ pTA->aBlocks[1].dwSize = 0;
+ pTA->bUsed = true; /* This is now a used block */
+
+ spin_unlock_irq(&pdx->stagedLock);
+ iReturn = U14ERR_NOERROR; /* say all was well */
+ } else {
+ iReturn = U14ERR_LOCKFAIL;
+ goto error;
+ }
+
+ return iReturn;
+
+error:
+ kfree(pPages);
+ return iReturn;
+}
+
+/****************************************************************************
+** SetTransfer
+**
+** Sets up a transfer area record. If the area is already set, we attempt to
+** unset it. Unsetting will fail if the area is booked, and a transfer to that
+** area is in progress. Otherwise, we will release the area and re-assign it.
+****************************************************************************/
+int SetTransfer(DEVICE_EXTENSION *pdx, struct transfer_area_desc __user *pTD)
+{
+ int iReturn;
+ struct transfer_area_desc td;
+
+ if (copy_from_user(&td, pTD, sizeof(td)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: area:%d, size:%08x\n",
+ __func__, td.wAreaNum, td.dwLength);
+ /* The strange cast is done so that we don't get warnings in 32-bit linux about the size of the */
+ /* pointer. The pointer is always passed as a 64-bit object so that we don't have problems using */
+ /* a 32-bit program on a 64-bit system. unsigned long is 64-bits on a 64-bit system. */
+ iReturn =
+ SetArea(pdx, td.wAreaNum,
+ (char __user *)((unsigned long)td.lpvBuff), td.dwLength,
+ false, false);
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/****************************************************************************
+** UnSetTransfer
+** Erases a transfer area record
+****************************************************************************/
+int UnsetTransfer(DEVICE_EXTENSION *pdx, int nArea)
+{
+ int iReturn;
+ mutex_lock(&pdx->io_mutex);
+ iReturn = ClearArea(pdx, nArea);
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/****************************************************************************
+** SetEvent
+** Creates an event that we can test for based on a transfer to/from an area.
+** The area must be setup for a transfer. We attempt to simulate the Windows
+** driver behavior for events (as we don't actually use them), which is to
+** pretend that whatever the user asked for was achieved, so we return 1 if
+** try to create one, and 0 if they ask to remove (assuming all else was OK).
+****************************************************************************/
+int SetEvent(DEVICE_EXTENSION *pdx, struct transfer_event __user *pTE)
+{
+ int iReturn = U14ERR_NOERROR;
+ struct transfer_event te;
+
+ /* get a local copy of the data */
+ if (copy_from_user(&te, pTE, sizeof(te)))
+ return -EFAULT;
+
+ if (te.wAreaNum >= MAX_TRANSAREAS) /* the area must exist */
+ return U14ERR_BADAREA;
+ else {
+ TRANSAREA *pTA = &pdx->rTransDef[te.wAreaNum];
+ mutex_lock(&pdx->io_mutex); /* make sure we have no competitor */
+ spin_lock_irq(&pdx->stagedLock);
+ if (pTA->bUsed) { /* area must be in use */
+ pTA->dwEventSt = te.dwStart; /* set area regions */
+ pTA->dwEventSz = te.dwLength; /* set size (0 cancels it) */
+ pTA->bEventToHost = te.wFlags & 1; /* set the direction */
+ pTA->iWakeUp = 0; /* zero the wake up count */
+ } else
+ iReturn = U14ERR_NOTSET;
+ spin_unlock_irq(&pdx->stagedLock);
+ mutex_unlock(&pdx->io_mutex);
+ }
+ return iReturn ==
+ U14ERR_NOERROR ? (te.iSetEvent ? 1 : U14ERR_NOERROR) : iReturn;
+}
+
+/****************************************************************************
+** WaitEvent
+** Sleep the process with a timeout waiting for an event. Returns the number
+** of times that a block met the event condition since we last cleared it or
+** 0 if timed out, or -ve error (bad area or not set, or signal).
+****************************************************************************/
+int WaitEvent(DEVICE_EXTENSION *pdx, int nArea, int msTimeOut)
+{
+ int iReturn;
+ if ((unsigned)nArea >= MAX_TRANSAREAS)
+ return U14ERR_BADAREA;
+ else {
+ int iWait;
+ TRANSAREA *pTA = &pdx->rTransDef[nArea];
+ msTimeOut = (msTimeOut * HZ + 999) / 1000; /* convert timeout to jiffies */
+
+ /* We cannot wait holding the mutex, but we check the flags while holding */
+ /* it. This may well be pointless as another thread could get in between */
+ /* releasing it and the wait call. However, this would have to clear the */
+ /* iWakeUp flag. However, the !pTA-bUsed may help us in this case. */
+ mutex_lock(&pdx->io_mutex); /* make sure we have no competitor */
+ if (!pTA->bUsed || !pTA->dwEventSz) /* check something to wait for... */
+ return U14ERR_NOTSET; /* ...else we do nothing */
+ mutex_unlock(&pdx->io_mutex);
+
+ if (msTimeOut)
+ iWait =
+ wait_event_interruptible_timeout(pTA->wqEvent,
+ pTA->iWakeUp
+ || !pTA->bUsed,
+ msTimeOut);
+ else
+ iWait =
+ wait_event_interruptible(pTA->wqEvent, pTA->iWakeUp
+ || !pTA->bUsed);
+ if (iWait)
+ iReturn = -ERESTARTSYS; /* oops - we have had a SIGNAL */
+ else
+ iReturn = pTA->iWakeUp; /* else the wakeup count */
+
+ spin_lock_irq(&pdx->stagedLock);
+ pTA->iWakeUp = 0; /* clear the flag */
+ spin_unlock_irq(&pdx->stagedLock);
+ }
+ return iReturn;
+}
+
+/****************************************************************************
+** TestEvent
+** Test the event to see if a WaitEvent would return immediately. Returns the
+** number of times a block completed since the last call, or 0 if none or a
+** negative error.
+****************************************************************************/
+int TestEvent(DEVICE_EXTENSION *pdx, int nArea)
+{
+ int iReturn;
+ if ((unsigned)nArea >= MAX_TRANSAREAS)
+ iReturn = U14ERR_BADAREA;
+ else {
+ TRANSAREA *pTA = &pdx->rTransDef[nArea];
+ mutex_lock(&pdx->io_mutex); /* make sure we have no competitor */
+ spin_lock_irq(&pdx->stagedLock);
+ iReturn = pTA->iWakeUp; /* get wakeup count since last call */
+ pTA->iWakeUp = 0; /* clear the count */
+ spin_unlock_irq(&pdx->stagedLock);
+ mutex_unlock(&pdx->io_mutex);
+ }
+ return iReturn;
+}
+
+/****************************************************************************
+** GetTransferInfo
+** Puts the current state of the 1401 in a TGET_TX_BLOCK.
+*****************************************************************************/
+int GetTransfer(DEVICE_EXTENSION *pdx, TGET_TX_BLOCK __user *pTX)
+{
+ int iReturn = U14ERR_NOERROR;
+ unsigned int dwIdent;
+
+ mutex_lock(&pdx->io_mutex);
+ dwIdent = pdx->StagedId; /* area ident for last xfer */
+ if (dwIdent >= MAX_TRANSAREAS)
+ iReturn = U14ERR_BADAREA;
+ else {
+ /* Return the best information we have - we don't have physical addresses */
+ TGET_TX_BLOCK *tx;
+
+ tx = kzalloc(sizeof(*tx), GFP_KERNEL);
+ if (!tx) {
+ mutex_unlock(&pdx->io_mutex);
+ return -ENOMEM;
+ }
+ tx->size = pdx->rTransDef[dwIdent].dwLength;
+ tx->linear = (long long)((long)pdx->rTransDef[dwIdent].lpvBuff);
+ tx->avail = GET_TX_MAXENTRIES; /* how many blocks we could return */
+ tx->used = 1; /* number we actually return */
+ tx->entries[0].physical =
+ (long long)(tx->linear + pdx->StagedOffset);
+ tx->entries[0].size = tx->size;
+
+ if (copy_to_user(pTX, tx, sizeof(*tx)))
+ iReturn = -EFAULT;
+ kfree(tx);
+ }
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/****************************************************************************
+** KillIO1401
+**
+** Empties the host i/o buffers
+****************************************************************************/
+int KillIO1401(DEVICE_EXTENSION *pdx)
+{
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+ mutex_lock(&pdx->io_mutex);
+ FlushOutBuff(pdx);
+ FlushInBuff(pdx);
+ mutex_unlock(&pdx->io_mutex);
+ return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** BlkTransState
+** Returns a 0 or a 1 for whether DMA is happening. No point holding a mutex
+** for this as it only does one read.
+*****************************************************************************/
+int BlkTransState(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = pdx->dwDMAFlag != MODE_CHAR;
+ dev_dbg(&pdx->interface->dev, "%s: %d\n", __func__, iReturn);
+ return iReturn;
+}
+
+/****************************************************************************
+** StateOf1401
+**
+** Puts the current state of the 1401 in the Irp return buffer.
+*****************************************************************************/
+int StateOf1401(DEVICE_EXTENSION *pdx)
+{
+ int iReturn;
+ mutex_lock(&pdx->io_mutex);
+
+ QuickCheck(pdx, false, false); /* get state up to date, no reset */
+ iReturn = pdx->sCurrentState;
+
+ mutex_unlock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: %d\n", __func__, iReturn);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** StartSelfTest
+**
+** Initiates a self-test cycle. The assumption is that we have no interrupts
+** active, so we should make sure that this is the case.
+*****************************************************************************/
+int StartSelfTest(DEVICE_EXTENSION *pdx)
+{
+ int nGot;
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ ced_draw_down(pdx); /* wait for, then kill outstanding Urbs */
+ FlushInBuff(pdx); /* Clear out input buffer & pipe */
+ FlushOutBuff(pdx); /* Clear output buffer & pipe */
+ /* so things stay tidy */
+ /* ReadWrite_Cancel(pDeviceObject); */
+ pdx->dwDMAFlag = MODE_CHAR; /* Clear DMA mode flags here */
+
+ nGot = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+ DB_SELFTEST, (H_TO_D | VENDOR | DEVREQ),
+ 0, 0, NULL, 0, HZ); /* allow 1 second timeout */
+ pdx->ulSelfTestTime = jiffies + HZ * 30; /* 30 seconds into the future */
+
+ mutex_unlock(&pdx->io_mutex);
+ if (nGot < 0)
+ dev_err(&pdx->interface->dev, "%s: err=%d\n", __func__, nGot);
+ return nGot < 0 ? U14ERR_FAIL : U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** CheckSelfTest
+**
+** Check progress of a self-test cycle
+****************************************************************************/
+int CheckSelfTest(DEVICE_EXTENSION *pdx, TGET_SELFTEST __user *pGST)
+{
+ unsigned int state, error;
+ int iReturn;
+ TGET_SELFTEST gst; /* local work space */
+ memset(&gst, 0, sizeof(gst)); /* clear out the space (sets code 0) */
+
+ mutex_lock(&pdx->io_mutex);
+
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+ iReturn = Get1401State(pdx, &state, &error);
+ if (iReturn == U14ERR_NOERROR) /* Only accept zero if it happens twice */
+ iReturn = Get1401State(pdx, &state, &error);
+
+ if (iReturn != U14ERR_NOERROR) { /* Self-test can cause comms errors */
+ /* so we assume still testing */
+ dev_err(&pdx->interface->dev,
+ "%s: Get1401State=%d, assuming still testing\n",
+ __func__, iReturn);
+ state = 0x80; /* Force still-testing, no error */
+ error = 0;
+ iReturn = U14ERR_NOERROR;
+ }
+
+ if ((state == -1) && (error == -1)) { /* If Get1401State had problems */
+ dev_err(&pdx->interface->dev,
+ "%s: Get1401State failed, assuming still testing\n",
+ __func__);
+ state = 0x80; /* Force still-testing, no error */
+ error = 0;
+ }
+
+ if ((state & 0xFF) == 0x80) { /* If we are still in self-test */
+ if (state & 0x00FF0000) { /* Have we got an error? */
+ gst.code = (state & 0x00FF0000) >> 16; /* read the error code */
+ gst.x = error & 0x0000FFFF; /* Error data X */
+ gst.y = (error & 0xFFFF0000) >> 16; /* and data Y */
+ dev_dbg(&pdx->interface->dev,
+ "Self-test error code %d\n", gst.code);
+ } else { /* No error, check for timeout */
+ unsigned long ulNow = jiffies; /* get current time */
+ if (time_after(ulNow, pdx->ulSelfTestTime)) {
+ gst.code = -2; /* Flag the timeout */
+ dev_dbg(&pdx->interface->dev,
+ "Self-test timed-out\n");
+ } else
+ dev_dbg(&pdx->interface->dev,
+ "Self-test on-going\n");
+ }
+ } else {
+ gst.code = -1; /* Flag the test is done */
+ dev_dbg(&pdx->interface->dev, "Self-test done\n");
+ }
+
+ if (gst.code < 0) { /* If we have a problem or finished */
+ /* If using the 2890 we should reset properly */
+ if ((pdx->nPipes == 4) && (pdx->s1401Type <= TYPEPOWER))
+ Is1401(pdx); /* Get 1401 reset and OK */
+ else
+ QuickCheck(pdx, true, true); /* Otherwise check without reset unless problems */
+ }
+ mutex_unlock(&pdx->io_mutex);
+
+ if (copy_to_user(pGST, &gst, sizeof(gst)))
+ return -EFAULT;
+
+ return iReturn;
+}
+
+/****************************************************************************
+** TypeOf1401
+**
+** Returns code for standard, plus, micro1401, power1401 or none
+****************************************************************************/
+int TypeOf1401(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = TYPEUNKNOWN;
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ switch (pdx->s1401Type) {
+ case TYPE1401:
+ iReturn = U14ERR_STD;
+ break; /* Handle these types directly */
+ case TYPEPLUS:
+ iReturn = U14ERR_PLUS;
+ break;
+ case TYPEU1401:
+ iReturn = U14ERR_U1401;
+ break;
+ default:
+ if ((pdx->s1401Type >= TYPEPOWER) && (pdx->s1401Type <= 25))
+ iReturn = pdx->s1401Type + 4; /* We can calculate types */
+ else /* for up-coming 1401 designs */
+ iReturn = TYPEUNKNOWN; /* Don't know or not there */
+ }
+ dev_dbg(&pdx->interface->dev, "%s %d\n", __func__, iReturn);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** TransferFlags
+**
+** Returns flags on block transfer abilities
+****************************************************************************/
+int TransferFlags(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = U14TF_MULTIA | U14TF_DIAG | /* we always have multiple DMA area */
+ U14TF_NOTIFY | U14TF_CIRCTH; /* diagnostics, notify and circular */
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+ mutex_lock(&pdx->io_mutex);
+ if (pdx->bIsUSB2) /* Set flag for USB2 if appropriate */
+ iReturn |= U14TF_USB2;
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/***************************************************************************
+** DbgCmd1401
+** Issues a debug\diagnostic command to the 1401 along with a 32-bit datum
+** This is a utility command used for dbg operations.
+*/
+static int DbgCmd1401(DEVICE_EXTENSION *pdx, unsigned char cmd,
+ unsigned int data)
+{
+ int iReturn;
+ dev_dbg(&pdx->interface->dev, "%s: entry\n", __func__);
+ iReturn = usb_control_msg(pdx->udev, usb_sndctrlpipe(pdx->udev, 0), cmd,
+ (H_TO_D | VENDOR | DEVREQ),
+ (unsigned short)data,
+ (unsigned short)(data >> 16), NULL, 0, HZ);
+ /* allow 1 second timeout */
+ if (iReturn < 0)
+ dev_err(&pdx->interface->dev, "%s: fail code=%d\n",
+ __func__, iReturn);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgPeek
+**
+** Execute the diagnostic peek operation. Uses address, width and repeats.
+****************************************************************************/
+int DbgPeek(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+ int iReturn;
+ TDBGBLOCK db;
+
+ if (copy_from_user(&db, pDB, sizeof(db)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: @ %08x\n", __func__, db.iAddr);
+
+ iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_PEEK, 0);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgPoke
+**
+** Execute the diagnostic poke operation. Parameters are in the CSBLOCK struct
+** in order address, size, repeats and value to poke.
+****************************************************************************/
+int DbgPoke(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+ int iReturn;
+ TDBGBLOCK db;
+
+ if (copy_from_user(&db, pDB, sizeof(db)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: @ %08x\n", __func__, db.iAddr);
+
+ iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_POKE, db.iData);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgRampData
+**
+** Execute the diagnostic ramp data operation. Parameters are in the CSBLOCK struct
+** in order address, default, enable mask, size and repeats.
+****************************************************************************/
+int DbgRampData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+ int iReturn;
+ TDBGBLOCK db;
+
+ if (copy_from_user(&db, pDB, sizeof(db)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: @ %08x\n", __func__, db.iAddr);
+
+ iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_SETDEF, db.iDefault);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_SETMASK, db.iMask);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_RAMPD, 0);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgRampAddr
+**
+** Execute the diagnostic ramp address operation
+****************************************************************************/
+int DbgRampAddr(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+ int iReturn;
+ TDBGBLOCK db;
+
+ if (copy_from_user(&db, pDB, sizeof(db)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ iReturn = DbgCmd1401(pdx, DB_SETDEF, db.iDefault);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_SETMASK, db.iMask);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+ if (iReturn == U14ERR_NOERROR)
+ iReturn = DbgCmd1401(pdx, DB_RAMPA, 0);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgGetData
+**
+** Retrieve the data resulting from the last debug Peek operation
+****************************************************************************/
+int DbgGetData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+ int iReturn;
+ TDBGBLOCK db;
+ memset(&db, 0, sizeof(db)); /* fill returned block with 0s */
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ /* Read back the last peeked value from the 1401. */
+ iReturn = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+ DB_DATA, (D_TO_H | VENDOR | DEVREQ), 0, 0,
+ &db.iData, sizeof(db.iData), HZ);
+ if (iReturn == sizeof(db.iData)) {
+ if (copy_to_user(pDB, &db, sizeof(db)))
+ iReturn = -EFAULT;
+ else
+ iReturn = U14ERR_NOERROR;
+ } else
+ dev_err(&pdx->interface->dev, "%s: failed, code %d\n",
+ __func__, iReturn);
+
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** DbgStopLoop
+**
+** Stop any never-ending debug loop, we just call Get1401State for USB
+**
+****************************************************************************/
+int DbgStopLoop(DEVICE_EXTENSION *pdx)
+{
+ int iReturn;
+ unsigned int uState, uErr;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+ iReturn = Get1401State(pdx, &uState, &uErr);
+ mutex_unlock(&pdx->io_mutex);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** SetCircular
+**
+** Sets up a transfer area record for circular transfers. If the area is
+** already set, we attempt to unset it. Unsetting will fail if the area is
+** booked and a transfer to that area is in progress. Otherwise, we will
+** release the area and re-assign it.
+****************************************************************************/
+int SetCircular(DEVICE_EXTENSION *pdx, struct transfer_area_desc __user *pTD)
+{
+ int iReturn;
+ bool bToHost;
+ struct transfer_area_desc td;
+
+ if (copy_from_user(&td, pTD, sizeof(td)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: area:%d, size:%08x\n",
+ __func__, td.wAreaNum, td.dwLength);
+ bToHost = td.eSize != 0; /* this is used as the tohost flag */
+
+ /* The strange cast is done so that we don't get warnings in 32-bit linux about the size of the */
+ /* pointer. The pointer is always passed as a 64-bit object so that we don't have problems using */
+ /* a 32-bit program on a 64-bit system. unsigned long is 64-bits on a 64-bit system. */
+ iReturn =
+ SetArea(pdx, td.wAreaNum,
+ (char __user *)((unsigned long)td.lpvBuff), td.dwLength,
+ true, bToHost);
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/****************************************************************************
+** GetCircBlock
+**
+** Return the next available block of circularly-transferred data.
+****************************************************************************/
+int GetCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user *pCB)
+{
+ int iReturn = U14ERR_NOERROR;
+ unsigned int nArea;
+ TCIRCBLOCK cb;
+
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ if (copy_from_user(&cb, pCB, sizeof(cb)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+
+ nArea = cb.nArea; /* Retrieve parameters first */
+ cb.dwOffset = 0; /* set default result (nothing) */
+ cb.dwSize = 0;
+
+ if (nArea < MAX_TRANSAREAS) { /* The area number must be OK */
+ TRANSAREA *pArea = &pdx->rTransDef[nArea]; /* Pointer to relevant info */
+ spin_lock_irq(&pdx->stagedLock); /* Lock others out */
+
+ if ((pArea->bUsed) && (pArea->bCircular) && /* Must be circular area */
+ (pArea->bCircToHost)) { /* For now at least must be to host */
+ if (pArea->aBlocks[0].dwSize > 0) { /* Got anything? */
+ cb.dwOffset = pArea->aBlocks[0].dwOffset;
+ cb.dwSize = pArea->aBlocks[0].dwSize;
+ dev_dbg(&pdx->interface->dev,
+ "%s: return block 0: %d bytes at %d\n",
+ __func__, cb.dwSize, cb.dwOffset);
+ }
+ } else
+ iReturn = U14ERR_NOTSET;
+
+ spin_unlock_irq(&pdx->stagedLock);
+ } else
+ iReturn = U14ERR_BADAREA;
+
+ if (copy_to_user(pCB, &cb, sizeof(cb)))
+ iReturn = -EFAULT;
+
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
+
+/****************************************************************************
+** FreeCircBlock
+**
+** Frees a block of circularly-transferred data and returns the next one.
+****************************************************************************/
+int FreeCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user *pCB)
+{
+ int iReturn = U14ERR_NOERROR;
+ unsigned int nArea, uStart, uSize;
+ TCIRCBLOCK cb;
+
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ if (copy_from_user(&cb, pCB, sizeof(cb)))
+ return -EFAULT;
+
+ mutex_lock(&pdx->io_mutex);
+
+ nArea = cb.nArea; /* Retrieve parameters first */
+ uStart = cb.dwOffset;
+ uSize = cb.dwSize;
+ cb.dwOffset = 0; /* then set default result (nothing) */
+ cb.dwSize = 0;
+
+ if (nArea < MAX_TRANSAREAS) { /* The area number must be OK */
+ TRANSAREA *pArea = &pdx->rTransDef[nArea]; /* Pointer to relevant info */
+ spin_lock_irq(&pdx->stagedLock); /* Lock others out */
+
+ if ((pArea->bUsed) && (pArea->bCircular) && /* Must be circular area */
+ (pArea->bCircToHost)) { /* For now at least must be to host */
+ bool bWaiting = false;
+
+ if ((pArea->aBlocks[0].dwSize >= uSize) && /* Got anything? */
+ (pArea->aBlocks[0].dwOffset == uStart)) { /* Must be legal data */
+ pArea->aBlocks[0].dwSize -= uSize;
+ pArea->aBlocks[0].dwOffset += uSize;
+ if (pArea->aBlocks[0].dwSize == 0) { /* Have we emptied this block? */
+ if (pArea->aBlocks[1].dwSize) { /* Is there a second block? */
+ pArea->aBlocks[0] = pArea->aBlocks[1]; /* Copy down block 2 data */
+ pArea->aBlocks[1].dwSize = 0; /* and mark the second block as unused */
+ pArea->aBlocks[1].dwOffset = 0;
+ } else
+ pArea->aBlocks[0].dwOffset = 0;
+ }
+
+ dev_dbg(&pdx->interface->dev,
+ "%s: free %d bytes at %d, return %d bytes at %d, wait=%d\n",
+ __func__, uSize, uStart,
+ pArea->aBlocks[0].dwSize,
+ pArea->aBlocks[0].dwOffset,
+ pdx->bXFerWaiting);
+
+ /* Return the next available block of memory as well */
+ if (pArea->aBlocks[0].dwSize > 0) { /* Got anything? */
+ cb.dwOffset =
+ pArea->aBlocks[0].dwOffset;
+ cb.dwSize = pArea->aBlocks[0].dwSize;
+ }
+
+ bWaiting = pdx->bXFerWaiting;
+ if (bWaiting && pdx->bStagedUrbPending) {
+ dev_err(&pdx->interface->dev,
+ "%s: ERROR: waiting xfer and staged Urb pending!\n",
+ __func__);
+ bWaiting = false;
+ }
+ } else {
+ dev_err(&pdx->interface->dev,
+ "%s: ERROR: freeing %d bytes at %d, block 0 is %d bytes at %d\n",
+ __func__, uSize, uStart,
+ pArea->aBlocks[0].dwSize,
+ pArea->aBlocks[0].dwOffset);
+ iReturn = U14ERR_NOMEMORY;
+ }
+
+ /* If we have one, kick off pending transfer */
+ if (bWaiting) { /* Got a block xfer waiting? */
+ int RWMStat =
+ ReadWriteMem(pdx, !pdx->rDMAInfo.bOutWard,
+ pdx->rDMAInfo.wIdent,
+ pdx->rDMAInfo.dwOffset,
+ pdx->rDMAInfo.dwSize);
+ if (RWMStat != U14ERR_NOERROR)
+ dev_err(&pdx->interface->dev,
+ "%s: rw setup failed %d\n",
+ __func__, RWMStat);
+ }
+ } else
+ iReturn = U14ERR_NOTSET;
+
+ spin_unlock_irq(&pdx->stagedLock);
+ } else
+ iReturn = U14ERR_BADAREA;
+
+ if (copy_to_user(pCB, &cb, sizeof(cb)))
+ iReturn = -EFAULT;
+
+ mutex_unlock(&pdx->io_mutex);
+ return iReturn;
+}
diff --git a/drivers/staging/ced1401/ced_ioctl.h b/drivers/staging/ced1401/ced_ioctl.h
new file mode 100644
index 00000000000..4b6c9dedb21
--- /dev/null
+++ b/drivers/staging/ced1401/ced_ioctl.h
@@ -0,0 +1,336 @@
+/*
+ * IOCTL calls for the CED1401 driver
+ * Copyright (C) 2010 Cambridge Electronic Design Ltd
+ * Author Greg P Smith (greg@ced.co.uk)
+ *
+ * This program is free software; you can redistribute 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.
+ */
+#ifndef __CED_IOCTL_H__
+#define __CED_IOCTL_H__
+
+#include <linux/ioctl.h>
+
+/* dma modes, only MODE_CHAR and MODE_LINEAR are used in this driver */
+#define MODE_CHAR 0
+#define MODE_LINEAR 1
+
+/****************************************************************************
+** TypeDefs
+*****************************************************************************/
+
+struct transfer_area_desc {
+ long long lpvBuff; /* address of transfer area (for 64 or 32 bit) */
+ unsigned int dwLength; /* length of the area */
+ unsigned short wAreaNum; /* number of transfer area to set up */
+ short eSize; /* element size - is tohost flag for circular */
+};
+
+
+struct transfer_event {
+ unsigned int dwStart; /* offset into the area */
+ unsigned int dwLength; /* length of the region */
+ unsigned short wAreaNum; /* the area number */
+ unsigned short wFlags; /* bit 0 set for toHost */
+ int iSetEvent; /* could be dummy in LINUX */
+};
+
+#define MAX_TRANSFER_SIZE 0x4000 /* Maximum data bytes per IRP */
+#define MAX_AREA_LENGTH 0x100000 /* Maximum size of transfer area */
+#define MAX_TRANSAREAS 8 /* definitions for dma set up */
+
+typedef struct TGetSelfTest {
+ int code; /* self-test error code */
+ int x, y; /* additional information */
+} TGET_SELFTEST;
+
+/* Debug block used for several commands. Not all fields are used for all commands. */
+typedef struct TDbgBlock {
+ int iAddr; /* the address in the 1401 */
+ int iRepeats; /* number of repeats */
+ int iWidth; /* width in bytes 1, 2, 4 */
+ int iDefault; /* default value */
+ int iMask; /* mask to apply */
+ int iData; /* data for poke, result for peek */
+} TDBGBLOCK;
+
+/* Used to collect information about a circular block from the device driver */
+typedef struct TCircBlock {
+ unsigned int nArea; /* the area to collect information from */
+ unsigned int dwOffset; /* offset into the area to the available block */
+ unsigned int dwSize; /* size of the area */
+} TCIRCBLOCK;
+
+/* Used to clollect the 1401 status */
+typedef struct TCSBlock {
+ unsigned int uiState;
+ unsigned int uiError;
+} TCSBLOCK;
+
+/*
+ * As seen by the user, an ioctl call looks like: int ioctl(int fd, unsigned
+ * long cmd, char* argp); We will then have all sorts of variants on this that
+ * can be used to pass stuff to our driver. We will generate macros for each
+ * type of call so as to provide some sort of type safety in the calling:
+ */
+#define CED_MAGIC_IOC 0xce
+
+#define IOCTL_CED_SENDSTRING(n) _IOC(_IOC_WRITE, CED_MAGIC_IOC, 2, n)
+
+#define IOCTL_CED_RESET1401 _IO(CED_MAGIC_IOC, 3)
+#define IOCTL_CED_GETCHAR _IO(CED_MAGIC_IOC, 4)
+#define IOCTL_CED_SENDCHAR _IO(CED_MAGIC_IOC, 5)
+#define IOCTL_CED_STAT1401 _IO(CED_MAGIC_IOC, 6)
+#define IOCTL_CED_LINECOUNT _IO(CED_MAGIC_IOC, 7)
+#define IOCTL_CED_GETSTRING(nMax) _IOC(_IOC_READ, CED_MAGIC_IOC, 8, nMax)
+
+#define IOCTL_CED_SETTRANSFER _IOW(CED_MAGIC_IOC, 11, struct transfer_area_desc)
+#define IOCTL_CED_UNSETTRANSFER _IO(CED_MAGIC_IOC, 12)
+#define IOCTL_CED_SETEVENT _IOW(CED_MAGIC_IOC, 13, struct transfer_event)
+#define IOCTL_CED_GETOUTBUFSPACE _IO(CED_MAGIC_IOC, 14)
+#define IOCTL_CED_GETBASEADDRESS _IO(CED_MAGIC_IOC, 15)
+#define IOCTL_CED_GETDRIVERREVISION _IO(CED_MAGIC_IOC, 16)
+
+#define IOCTL_CED_GETTRANSFER _IOR(CED_MAGIC_IOC, 17, TGET_TX_BLOCK)
+#define IOCTL_CED_KILLIO1401 _IO(CED_MAGIC_IOC, 18)
+#define IOCTL_CED_BLKTRANSSTATE _IO(CED_MAGIC_IOC, 19)
+
+#define IOCTL_CED_STATEOF1401 _IO(CED_MAGIC_IOC, 23)
+#define IOCTL_CED_GRAB1401 _IO(CED_MAGIC_IOC, 25)
+#define IOCTL_CED_FREE1401 _IO(CED_MAGIC_IOC, 26)
+#define IOCTL_CED_STARTSELFTEST _IO(CED_MAGIC_IOC, 31)
+#define IOCTL_CED_CHECKSELFTEST _IOR(CED_MAGIC_IOC, 32, TGET_SELFTEST)
+#define IOCTL_CED_TYPEOF1401 _IO(CED_MAGIC_IOC, 33)
+#define IOCTL_CED_TRANSFERFLAGS _IO(CED_MAGIC_IOC, 34)
+
+#define IOCTL_CED_DBGPEEK _IOW(CED_MAGIC_IOC, 35, TDBGBLOCK)
+#define IOCTL_CED_DBGPOKE _IOW(CED_MAGIC_IOC, 36, TDBGBLOCK)
+#define IOCTL_CED_DBGRAMPDATA _IOW(CED_MAGIC_IOC, 37, TDBGBLOCK)
+#define IOCTL_CED_DBGRAMPADDR _IOW(CED_MAGIC_IOC, 38, TDBGBLOCK)
+#define IOCTL_CED_DBGGETDATA _IOR(CED_MAGIC_IOC, 39, TDBGBLOCK)
+#define IOCTL_CED_DBGSTOPLOOP _IO(CED_MAGIC_IOC, 40)
+#define IOCTL_CED_FULLRESET _IO(CED_MAGIC_IOC, 41)
+#define IOCTL_CED_SETCIRCULAR _IOW(CED_MAGIC_IOC, 42, struct transfer_area_desc)
+#define IOCTL_CED_GETCIRCBLOCK _IOWR(CED_MAGIC_IOC, 43, TCIRCBLOCK)
+#define IOCTL_CED_FREECIRCBLOCK _IOWR(CED_MAGIC_IOC, 44, TCIRCBLOCK)
+#define IOCTL_CED_WAITEVENT _IO(CED_MAGIC_IOC, 45)
+#define IOCTL_CED_TESTEVENT _IO(CED_MAGIC_IOC, 46)
+
+#ifndef __KERNEL__
+/*
+ * If nothing said about return value, it is a U14ERR_... error code
+ * (U14ERR_NOERROR for none)
+ */
+inline int CED_SendString(int fh, const char *szText, int n)
+{
+ return ioctl(fh, IOCTL_CED_SENDSTRING(n), szText);
+}
+
+inline int CED_Reset1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_RESET1401);
+}
+
+/* Return the singe character or a -ve error code. */
+inline int CED_GetChar(int fh)
+{
+ return ioctl(fh, IOCTL_CED_GETCHAR);
+}
+
+/* Return character count in input buffer */
+inline int CED_Stat1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_STAT1401);
+}
+
+inline int CED_SendChar(int fh, char c)
+{
+ return ioctl(fh, IOCTL_CED_SENDCHAR, c);
+}
+
+inline int CED_LineCount(int fh)
+{
+ return ioctl(fh, IOCTL_CED_LINECOUNT);
+}
+
+/*
+ * return the count of characters returned. If the string was terminated by CR
+ * or 0, then the 0 is part of the count. Otherwise, we will add a zero if
+ * there is room, but it is not included in the count. The return value is 0
+ * if there was nothing to read.
+ */
+inline int CED_GetString(int fh, char *szText, int nMax)
+{
+ return ioctl(fh, IOCTL_CED_GETSTRING(nMax), szText);
+}
+
+/* returns space in the output buffer. */
+inline int CED_GetOutBufSpace(int fh)
+{
+ return ioctl(fh, IOCTL_CED_GETOUTBUFSPACE);
+}
+
+/* This always returns -1 as not implemented. */
+inline int CED_GetBaseAddress(int fh)
+{
+ return ioctl(fh, IOCTL_CED_GETBASEADDRESS);
+}
+
+/* returns the major revision <<16 | minor revision. */
+inline int CED_GetDriverRevision(int fh)
+{
+ return ioctl(fh, IOCTL_CED_GETDRIVERREVISION);
+}
+
+inline int CED_SetTransfer(int fh, struct transfer_area_desc *pTD)
+{
+ return ioctl(fh, IOCTL_CED_SETTRANSFER, pTD);
+}
+
+inline int CED_UnsetTransfer(int fh, int nArea)
+{
+ return ioctl(fh, IOCTL_CED_UNSETTRANSFER, nArea);
+}
+
+inline int CED_SetEvent(int fh, struct transfer_event *pTE)
+{
+ return ioctl(fh, IOCTL_CED_SETEVENT, pTE);
+}
+
+inline int CED_GetTransfer(int fh, TGET_TX_BLOCK *pTX)
+{
+ return ioctl(fh, IOCTL_CED_GETTRANSFER, pTX);
+}
+
+inline int CED_KillIO1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_KILLIO1401);
+}
+
+/* returns 0 if no active DMA, 1 if active */
+inline int CED_BlkTransState(int fh)
+{
+ return ioctl(fh, IOCTL_CED_BLKTRANSSTATE);
+}
+
+inline int CED_StateOf1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_STATEOF1401);
+}
+
+inline int CED_Grab1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_GRAB1401);
+}
+
+inline int CED_Free1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_FREE1401);
+}
+
+inline int CED_StartSelfTest(int fh)
+{
+ return ioctl(fh, IOCTL_CED_STARTSELFTEST);
+}
+
+inline int CED_CheckSelfTest(int fh, TGET_SELFTEST *pGST)
+{
+ return ioctl(fh, IOCTL_CED_CHECKSELFTEST, pGST);
+}
+
+inline int CED_TypeOf1401(int fh)
+{
+ return ioctl(fh, IOCTL_CED_TYPEOF1401);
+}
+
+inline int CED_TransferFlags(int fh)
+{
+ return ioctl(fh, IOCTL_CED_TRANSFERFLAGS);
+}
+
+inline int CED_DbgPeek(int fh, TDBGBLOCK *pDB)
+{
+ return ioctl(fh, IOCTL_CED_DBGPEEK, pDB);
+}
+
+inline int CED_DbgPoke(int fh, TDBGBLOCK *pDB)
+{
+ return ioctl(fh, IOCTL_CED_DBGPOKE, pDB);
+}
+
+inline int CED_DbgRampData(int fh, TDBGBLOCK *pDB)
+{
+ return ioctl(fh, IOCTL_CED_DBGRAMPDATA, pDB);
+}
+
+inline int CED_DbgRampAddr(int fh, TDBGBLOCK *pDB)
+{
+ return ioctl(fh, IOCTL_CED_DBGRAMPADDR, pDB);
+}
+
+inline int CED_DbgGetData(int fh, TDBGBLOCK *pDB)
+{
+ return ioctl(fh, IOCTL_CED_DBGGETDATA, pDB);
+}
+
+inline int CED_DbgStopLoop(int fh)
+{
+ return ioctl(fh, IOCTL_CED_DBGSTOPLOOP);
+}
+
+inline int CED_FullReset(int fh)
+{
+ return ioctl(fh, IOCTL_CED_FULLRESET);
+}
+
+inline int CED_SetCircular(int fh, struct transfer_area_desc *pTD)
+{
+ return ioctl(fh, IOCTL_CED_SETCIRCULAR, pTD);
+}
+
+inline int CED_GetCircBlock(int fh, TCIRCBLOCK *pCB)
+{
+ return ioctl(fh, IOCTL_CED_GETCIRCBLOCK, pCB);
+}
+
+inline int CED_FreeCircBlock(int fh, TCIRCBLOCK *pCB)
+{
+ return ioctl(fh, IOCTL_CED_FREECIRCBLOCK, pCB);
+}
+
+inline int CED_WaitEvent(int fh, int nArea, int msTimeOut)
+{
+ return ioctl(fh, IOCTL_CED_WAITEVENT, (nArea & 0xff)|(msTimeOut << 8));
+}
+
+inline int CED_TestEvent(int fh, int nArea)
+{
+ return ioctl(fh, IOCTL_CED_TESTEVENT, nArea);
+}
+#endif
+
+#ifdef NOTWANTEDYET
+#define IOCTL_CED_REGCALLBACK _IO(CED_MAGIC_IOC, 9) /* Not used */
+#define IOCTL_CED_GETMONITORBUF _IO(CED_MAGIC_IOC, 10) /* Not used */
+
+#define IOCTL_CED_BYTECOUNT _IO(CED_MAGIC_IOC, 20) /* Not used */
+#define IOCTL_CED_ZEROBLOCKCOUNT _IO(CED_MAGIC_IOC, 21) /* Not used */
+#define IOCTL_CED_STOPCIRCULAR _IO(CED_MAGIC_IOC, 22) /* Not used */
+
+#define IOCTL_CED_REGISTERS1401 _IO(CED_MAGIC_IOC, 24) /* Not used */
+#define IOCTL_CED_STEP1401 _IO(CED_MAGIC_IOC, 27) /* Not used */
+#define IOCTL_CED_SET1401REGISTERS _IO(CED_MAGIC_IOC, 28) /* Not used */
+#define IOCTL_CED_STEPTILL1401 _IO(CED_MAGIC_IOC, 29) /* Not used */
+#define IOCTL_CED_SETORIN _IO(CED_MAGIC_IOC, 30) /* Not used */
+
+#endif
+
+/* __CED_IOCTL_H__ */
+#endif
diff --git a/drivers/staging/ced1401/machine.h b/drivers/staging/ced1401/machine.h
new file mode 100644
index 00000000000..dbd4036d9bd
--- /dev/null
+++ b/drivers/staging/ced1401/machine.h
@@ -0,0 +1,119 @@
+/*****************************************************************************
+**
+** machine.h
+**
+** Copyright (c) Cambridge Electronic Design Limited 1991,1992,2010
+**
+** This program is free software; you can redistribute 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.
+**
+** Contact CED: Cambridge Electronic Design Limited, Science Park, Milton Road
+** Cambridge, CB6 0FE.
+** www.ced.co.uk
+** greg@ced.co.uk
+**
+** This file is included at the start of 'C' or 'C++' source file to define
+** things for cross-platform/compiler interoperability. This used to deal with
+** MSDOS/16-bit stuff, but this was all removed in Decemeber 2010. There are
+** three things to consider: Windows, LINUX, mac OSX (BSD Unix) and 32 vs 64
+** bit. At the time of writing (DEC 2010) there is a consensus on the following
+** and their unsigned equivalents:
+**
+** type bits
+** char 8
+** short 16
+** int 32
+** long long 64
+**
+** long is a problem as it is always 64 bits on linux/unix and is always 32 bits
+** on windows.
+** On windows, we define _IS_WINDOWS_ and one of WIN32 or WIN64.
+** On linux we define LINUX
+** On Max OSX we define MACOSX
+**
+*/
+
+#ifndef __MACHINE_H__
+#define __MACHINE_H__
+#ifndef __KERNEL__
+#include <float.h>
+#include <limits.h>
+#endif
+
+/*
+** The initial section is to identify the operating system
+*/
+#if (defined(__linux__) || defined(_linux) || defined(__linux)) && !defined(LINUX)
+#define LINUX 1
+#endif
+
+#if (defined(__WIN32__) || defined(_WIN32)) && !defined(WIN32)
+#define WIN32 1
+#endif
+
+#if defined(__APPLE__)
+#define MACOSX
+#endif
+
+#if defined(_WIN64)
+#undef WIN32
+#undef WIN64
+#define WIN64 1
+#endif
+
+#if defined(WIN32) || defined(WIN64)
+#define _IS_WINDOWS_ 1
+#endif
+
+#if defined(LINUX) || defined(MAXOSX)
+ #define FAR
+
+ typedef int BOOL; /* To match Windows */
+ typedef unsigned char BYTE;
+ #define __packed __attribute__((packed))
+ #define HIWORD(x) (unsigned short)(((x)>>16) & 0xffff)
+ #define LOWORD(x) (unsigned short)((x) & 0xffff)
+#endif
+
+#ifdef _IS_WINDOWS_
+#include <windows.h>
+#define __packed
+#endif
+
+/*
+** Sort out the DllExport and DllImport macros. The GCC compiler has its own
+** syntax for this, though it also supports the MS specific __declspec() as
+** a synonym.
+*/
+#ifdef GNUC
+ #define DllExport __attribute__((dllexport))
+ #define DllImport __attribute__((dllimport))
+#endif
+
+#ifndef DllExport
+#ifdef _IS_WINDOWS_
+ #define DllExport __declspec(dllexport)
+ #define DllImport __declspec(dllimport)
+#else
+ #define DllExport
+ #define DllImport
+#endif
+#endif /* _IS_WINDOWS_ */
+
+#ifndef TRUE
+ #define TRUE 1
+ #define FALSE 0
+#endif
+
+#endif
diff --git a/drivers/staging/ced1401/usb1401.c b/drivers/staging/ced1401/usb1401.c
new file mode 100644
index 00000000000..284abc08922
--- /dev/null
+++ b/drivers/staging/ced1401/usb1401.c
@@ -0,0 +1,1582 @@
+/***********************************************************************************
+ CED1401 usb driver. This basic loading is based on the usb-skeleton.c code that is:
+ Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ Copyright (C) 2012 Alois Schloegl <alois.schloegl@ist.ac.at>
+ There is not a great deal of the skeleton left.
+
+ All the remainder dealing specifically with the CED1401 is based on drivers written
+ by CED for other systems (mainly Windows) and is:
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute 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.
+
+Endpoints
+*********
+There are 4 endpoints plus the control endpoint in the standard interface
+provided by most 1401s. The control endpoint is used for standard USB requests,
+plus various CED-specific transactions such as start self test, debug and get
+the 1401 status. The other endpoints are:
+
+ 1 Characters to the 1401
+ 2 Characters from the 1401
+ 3 Block data to the 1401
+ 4 Block data to the host.
+
+inside the driver these are indexed as an array from 0 to 3, transactions
+over the control endpoint are carried out using a separate mechanism. The
+use of the endpoints is mostly straightforward, with the driver issuing
+IO request packets (IRPs) as required to transfer data to and from the 1401.
+The handling of endpoint 2 is different because it is used for characters
+from the 1401, which can appear spontaneously and without any other driver
+activity - for example to repeatedly request DMA transfers in Spike2. The
+desired effect is achieved by using an interrupt endpoint which can be
+polled to see if it has data available, and writing the driver so that it
+always maintains a pending read IRP from that endpoint which will read the
+character data and terminate as soon as the 1401 makes data available. This
+works very well, some care is taken with when you kick off this character
+read IRP to avoid it being active when it is not wanted but generally it
+is running all the time.
+
+In the 2270, there are only three endpoints plus the control endpoint. In
+addition to the transactions mentioned above, the control endpoint is used
+to transfer character data to the 1401. The other endpoints are used as:
+
+ 1 Characters from the 1401
+ 2 Block data to the 1401
+ 3 Block data to the host.
+
+The type of interface available is specified by the interface subclass field
+in the interface descriptor provided by the 1401. See the USB_INT_ constants
+for the values that this field can hold.
+
+****************************************************************************
+Linux implementation
+
+Although Linux Device Drivers (3rd Edition) was a major source of information,
+it is very out of date. A lot of information was gleaned from the latest
+usb_skeleton.c code (you need to download the kernel sources to get this).
+
+To match the Windows version, everything is done using ioctl calls. All the
+device state is held in the DEVICE_EXTENSION (named to match Windows use).
+Block transfers are done by using get_user_pages() to pin down a list of
+pages that we hold a pointer to in the device driver. We also allocate a
+coherent transfer buffer of size STAGED_SZ (this must be a multiple of the
+bulk endpoint size so that the 1401 does not realise that we break large
+transfers down into smaller pieces). We use kmap_atomic() to get a kernel
+va for each page, as it is required, for copying; see CopyUserSpace().
+
+All character and data transfers are done using asynchronous IO. All Urbs are
+tracked by anchoring them. Status and debug ioctls are implemented with the
+synchronous non-Urb based transfers.
+*/
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+
+#include "usb1401.h"
+
+/* Define these values to match your devices */
+#define USB_CED_VENDOR_ID 0x0525
+#define USB_CED_PRODUCT_ID 0xa0f0
+
+/* table of devices that work with this driver */
+static const struct usb_device_id ced_table[] = {
+ {USB_DEVICE(USB_CED_VENDOR_ID, USB_CED_PRODUCT_ID)},
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, ced_table);
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_CED_MINOR_BASE 192
+
+/* our private defines. if this grows any larger, use your own .h file */
+#define MAX_TRANSFER (PAGE_SIZE - 512)
+/* MAX_TRANSFER is chosen so that the VM is not stressed by
+ allocations > PAGE_SIZE and the number of packets in a page
+ is an integer 512 is the largest possible packet on EHCI */
+#define WRITES_IN_FLIGHT 8
+/* arbitrarily chosen */
+
+static struct usb_driver ced_driver;
+
+static void ced_delete(struct kref *kref)
+{
+ DEVICE_EXTENSION *pdx = to_DEVICE_EXTENSION(kref);
+
+ /* Free up the output buffer, then free the output urb. Note that the interface member */
+ /* of pdx will probably be NULL, so cannot be used to get to dev. */
+ usb_free_coherent(pdx->udev, OUTBUF_SZ, pdx->pCoherCharOut,
+ pdx->pUrbCharOut->transfer_dma);
+ usb_free_urb(pdx->pUrbCharOut);
+
+ /* Do the same for chan input */
+ usb_free_coherent(pdx->udev, INBUF_SZ, pdx->pCoherCharIn,
+ pdx->pUrbCharIn->transfer_dma);
+ usb_free_urb(pdx->pUrbCharIn);
+
+ /* Do the same for the block transfers */
+ usb_free_coherent(pdx->udev, STAGED_SZ, pdx->pCoherStagedIO,
+ pdx->pStagedUrb->transfer_dma);
+ usb_free_urb(pdx->pStagedUrb);
+
+ usb_put_dev(pdx->udev);
+ kfree(pdx);
+}
+
+/* This is the driver end of the open() call from user space. */
+static int ced_open(struct inode *inode, struct file *file)
+{
+ DEVICE_EXTENSION *pdx;
+ int retval = 0;
+ int subminor = iminor(inode);
+ struct usb_interface *interface =
+ usb_find_interface(&ced_driver, subminor);
+ if (!interface) {
+ pr_err("%s - error, can't find device for minor %d", __func__,
+ subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ pdx = usb_get_intfdata(interface);
+ if (!pdx) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev_dbg(&interface->dev, "%s: got pdx\n", __func__);
+
+ /* increment our usage count for the device */
+ kref_get(&pdx->kref);
+
+ /* lock the device to allow correctly handling errors
+ * in resumption */
+ mutex_lock(&pdx->io_mutex);
+
+ if (!pdx->open_count++) {
+ retval = usb_autopm_get_interface(interface);
+ if (retval) {
+ pdx->open_count--;
+ mutex_unlock(&pdx->io_mutex);
+ kref_put(&pdx->kref, ced_delete);
+ goto exit;
+ }
+ } else { /* uncomment this block if you want exclusive open */
+ dev_err(&interface->dev, "%s: fail: already open\n", __func__);
+ retval = -EBUSY;
+ pdx->open_count--;
+ mutex_unlock(&pdx->io_mutex);
+ kref_put(&pdx->kref, ced_delete);
+ goto exit;
+ }
+ /* prevent the device from being autosuspended */
+
+ /* save our object in the file's private structure */
+ file->private_data = pdx;
+ mutex_unlock(&pdx->io_mutex);
+
+exit:
+ return retval;
+}
+
+static int ced_release(struct inode *inode, struct file *file)
+{
+ DEVICE_EXTENSION *pdx = file->private_data;
+ if (pdx == NULL)
+ return -ENODEV;
+
+ dev_dbg(&pdx->interface->dev, "%s: called\n", __func__);
+ mutex_lock(&pdx->io_mutex);
+ if (!--pdx->open_count && pdx->interface) /* Allow autosuspend */
+ usb_autopm_put_interface(pdx->interface);
+ mutex_unlock(&pdx->io_mutex);
+
+ kref_put(&pdx->kref, ced_delete); /* decrement the count on our device */
+ return 0;
+}
+
+static int ced_flush(struct file *file, fl_owner_t id)
+{
+ int res;
+ DEVICE_EXTENSION *pdx = file->private_data;
+ if (pdx == NULL)
+ return -ENODEV;
+
+ dev_dbg(&pdx->interface->dev, "%s: char in pend=%d\n",
+ __func__, pdx->bReadCharsPending);
+
+ /* wait for io to stop */
+ mutex_lock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: got io_mutex\n", __func__);
+ ced_draw_down(pdx);
+
+ /* read out errors, leave subsequent opens a clean slate */
+ spin_lock_irq(&pdx->err_lock);
+ res = pdx->errors ? (pdx->errors == -EPIPE ? -EPIPE : -EIO) : 0;
+ pdx->errors = 0;
+ spin_unlock_irq(&pdx->err_lock);
+
+ mutex_unlock(&pdx->io_mutex);
+ dev_dbg(&pdx->interface->dev, "%s: exit reached\n", __func__);
+
+ return res;
+}
+
+/***************************************************************************
+** CanAcceptIoRequests
+** If the device is removed, interface is set NULL. We also clear our pointer
+** from the interface, so we should make sure that pdx is not NULL. This will
+** not help with a device extension held by a file.
+** return true if can accept new io requests, else false
+*/
+static bool CanAcceptIoRequests(DEVICE_EXTENSION *pdx)
+{
+ return pdx && pdx->interface; /* Can we accept IO requests */
+}
+
+/****************************************************************************
+** Callback routine to complete writes. This may need to fire off another
+** urb to complete the transfer.
+****************************************************************************/
+static void ced_writechar_callback(struct urb *pUrb)
+{
+ DEVICE_EXTENSION *pdx = pUrb->context;
+ int nGot = pUrb->actual_length; /* what we transferred */
+
+ if (pUrb->status) { /* sync/async unlink faults aren't errors */
+ if (!
+ (pUrb->status == -ENOENT || pUrb->status == -ECONNRESET
+ || pUrb->status == -ESHUTDOWN)) {
+ dev_err(&pdx->interface->dev,
+ "%s: nonzero write bulk status received: %d\n",
+ __func__, pUrb->status);
+ }
+
+ spin_lock(&pdx->err_lock);
+ pdx->errors = pUrb->status;
+ spin_unlock(&pdx->err_lock);
+ nGot = 0; /* and tidy up again if so */
+
+ spin_lock(&pdx->charOutLock); /* already at irq level */
+ pdx->dwOutBuffGet = 0; /* Reset the output buffer */
+ pdx->dwOutBuffPut = 0;
+ pdx->dwNumOutput = 0; /* Clear the char count */
+ pdx->bPipeError[0] = 1; /* Flag an error for later */
+ pdx->bSendCharsPending = false; /* Allow other threads again */
+ spin_unlock(&pdx->charOutLock); /* already at irq level */
+ dev_dbg(&pdx->interface->dev,
+ "%s: char out done, 0 chars sent\n", __func__);
+ } else {
+ dev_dbg(&pdx->interface->dev,
+ "%s: char out done, %d chars sent\n", __func__, nGot);
+ spin_lock(&pdx->charOutLock); /* already at irq level */
+ pdx->dwNumOutput -= nGot; /* Now adjust the char send buffer */
+ pdx->dwOutBuffGet += nGot; /* to match what we did */
+ if (pdx->dwOutBuffGet >= OUTBUF_SZ) /* Can't do this any earlier as data could be overwritten */
+ pdx->dwOutBuffGet = 0;
+
+ if (pdx->dwNumOutput > 0) { /* if more to be done... */
+ int nPipe = 0; /* The pipe number to use */
+ int iReturn;
+ char *pDat = &pdx->outputBuffer[pdx->dwOutBuffGet];
+ unsigned int dwCount = pdx->dwNumOutput; /* maximum to send */
+ if ((pdx->dwOutBuffGet + dwCount) > OUTBUF_SZ) /* does it cross buffer end? */
+ dwCount = OUTBUF_SZ - pdx->dwOutBuffGet;
+ spin_unlock(&pdx->charOutLock); /* we are done with stuff that changes */
+ memcpy(pdx->pCoherCharOut, pDat, dwCount); /* copy output data to the buffer */
+ usb_fill_bulk_urb(pdx->pUrbCharOut, pdx->udev,
+ usb_sndbulkpipe(pdx->udev,
+ pdx->epAddr[0]),
+ pdx->pCoherCharOut, dwCount,
+ ced_writechar_callback, pdx);
+ pdx->pUrbCharOut->transfer_flags |=
+ URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(pdx->pUrbCharOut, &pdx->submitted); /* in case we need to kill it */
+ iReturn = usb_submit_urb(pdx->pUrbCharOut, GFP_ATOMIC);
+ dev_dbg(&pdx->interface->dev, "%s: n=%d>%s<\n",
+ __func__, dwCount, pDat);
+ spin_lock(&pdx->charOutLock); /* grab lock for errors */
+ if (iReturn) {
+ pdx->bPipeError[nPipe] = 1; /* Flag an error to be handled later */
+ pdx->bSendCharsPending = false; /* Allow other threads again */
+ usb_unanchor_urb(pdx->pUrbCharOut);
+ dev_err(&pdx->interface->dev,
+ "%s: usb_submit_urb() returned %d\n",
+ __func__, iReturn);
+ }
+ } else
+ pdx->bSendCharsPending = false; /* Allow other threads again */
+ spin_unlock(&pdx->charOutLock); /* already at irq level */
+ }
+}
+
+/****************************************************************************
+** SendChars
+** Transmit the characters in the output buffer to the 1401. This may need
+** breaking down into multiple transfers.
+****************************************************************************/
+int SendChars(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = U14ERR_NOERROR;
+
+ spin_lock_irq(&pdx->charOutLock); /* Protect ourselves */
+
+ if ((!pdx->bSendCharsPending) && /* Not currently sending */
+ (pdx->dwNumOutput > 0) && /* has characters to output */
+ (CanAcceptIoRequests(pdx))) { /* and current activity is OK */
+ unsigned int dwCount = pdx->dwNumOutput; /* Get a copy of the character count */
+ pdx->bSendCharsPending = true; /* Set flag to lock out other threads */
+
+ dev_dbg(&pdx->interface->dev,
+ "Send %d chars to 1401, EP0 flag %d\n",
+ dwCount, pdx->nPipes == 3);
+ /* If we have only 3 end points we must send the characters to the 1401 using EP0. */
+ if (pdx->nPipes == 3) {
+ /* For EP0 character transmissions to the 1401, we have to hang about until they */
+ /* are gone, as otherwise without more character IO activity they will never go. */
+ unsigned int count = dwCount; /* Local char counter */
+ unsigned int index = 0; /* The index into the char buffer */
+
+ spin_unlock_irq(&pdx->charOutLock); /* Free spinlock as we call USBD */
+
+ while ((count > 0) && (iReturn == U14ERR_NOERROR)) {
+ /* We have to break the transfer up into 64-byte chunks because of a 2270 problem */
+ int n = count > 64 ? 64 : count; /* Chars for this xfer, max of 64 */
+ int nSent = usb_control_msg(pdx->udev,
+ usb_sndctrlpipe(pdx->udev, 0), /* use end point 0 */
+ DB_CHARS, /* bRequest */
+ (H_TO_D | VENDOR | DEVREQ), /* to the device, vendor request to the device */
+ 0, 0, /* value and index are both 0 */
+ &pdx->outputBuffer[index], /* where to send from */
+ n, /* how much to send */
+ 1000); /* timeout in jiffies */
+ if (nSent <= 0) {
+ iReturn = nSent ? nSent : -ETIMEDOUT; /* if 0 chars says we timed out */
+ dev_err(&pdx->interface->dev,
+ "Send %d chars by EP0 failed: %d\n",
+ n, iReturn);
+ } else {
+ dev_dbg(&pdx->interface->dev,
+ "Sent %d chars by EP0\n", n);
+ count -= nSent;
+ index += nSent;
+ }
+ }
+
+ spin_lock_irq(&pdx->charOutLock); /* Protect pdx changes, released by general code */
+ pdx->dwOutBuffGet = 0; /* so reset the output buffer */
+ pdx->dwOutBuffPut = 0;
+ pdx->dwNumOutput = 0; /* and clear the buffer count */
+ pdx->bSendCharsPending = false; /* Allow other threads again */
+ } else { /* Here for sending chars normally - we hold the spin lock */
+ int nPipe = 0; /* The pipe number to use */
+ char *pDat = &pdx->outputBuffer[pdx->dwOutBuffGet];
+
+ if ((pdx->dwOutBuffGet + dwCount) > OUTBUF_SZ) /* does it cross buffer end? */
+ dwCount = OUTBUF_SZ - pdx->dwOutBuffGet;
+ spin_unlock_irq(&pdx->charOutLock); /* we are done with stuff that changes */
+ memcpy(pdx->pCoherCharOut, pDat, dwCount); /* copy output data to the buffer */
+ usb_fill_bulk_urb(pdx->pUrbCharOut, pdx->udev,
+ usb_sndbulkpipe(pdx->udev,
+ pdx->epAddr[0]),
+ pdx->pCoherCharOut, dwCount,
+ ced_writechar_callback, pdx);
+ pdx->pUrbCharOut->transfer_flags |=
+ URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(pdx->pUrbCharOut, &pdx->submitted);
+ iReturn = usb_submit_urb(pdx->pUrbCharOut, GFP_KERNEL);
+ spin_lock_irq(&pdx->charOutLock); /* grab lock for errors */
+ if (iReturn) {
+ pdx->bPipeError[nPipe] = 1; /* Flag an error to be handled later */
+ pdx->bSendCharsPending = false; /* Allow other threads again */
+ usb_unanchor_urb(pdx->pUrbCharOut); /* remove from list of active urbs */
+ }
+ }
+ } else if (pdx->bSendCharsPending && (pdx->dwNumOutput > 0))
+ dev_dbg(&pdx->interface->dev,
+ "%s: bSendCharsPending:true\n", __func__);
+
+ dev_dbg(&pdx->interface->dev, "%s: exit code: %d\n", __func__, iReturn);
+ spin_unlock_irq(&pdx->charOutLock); /* Now let go of the spinlock */
+ return iReturn;
+}
+
+/***************************************************************************
+** CopyUserSpace
+** This moves memory between pinned down user space and the pCoherStagedIO
+** memory buffer we use for transfers. Copy n bytes in the directions that
+** is defined by pdx->StagedRead. The user space is determined by the area
+** in pdx->StagedId and the offset in pdx->StagedDone. The user
+** area may well not start on a page boundary, so allow for that.
+**
+** We have a table of physical pages that describe the area, so we can use
+** this to get a virtual address that the kernel can use.
+**
+** pdx Is our device extension which holds all we know about the transfer.
+** n The number of bytes to move one way or the other.
+***************************************************************************/
+static void CopyUserSpace(DEVICE_EXTENSION *pdx, int n)
+{
+ unsigned int nArea = pdx->StagedId;
+ if (nArea < MAX_TRANSAREAS) {
+ TRANSAREA *pArea = &pdx->rTransDef[nArea]; /* area to be used */
+ unsigned int dwOffset =
+ pdx->StagedDone + pdx->StagedOffset + pArea->dwBaseOffset;
+ char *pCoherBuf = pdx->pCoherStagedIO; /* coherent buffer */
+ if (!pArea->bUsed) {
+ dev_err(&pdx->interface->dev, "%s: area %d unused\n",
+ __func__, nArea);
+ return;
+ }
+
+ while (n) {
+ int nPage = dwOffset >> PAGE_SHIFT; /* page number in table */
+ if (nPage < pArea->nPages) {
+ char *pvAddress =
+ (char *)kmap_atomic(pArea->pPages[nPage]);
+ if (pvAddress) {
+ unsigned int uiPageOff = dwOffset & (PAGE_SIZE - 1); /* offset into the page */
+ size_t uiXfer = PAGE_SIZE - uiPageOff; /* max to transfer on this page */
+ if (uiXfer > n) /* limit byte count if too much */
+ uiXfer = n; /* for the page */
+ if (pdx->StagedRead)
+ memcpy(pvAddress + uiPageOff,
+ pCoherBuf, uiXfer);
+ else
+ memcpy(pCoherBuf,
+ pvAddress + uiPageOff,
+ uiXfer);
+ kunmap_atomic(pvAddress);
+ dwOffset += uiXfer;
+ pCoherBuf += uiXfer;
+ n -= uiXfer;
+ } else {
+ dev_err(&pdx->interface->dev,
+ "%s: did not map page %d\n",
+ __func__, nPage);
+ return;
+ }
+
+ } else {
+ dev_err(&pdx->interface->dev,
+ "%s: exceeded pages %d\n",
+ __func__, nPage);
+ return;
+ }
+ }
+ } else
+ dev_err(&pdx->interface->dev, "%s: bad area %d\n",
+ __func__, nArea);
+}
+
+/* Forward declarations for stuff used circularly */
+static int StageChunk(DEVICE_EXTENSION *pdx);
+/***************************************************************************
+** ReadWrite_Complete
+**
+** Completion routine for our staged read/write Irps
+*/
+static void staged_callback(struct urb *pUrb)
+{
+ DEVICE_EXTENSION *pdx = pUrb->context;
+ unsigned int nGot = pUrb->actual_length; /* what we transferred */
+ bool bCancel = false;
+ bool bRestartCharInput; /* used at the end */
+
+ spin_lock(&pdx->stagedLock); /* stop ReadWriteMem() action while this routine is running */
+ pdx->bStagedUrbPending = false; /* clear the flag for staged IRP pending */
+
+ if (pUrb->status) { /* sync/async unlink faults aren't errors */
+ if (!
+ (pUrb->status == -ENOENT || pUrb->status == -ECONNRESET
+ || pUrb->status == -ESHUTDOWN)) {
+ dev_err(&pdx->interface->dev,
+ "%s: nonzero write bulk status received: %d\n",
+ __func__, pUrb->status);
+ } else
+ dev_info(&pdx->interface->dev,
+ "%s: staged xfer cancelled\n", __func__);
+
+ spin_lock(&pdx->err_lock);
+ pdx->errors = pUrb->status;
+ spin_unlock(&pdx->err_lock);
+ nGot = 0; /* and tidy up again if so */
+ bCancel = true;
+ } else {
+ dev_dbg(&pdx->interface->dev, "%s: %d chars xferred\n",
+ __func__, nGot);
+ if (pdx->StagedRead) /* if reading, save to user space */
+ CopyUserSpace(pdx, nGot); /* copy from buffer to user */
+ if (nGot == 0)
+ dev_dbg(&pdx->interface->dev, "%s: ZLP\n", __func__);
+ }
+
+ /* Update the transfer length based on the TransferBufferLength value in the URB */
+ pdx->StagedDone += nGot;
+
+ dev_dbg(&pdx->interface->dev, "%s: done %d bytes of %d\n",
+ __func__, pdx->StagedDone, pdx->StagedLength);
+
+ if ((pdx->StagedDone == pdx->StagedLength) || /* If no more to do */
+ (bCancel)) { /* or this IRP was cancelled */
+ TRANSAREA *pArea = &pdx->rTransDef[pdx->StagedId]; /* Transfer area info */
+ dev_dbg(&pdx->interface->dev,
+ "%s: transfer done, bytes %d, cancel %d\n",
+ __func__, pdx->StagedDone, bCancel);
+
+ /* Here is where we sort out what to do with this transfer if using a circular buffer. We have */
+ /* a completed transfer that can be assumed to fit into the transfer area. We should be able to */
+ /* add this to the end of a growing block or to use it to start a new block unless the code */
+ /* that calculates the offset to use (in ReadWriteMem) is totally duff. */
+ if ((pArea->bCircular) && (pArea->bCircToHost) && (!bCancel) && /* Time to sort out circular buffer info? */
+ (pdx->StagedRead)) { /* Only for tohost transfers for now */
+ if (pArea->aBlocks[1].dwSize > 0) { /* If block 1 is in use we must append to it */
+ if (pdx->StagedOffset ==
+ (pArea->aBlocks[1].dwOffset +
+ pArea->aBlocks[1].dwSize)) {
+ pArea->aBlocks[1].dwSize +=
+ pdx->StagedLength;
+ dev_dbg(&pdx->interface->dev,
+ "RWM_Complete, circ block 1 now %d bytes at %d\n",
+ pArea->aBlocks[1].dwSize,
+ pArea->aBlocks[1].dwOffset);
+ } else {
+ /* Here things have gone very, very, wrong, but I cannot see how this can actually be achieved */
+ pArea->aBlocks[1].dwOffset =
+ pdx->StagedOffset;
+ pArea->aBlocks[1].dwSize =
+ pdx->StagedLength;
+ dev_err(&pdx->interface->dev,
+ "%s: ERROR, circ block 1 re-started %d bytes at %d\n",
+ __func__,
+ pArea->aBlocks[1].dwSize,
+ pArea->aBlocks[1].dwOffset);
+ }
+ } else { /* If block 1 is not used, we try to add to block 0 */
+ if (pArea->aBlocks[0].dwSize > 0) { /* Got stored block 0 information? */
+ /* Must append onto the existing block 0 */
+ if (pdx->StagedOffset ==
+ (pArea->aBlocks[0].dwOffset +
+ pArea->aBlocks[0].dwSize)) {
+ pArea->aBlocks[0].dwSize += pdx->StagedLength; /* Just add this transfer in */
+ dev_dbg(&pdx->interface->dev,
+ "RWM_Complete, circ block 0 now %d bytes at %d\n",
+ pArea->aBlocks[0].
+ dwSize,
+ pArea->aBlocks[0].
+ dwOffset);
+ } else { /* If it doesn't append, put into new block 1 */
+ pArea->aBlocks[1].dwOffset =
+ pdx->StagedOffset;
+ pArea->aBlocks[1].dwSize =
+ pdx->StagedLength;
+ dev_dbg(&pdx->interface->dev,
+ "RWM_Complete, circ block 1 started %d bytes at %d\n",
+ pArea->aBlocks[1].
+ dwSize,
+ pArea->aBlocks[1].
+ dwOffset);
+ }
+ } else { /* No info stored yet, just save in block 0 */
+ pArea->aBlocks[0].dwOffset =
+ pdx->StagedOffset;
+ pArea->aBlocks[0].dwSize =
+ pdx->StagedLength;
+ dev_dbg(&pdx->interface->dev,
+ "RWM_Complete, circ block 0 started %d bytes at %d\n",
+ pArea->aBlocks[0].dwSize,
+ pArea->aBlocks[0].dwOffset);
+ }
+ }
+ }
+
+ if (!bCancel) { /* Don't generate an event if cancelled */
+ dev_dbg(&pdx->interface->dev,
+ "RWM_Complete, bCircular %d, bToHost %d, eStart %d, eSize %d\n",
+ pArea->bCircular, pArea->bEventToHost,
+ pArea->dwEventSt, pArea->dwEventSz);
+ if ((pArea->dwEventSz) && /* Set a user-mode event... */
+ (pdx->StagedRead == pArea->bEventToHost)) { /* ...on transfers in this direction? */
+ int iWakeUp = 0; /* assume */
+ /* If we have completed the right sort of DMA transfer then set the event to notify */
+ /* the user code to wake up anyone that is waiting. */
+ if ((pArea->bCircular) && /* Circular areas use a simpler test */
+ (pArea->bCircToHost)) { /* only in supported direction */
+ /* Is total data waiting up to size limit? */
+ unsigned int dwTotal =
+ pArea->aBlocks[0].dwSize +
+ pArea->aBlocks[1].dwSize;
+ iWakeUp = (dwTotal >= pArea->dwEventSz);
+ } else {
+ unsigned int transEnd =
+ pdx->StagedOffset +
+ pdx->StagedLength;
+ unsigned int eventEnd =
+ pArea->dwEventSt + pArea->dwEventSz;
+ iWakeUp = (pdx->StagedOffset < eventEnd)
+ && (transEnd > pArea->dwEventSt);
+ }
+
+ if (iWakeUp) {
+ dev_dbg(&pdx->interface->dev,
+ "About to set event to notify app\n");
+ wake_up_interruptible(&pArea->wqEvent); /* wake up waiting processes */
+ ++pArea->iWakeUp; /* increment wakeup count */
+ }
+ }
+ }
+
+ pdx->dwDMAFlag = MODE_CHAR; /* Switch back to char mode before ReadWriteMem call */
+
+ if (!bCancel) { /* Don't look for waiting transfer if cancelled */
+ /* If we have a transfer waiting, kick it off */
+ if (pdx->bXFerWaiting) { /* Got a block xfer waiting? */
+ int iReturn;
+ dev_info(&pdx->interface->dev,
+ "*** RWM_Complete *** pending transfer will now be set up!!!\n");
+ iReturn =
+ ReadWriteMem(pdx, !pdx->rDMAInfo.bOutWard,
+ pdx->rDMAInfo.wIdent,
+ pdx->rDMAInfo.dwOffset,
+ pdx->rDMAInfo.dwSize);
+
+ if (iReturn)
+ dev_err(&pdx->interface->dev,
+ "RWM_Complete rw setup failed %d\n",
+ iReturn);
+ }
+ }
+
+ } else /* Here for more to do */
+ StageChunk(pdx); /* fire off the next bit */
+
+ /* While we hold the stagedLock, see if we should reallow character input ints */
+ /* Don't allow if cancelled, or if a new block has started or if there is a waiting block. */
+ /* This feels wrong as we should ask which spin lock protects dwDMAFlag. */
+ bRestartCharInput = !bCancel && (pdx->dwDMAFlag == MODE_CHAR)
+ && !pdx->bXFerWaiting;
+
+ spin_unlock(&pdx->stagedLock); /* Finally release the lock again */
+
+ /* This is not correct as dwDMAFlag is protected by the staged lock, but it is treated */
+ /* in Allowi as if it were protected by the char lock. In any case, most systems will */
+ /* not be upset by char input during DMA... sigh. Needs sorting out. */
+ if (bRestartCharInput) /* may be out of date, but... */
+ Allowi(pdx); /* ...Allowi tests a lock too. */
+ dev_dbg(&pdx->interface->dev, "%s: done\n", __func__);
+}
+
+/****************************************************************************
+** StageChunk
+**
+** Generates the next chunk of data making up a staged transfer.
+**
+** The calling code must have acquired the staging spinlock before calling
+** this function, and is responsible for releasing it. We are at callback level.
+****************************************************************************/
+static int StageChunk(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = U14ERR_NOERROR;
+ unsigned int ChunkSize;
+ int nPipe = pdx->StagedRead ? 3 : 2; /* The pipe number to use for reads or writes */
+ if (pdx->nPipes == 3)
+ nPipe--; /* Adjust for the 3-pipe case */
+ if (nPipe < 0) /* and trap case that should never happen */
+ return U14ERR_FAIL;
+
+ if (!CanAcceptIoRequests(pdx)) { /* got sudden remove? */
+ dev_info(&pdx->interface->dev, "%s: sudden remove, giving up\n",
+ __func__);
+ return U14ERR_FAIL; /* could do with a better error */
+ }
+
+ ChunkSize = (pdx->StagedLength - pdx->StagedDone); /* transfer length remaining */
+ if (ChunkSize > STAGED_SZ) /* make sure to keep legal */
+ ChunkSize = STAGED_SZ; /* limit to max allowed */
+
+ if (!pdx->StagedRead) /* if writing... */
+ CopyUserSpace(pdx, ChunkSize); /* ...copy data into the buffer */
+
+ usb_fill_bulk_urb(pdx->pStagedUrb, pdx->udev,
+ pdx->StagedRead ? usb_rcvbulkpipe(pdx->udev,
+ pdx->
+ epAddr[nPipe]) :
+ usb_sndbulkpipe(pdx->udev, pdx->epAddr[nPipe]),
+ pdx->pCoherStagedIO, ChunkSize, staged_callback, pdx);
+ pdx->pStagedUrb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(pdx->pStagedUrb, &pdx->submitted); /* in case we need to kill it */
+ iReturn = usb_submit_urb(pdx->pStagedUrb, GFP_ATOMIC);
+ if (iReturn) {
+ usb_unanchor_urb(pdx->pStagedUrb); /* kill it */
+ pdx->bPipeError[nPipe] = 1; /* Flag an error to be handled later */
+ dev_err(&pdx->interface->dev, "%s: submit urb failed, code %d\n",
+ __func__, iReturn);
+ } else
+ pdx->bStagedUrbPending = true; /* Set the flag for staged URB pending */
+ dev_dbg(&pdx->interface->dev, "%s: done so far:%d, this size:%d\n",
+ __func__, pdx->StagedDone, ChunkSize);
+
+ return iReturn;
+}
+
+/***************************************************************************
+** ReadWriteMem
+**
+** This routine is used generally for block read and write operations.
+** Breaks up a read or write in to specified sized chunks, as specified by pipe
+** information on maximum transfer size.
+**
+** Any code that calls this must be holding the stagedLock
+**
+** Arguments:
+** DeviceObject - pointer to our FDO (Functional Device Object)
+** Read - TRUE for read, FALSE for write. This is from POV of the driver
+** wIdent - the transfer area number - defines memory area and more.
+** dwOffs - the start offset within the transfer area of the start of this
+** transfer.
+** dwLen - the number of bytes to transfer.
+*/
+int ReadWriteMem(DEVICE_EXTENSION *pdx, bool Read, unsigned short wIdent,
+ unsigned int dwOffs, unsigned int dwLen)
+{
+ TRANSAREA *pArea = &pdx->rTransDef[wIdent]; /* Transfer area info */
+
+ if (!CanAcceptIoRequests(pdx)) { /* Are we in a state to accept new requests? */
+ dev_err(&pdx->interface->dev, "%s: can't accept requests\n",
+ __func__);
+ return U14ERR_FAIL;
+ }
+
+ dev_dbg(&pdx->interface->dev,
+ "%s: xfer %d bytes to %s, offset %d, area %d\n",
+ __func__, dwLen, Read ? "host" : "1401", dwOffs, wIdent);
+
+ /* Amazingly, we can get an escape sequence back before the current staged Urb is done, so we */
+ /* have to check for this situation and, if so, wait until all is OK. */
+ if (pdx->bStagedUrbPending) {
+ pdx->bXFerWaiting = true; /* Flag we are waiting */
+ dev_info(&pdx->interface->dev,
+ "%s: xfer is waiting, as previous staged pending\n",
+ __func__);
+ return U14ERR_NOERROR;
+ }
+
+ if (dwLen == 0) { /* allow 0-len read or write; just return success */
+ dev_dbg(&pdx->interface->dev,
+ "%s: OK; zero-len read/write request\n", __func__);
+ return U14ERR_NOERROR;
+ }
+
+ if ((pArea->bCircular) && /* Circular transfer? */
+ (pArea->bCircToHost) && (Read)) { /* In a supported direction */
+ /* If so, we sort out offset ourself */
+ bool bWait = false; /* Flag for transfer having to wait */
+
+ dev_dbg(&pdx->interface->dev,
+ "Circular buffers are %d at %d and %d at %d\n",
+ pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset,
+ pArea->aBlocks[1].dwSize, pArea->aBlocks[1].dwOffset);
+ if (pArea->aBlocks[1].dwSize > 0) { /* Using the second block already? */
+ dwOffs = pArea->aBlocks[1].dwOffset + pArea->aBlocks[1].dwSize; /* take offset from that */
+ bWait = (dwOffs + dwLen) > pArea->aBlocks[0].dwOffset; /* Wait if will overwrite block 0? */
+ bWait |= (dwOffs + dwLen) > pArea->dwLength; /* or if it overflows the buffer */
+ } else { /* Area 1 not in use, try to use area 0 */
+ if (pArea->aBlocks[0].dwSize == 0) /* Reset block 0 if not in use */
+ pArea->aBlocks[0].dwOffset = 0;
+ dwOffs =
+ pArea->aBlocks[0].dwOffset +
+ pArea->aBlocks[0].dwSize;
+ if ((dwOffs + dwLen) > pArea->dwLength) { /* Off the end of the buffer? */
+ pArea->aBlocks[1].dwOffset = 0; /* Set up to use second block */
+ dwOffs = 0;
+ bWait = (dwOffs + dwLen) > pArea->aBlocks[0].dwOffset; /* Wait if will overwrite block 0? */
+ bWait |= (dwOffs + dwLen) > pArea->dwLength; /* or if it overflows the buffer */
+ }
+ }
+
+ if (bWait) { /* This transfer will have to wait? */
+ pdx->bXFerWaiting = true; /* Flag we are waiting */
+ dev_dbg(&pdx->interface->dev,
+ "%s: xfer waiting for circular buffer space\n",
+ __func__);
+ return U14ERR_NOERROR;
+ }
+
+ dev_dbg(&pdx->interface->dev,
+ "%s: circular xfer, %d bytes starting at %d\n",
+ __func__, dwLen, dwOffs);
+ }
+ /* Save the parameters for the read\write transfer */
+ pdx->StagedRead = Read; /* Save the parameters for this read */
+ pdx->StagedId = wIdent; /* ID allows us to get transfer area info */
+ pdx->StagedOffset = dwOffs; /* The area within the transfer area */
+ pdx->StagedLength = dwLen;
+ pdx->StagedDone = 0; /* Initialise the byte count */
+ pdx->dwDMAFlag = MODE_LINEAR; /* Set DMA mode flag at this point */
+ pdx->bXFerWaiting = false; /* Clearly not a transfer waiting now */
+
+/* KeClearEvent(&pdx->StagingDoneEvent); // Clear the transfer done event */
+ StageChunk(pdx); /* fire off the first chunk */
+
+ return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+**
+** ReadChar
+**
+** Reads a character a buffer. If there is no more
+** data we return FALSE. Used as part of decoding a DMA request.
+**
+****************************************************************************/
+static bool ReadChar(unsigned char *pChar, char *pBuf, unsigned int *pdDone,
+ unsigned int dGot)
+{
+ bool bRead = false;
+ unsigned int dDone = *pdDone;
+
+ if (dDone < dGot) { /* If there is more data */
+ *pChar = (unsigned char)pBuf[dDone]; /* Extract the next char */
+ dDone++; /* Increment the done count */
+ *pdDone = dDone;
+ bRead = true; /* and flag success */
+ }
+
+ return bRead;
+}
+
+#ifdef NOTUSED
+/****************************************************************************
+**
+** ReadWord
+**
+** Reads a word from the 1401, just uses ReadChar twice; passes on any error
+**
+*****************************************************************************/
+static bool ReadWord(unsigned short *pWord, char *pBuf, unsigned int *pdDone,
+ unsigned int dGot)
+{
+ if (ReadChar((unsigned char *)pWord, pBuf, pdDone, dGot))
+ return ReadChar(((unsigned char *)pWord) + 1, pBuf, pdDone,
+ dGot);
+ else
+ return false;
+}
+#endif
+
+/****************************************************************************
+** ReadHuff
+**
+** Reads a coded number in and returns it, Code is:
+** If data is in range 0..127 we receive 1 byte. If data in range 128-16383
+** we receive two bytes, top bit of first indicates another on its way. If
+** data in range 16384-4194303 we get three bytes, top two bits of first set
+** to indicate three byte total.
+**
+*****************************************************************************/
+static bool ReadHuff(volatile unsigned int *pDWord, char *pBuf,
+ unsigned int *pdDone, unsigned int dGot)
+{
+ unsigned char ucData; /* for each read to ReadChar */
+ bool bReturn = true; /* assume we will succeed */
+ unsigned int dwData = 0; /* Accumulator for the data */
+
+ if (ReadChar(&ucData, pBuf, pdDone, dGot)) {
+ dwData = ucData; /* copy the data */
+ if ((dwData & 0x00000080) != 0) { /* Bit set for more data ? */
+ dwData &= 0x0000007F; /* Clear the relevant bit */
+ if (ReadChar(&ucData, pBuf, pdDone, dGot)) {
+ dwData = (dwData << 8) | ucData;
+ if ((dwData & 0x00004000) != 0) { /* three byte sequence ? */
+ dwData &= 0x00003FFF; /* Clear the relevant bit */
+ if (ReadChar
+ (&ucData, pBuf, pdDone, dGot))
+ dwData = (dwData << 8) | ucData;
+ else
+ bReturn = false;
+ }
+ } else
+ bReturn = false; /* couldn't read data */
+ }
+ } else
+ bReturn = false;
+
+ *pDWord = dwData; /* return the data */
+ return bReturn;
+}
+
+/***************************************************************************
+**
+** ReadDMAInfo
+**
+** Tries to read info about the dma request from the 1401 and decode it into
+** the dma descriptor block. We have at this point had the escape character
+** from the 1401 and now we must read in the rest of the information about
+** the transfer request. Returns FALSE if 1401 fails to respond or obselete
+** code from 1401 or bad parameters.
+**
+** The pBuf char pointer does not include the initial escape character, so
+** we start handling the data at offset zero.
+**
+*****************************************************************************/
+static bool ReadDMAInfo(volatile DMADESC *pDmaDesc, DEVICE_EXTENSION *pdx,
+ char *pBuf, unsigned int dwCount)
+{
+ bool bResult = false; /* assume we won't succeed */
+ unsigned char ucData;
+ unsigned int dDone = 0; /* We haven't parsed anything so far */
+
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ if (ReadChar(&ucData, pBuf, &dDone, dwCount)) {
+ unsigned char ucTransCode = (ucData & 0x0F); /* get code for transfer type */
+ unsigned short wIdent = ((ucData >> 4) & 0x07); /* and area identifier */
+
+ /* fill in the structure we were given */
+ pDmaDesc->wTransType = ucTransCode; /* type of transfer */
+ pDmaDesc->wIdent = wIdent; /* area to use */
+ pDmaDesc->dwSize = 0; /* initialise other bits */
+ pDmaDesc->dwOffset = 0;
+
+ dev_dbg(&pdx->interface->dev, "%s: type: %d ident: %d\n",
+ __func__, pDmaDesc->wTransType, pDmaDesc->wIdent);
+
+ pDmaDesc->bOutWard = (ucTransCode != TM_EXTTOHOST); /* set transfer direction */
+
+ switch (ucTransCode) {
+ case TM_EXTTOHOST: /* Extended linear transfer modes (the only ones!) */
+ case TM_EXTTO1401:
+ {
+ bResult =
+ ReadHuff(&(pDmaDesc->dwOffset), pBuf,
+ &dDone, dwCount)
+ && ReadHuff(&(pDmaDesc->dwSize), pBuf,
+ &dDone, dwCount);
+ if (bResult) {
+ dev_dbg(&pdx->interface->dev,
+ "%s: xfer offset & size %d %d\n",
+ __func__, pDmaDesc->dwOffset,
+ pDmaDesc->dwSize);
+
+ if ((wIdent >= MAX_TRANSAREAS) || /* Illegal area number, or... */
+ (!pdx->rTransDef[wIdent].bUsed) || /* area not set up, or... */
+ (pDmaDesc->dwOffset > pdx->rTransDef[wIdent].dwLength) || /* range/size */
+ ((pDmaDesc->dwOffset +
+ pDmaDesc->dwSize) >
+ (pdx->rTransDef[wIdent].
+ dwLength))) {
+ bResult = false; /* bad parameter(s) */
+ dev_dbg(&pdx->interface->dev,
+ "%s: bad param - id %d, bUsed %d, offset %d, size %d, area length %d\n",
+ __func__, wIdent,
+ pdx->rTransDef[wIdent].
+ bUsed,
+ pDmaDesc->dwOffset,
+ pDmaDesc->dwSize,
+ pdx->rTransDef[wIdent].
+ dwLength);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ } else
+ bResult = false;
+
+ if (!bResult) /* now check parameters for validity */
+ dev_err(&pdx->interface->dev, "%s: error reading Esc sequence\n",
+ __func__);
+
+ return bResult;
+}
+
+/****************************************************************************
+**
+** Handle1401Esc
+**
+** Deals with an escape sequence coming from the 1401. This can either be
+** a DMA transfer request of various types or a response to an escape sequence
+** sent to the 1401. This is called from a callback.
+**
+** Parameters are
+**
+** dwCount - the number of characters in the device extension char in buffer,
+** this is known to be at least 2 or we will not be called.
+**
+****************************************************************************/
+static int Handle1401Esc(DEVICE_EXTENSION *pdx, char *pCh,
+ unsigned int dwCount)
+{
+ int iReturn = U14ERR_FAIL;
+
+ /* I have no idea what this next test is about. '?' is 0x3f, which is area 3, code */
+ /* 15. At the moment, this is not used, so it does no harm, but unless someone can */
+ /* tell me what this is for, it should be removed from this and the Windows driver. */
+ if (pCh[0] == '?') { /* Is this an information response */
+ /* Parse and save the information */
+ } else {
+ spin_lock(&pdx->stagedLock); /* Lock others out */
+
+ if (ReadDMAInfo(&pdx->rDMAInfo, pdx, pCh, dwCount)) { /* Get DMA parameters */
+ unsigned short wTransType = pdx->rDMAInfo.wTransType; /* check transfer type */
+
+ dev_dbg(&pdx->interface->dev,
+ "%s: xfer to %s, offset %d, length %d\n",
+ __func__,
+ pdx->rDMAInfo.bOutWard ? "1401" : "host",
+ pdx->rDMAInfo.dwOffset, pdx->rDMAInfo.dwSize);
+
+ if (pdx->bXFerWaiting) { /* Check here for badly out of kilter... */
+ /* This can never happen, really */
+ dev_err(&pdx->interface->dev,
+ "ERROR: DMA setup while transfer still waiting\n");
+ } else {
+ if ((wTransType == TM_EXTTOHOST)
+ || (wTransType == TM_EXTTO1401)) {
+ iReturn =
+ ReadWriteMem(pdx,
+ !pdx->rDMAInfo.
+ bOutWard,
+ pdx->rDMAInfo.wIdent,
+ pdx->rDMAInfo.dwOffset,
+ pdx->rDMAInfo.dwSize);
+ if (iReturn != U14ERR_NOERROR)
+ dev_err(&pdx->interface->dev,
+ "%s: ReadWriteMem() failed %d\n",
+ __func__, iReturn);
+ } else /* This covers non-linear transfer setup */
+ dev_err(&pdx->interface->dev,
+ "%s: Unknown block xfer type %d\n",
+ __func__, wTransType);
+ }
+ } else /* Failed to read parameters */
+ dev_err(&pdx->interface->dev, "%s: ReadDMAInfo() fail\n",
+ __func__);
+
+ spin_unlock(&pdx->stagedLock); /* OK here */
+ }
+
+ dev_dbg(&pdx->interface->dev, "%s: returns %d\n", __func__, iReturn);
+
+ return iReturn;
+}
+
+/****************************************************************************
+** Callback for the character read complete or error
+****************************************************************************/
+static void ced_readchar_callback(struct urb *pUrb)
+{
+ DEVICE_EXTENSION *pdx = pUrb->context;
+ int nGot = pUrb->actual_length; /* what we transferred */
+
+ if (pUrb->status) { /* Do we have a problem to handle? */
+ int nPipe = pdx->nPipes == 4 ? 1 : 0; /* The pipe number to use for error */
+ /* sync/async unlink faults aren't errors... just saying device removed or stopped */
+ if (!
+ (pUrb->status == -ENOENT || pUrb->status == -ECONNRESET
+ || pUrb->status == -ESHUTDOWN)) {
+ dev_err(&pdx->interface->dev,
+ "%s: nonzero write bulk status received: %d\n",
+ __func__, pUrb->status);
+ } else
+ dev_dbg(&pdx->interface->dev,
+ "%s: 0 chars pUrb->status=%d (shutdown?)\n",
+ __func__, pUrb->status);
+
+ spin_lock(&pdx->err_lock);
+ pdx->errors = pUrb->status;
+ spin_unlock(&pdx->err_lock);
+ nGot = 0; /* and tidy up again if so */
+
+ spin_lock(&pdx->charInLock); /* already at irq level */
+ pdx->bPipeError[nPipe] = 1; /* Flag an error for later */
+ } else {
+ if ((nGot > 1) && ((pdx->pCoherCharIn[0] & 0x7f) == 0x1b)) { /* Esc sequence? */
+ Handle1401Esc(pdx, &pdx->pCoherCharIn[1], nGot - 1); /* handle it */
+ spin_lock(&pdx->charInLock); /* already at irq level */
+ } else {
+ spin_lock(&pdx->charInLock); /* already at irq level */
+ if (nGot > 0) {
+ unsigned int i;
+ if (nGot < INBUF_SZ) {
+ pdx->pCoherCharIn[nGot] = 0; /* tidy the string */
+ dev_dbg(&pdx->interface->dev,
+ "%s: got %d chars >%s<\n",
+ __func__, nGot,
+ pdx->pCoherCharIn);
+ }
+ /* We know that whatever we read must fit in the input buffer */
+ for (i = 0; i < nGot; i++) {
+ pdx->inputBuffer[pdx->dwInBuffPut++] =
+ pdx->pCoherCharIn[i] & 0x7F;
+ if (pdx->dwInBuffPut >= INBUF_SZ)
+ pdx->dwInBuffPut = 0;
+ }
+
+ if ((pdx->dwNumInput + nGot) <= INBUF_SZ)
+ pdx->dwNumInput += nGot; /* Adjust the buffer count accordingly */
+ } else
+ dev_dbg(&pdx->interface->dev, "%s: read ZLP\n",
+ __func__);
+ }
+ }
+
+ pdx->bReadCharsPending = false; /* No longer have a pending read */
+ spin_unlock(&pdx->charInLock); /* already at irq level */
+
+ Allowi(pdx); /* see if we can do the next one */
+}
+
+/****************************************************************************
+** Allowi
+**
+** This is used to make sure that there is always a pending input transfer so
+** we can pick up any inward transfers. This can be called in multiple contexts
+** so we use the irqsave version of the spinlock.
+****************************************************************************/
+int Allowi(DEVICE_EXTENSION *pdx)
+{
+ int iReturn = U14ERR_NOERROR;
+ unsigned long flags;
+ spin_lock_irqsave(&pdx->charInLock, flags); /* can be called in multiple contexts */
+
+ /* We don't want char input running while DMA is in progress as we know that this */
+ /* can cause sequencing problems for the 2270. So don't. It will also allow the */
+ /* ERR response to get back to the host code too early on some PCs, even if there */
+ /* is no actual driver failure, so we don't allow this at all. */
+ if (!pdx->bInDrawDown && /* stop input if */
+ !pdx->bReadCharsPending && /* If no read request outstanding */
+ (pdx->dwNumInput < (INBUF_SZ / 2)) && /* and there is some space */
+ (pdx->dwDMAFlag == MODE_CHAR) && /* not doing any DMA */
+ (!pdx->bXFerWaiting) && /* no xfer waiting to start */
+ (CanAcceptIoRequests(pdx))) { /* and activity is generally OK */
+ /* then off we go */
+ unsigned int nMax = INBUF_SZ - pdx->dwNumInput; /* max we could read */
+ int nPipe = pdx->nPipes == 4 ? 1 : 0; /* The pipe number to use */
+
+ dev_dbg(&pdx->interface->dev, "%s: %d chars in input buffer\n",
+ __func__, pdx->dwNumInput);
+
+ usb_fill_int_urb(pdx->pUrbCharIn, pdx->udev,
+ usb_rcvintpipe(pdx->udev, pdx->epAddr[nPipe]),
+ pdx->pCoherCharIn, nMax, ced_readchar_callback,
+ pdx, pdx->bInterval);
+ pdx->pUrbCharIn->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* short xfers are OK by default */
+ usb_anchor_urb(pdx->pUrbCharIn, &pdx->submitted); /* in case we need to kill it */
+ iReturn = usb_submit_urb(pdx->pUrbCharIn, GFP_ATOMIC);
+ if (iReturn) {
+ usb_unanchor_urb(pdx->pUrbCharIn); /* remove from list of active Urbs */
+ pdx->bPipeError[nPipe] = 1; /* Flag an error to be handled later */
+ dev_err(&pdx->interface->dev,
+ "%s: submit urb failed: %d\n",
+ __func__, iReturn);
+ } else
+ pdx->bReadCharsPending = true; /* Flag that we are active here */
+ }
+
+ spin_unlock_irqrestore(&pdx->charInLock, flags);
+
+ return iReturn;
+
+}
+
+/*****************************************************************************
+** The ioctl entry point to the driver that is used by us to talk to it.
+** inode The device node (no longer in 3.0.0 kernels)
+** file The file that is open, which holds our pdx pointer
+** ulArg The argument passed in. Note that long is 64-bits in 64-bit system, i.e. it is big
+** enough for a 64-bit pointer.
+*****************************************************************************/
+static long ced_ioctl(struct file *file, unsigned int cmd, unsigned long ulArg)
+{
+ int err = 0;
+ DEVICE_EXTENSION *pdx = file->private_data;
+ if (!CanAcceptIoRequests(pdx)) /* check we still exist */
+ return -ENODEV;
+
+ /* Check that access is allowed, where is is needed. Anything that would have an indeterminate */
+ /* size will be checked by the specific command. */
+ if (_IOC_DIR(cmd) & _IOC_READ) /* read from point of view of user... */
+ err = !access_ok(VERIFY_WRITE, (void __user *)ulArg, _IOC_SIZE(cmd)); /* is kernel write */
+ else if (_IOC_DIR(cmd) & _IOC_WRITE) /* and write from point of view of user... */
+ err = !access_ok(VERIFY_READ, (void __user *)ulArg, _IOC_SIZE(cmd)); /* is kernel read */
+ if (err)
+ return -EFAULT;
+
+ switch (_IOC_NR(cmd)) {
+ case _IOC_NR(IOCTL_CED_SENDSTRING(0)):
+ return SendString(pdx, (const char __user *)ulArg,
+ _IOC_SIZE(cmd));
+
+ case _IOC_NR(IOCTL_CED_RESET1401):
+ return Reset1401(pdx);
+
+ case _IOC_NR(IOCTL_CED_GETCHAR):
+ return GetChar(pdx);
+
+ case _IOC_NR(IOCTL_CED_SENDCHAR):
+ return SendChar(pdx, (char)ulArg);
+
+ case _IOC_NR(IOCTL_CED_STAT1401):
+ return Stat1401(pdx);
+
+ case _IOC_NR(IOCTL_CED_LINECOUNT):
+ return LineCount(pdx);
+
+ case _IOC_NR(IOCTL_CED_GETSTRING(0)):
+ return GetString(pdx, (char __user *)ulArg, _IOC_SIZE(cmd));
+
+ case _IOC_NR(IOCTL_CED_SETTRANSFER):
+ return SetTransfer(pdx, (struct transfer_area_desc __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_UNSETTRANSFER):
+ return UnsetTransfer(pdx, (int)ulArg);
+
+ case _IOC_NR(IOCTL_CED_SETEVENT):
+ return SetEvent(pdx, (struct transfer_event __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_GETOUTBUFSPACE):
+ return GetOutBufSpace(pdx);
+
+ case _IOC_NR(IOCTL_CED_GETBASEADDRESS):
+ return -1;
+
+ case _IOC_NR(IOCTL_CED_GETDRIVERREVISION):
+ return (2 << 24) | (DRIVERMAJREV << 16) | DRIVERMINREV; /* USB | MAJOR | MINOR */
+
+ case _IOC_NR(IOCTL_CED_GETTRANSFER):
+ return GetTransfer(pdx, (TGET_TX_BLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_KILLIO1401):
+ return KillIO1401(pdx);
+
+ case _IOC_NR(IOCTL_CED_STATEOF1401):
+ return StateOf1401(pdx);
+
+ case _IOC_NR(IOCTL_CED_GRAB1401):
+ case _IOC_NR(IOCTL_CED_FREE1401):
+ return U14ERR_NOERROR;
+
+ case _IOC_NR(IOCTL_CED_STARTSELFTEST):
+ return StartSelfTest(pdx);
+
+ case _IOC_NR(IOCTL_CED_CHECKSELFTEST):
+ return CheckSelfTest(pdx, (TGET_SELFTEST __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_TYPEOF1401):
+ return TypeOf1401(pdx);
+
+ case _IOC_NR(IOCTL_CED_TRANSFERFLAGS):
+ return TransferFlags(pdx);
+
+ case _IOC_NR(IOCTL_CED_DBGPEEK):
+ return DbgPeek(pdx, (TDBGBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_DBGPOKE):
+ return DbgPoke(pdx, (TDBGBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_DBGRAMPDATA):
+ return DbgRampData(pdx, (TDBGBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_DBGRAMPADDR):
+ return DbgRampAddr(pdx, (TDBGBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_DBGGETDATA):
+ return DbgGetData(pdx, (TDBGBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_DBGSTOPLOOP):
+ return DbgStopLoop(pdx);
+
+ case _IOC_NR(IOCTL_CED_FULLRESET):
+ pdx->bForceReset = true; /* Set a flag for a full reset */
+ break;
+
+ case _IOC_NR(IOCTL_CED_SETCIRCULAR):
+ return SetCircular(pdx, (struct transfer_area_desc __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_GETCIRCBLOCK):
+ return GetCircBlock(pdx, (TCIRCBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_FREECIRCBLOCK):
+ return FreeCircBlock(pdx, (TCIRCBLOCK __user *) ulArg);
+
+ case _IOC_NR(IOCTL_CED_WAITEVENT):
+ return WaitEvent(pdx, (int)(ulArg & 0xff), (int)(ulArg >> 8));
+
+ case _IOC_NR(IOCTL_CED_TESTEVENT):
+ return TestEvent(pdx, (int)ulArg);
+
+ default:
+ return U14ERR_NO_SUCH_FN;
+ }
+ return U14ERR_NOERROR;
+}
+
+static const struct file_operations ced_fops = {
+ .owner = THIS_MODULE,
+ .open = ced_open,
+ .release = ced_release,
+ .flush = ced_flush,
+ .llseek = noop_llseek,
+ .unlocked_ioctl = ced_ioctl,
+};
+
+/*
+ * 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 ced_class = {
+ .name = "cedusb%d",
+ .fops = &ced_fops,
+ .minor_base = USB_CED_MINOR_BASE,
+};
+
+/* Check that the device that matches a 1401 vendor and product ID is OK to use and */
+/* initialise our DEVICE_EXTENSION. */
+static int ced_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ DEVICE_EXTENSION *pdx;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int i, bcdDevice;
+ int retval = -ENOMEM;
+
+ /* allocate memory for our device extension and initialize it */
+ pdx = kzalloc(sizeof(*pdx), GFP_KERNEL);
+ if (!pdx)
+ goto error;
+
+ for (i = 0; i < MAX_TRANSAREAS; ++i) { /* Initialise the wait queues */
+ init_waitqueue_head(&pdx->rTransDef[i].wqEvent);
+ }
+
+ /* Put initialises for our stuff here. Note that all of *pdx is zero, so */
+ /* no need to explicitly zero it. */
+ spin_lock_init(&pdx->charOutLock);
+ spin_lock_init(&pdx->charInLock);
+ spin_lock_init(&pdx->stagedLock);
+
+ /* Initialises from the skeleton stuff */
+ kref_init(&pdx->kref);
+ mutex_init(&pdx->io_mutex);
+ spin_lock_init(&pdx->err_lock);
+ init_usb_anchor(&pdx->submitted);
+
+ pdx->udev = usb_get_dev(interface_to_usbdev(interface));
+ pdx->interface = interface;
+
+ /* Attempt to identify the device */
+ bcdDevice = pdx->udev->descriptor.bcdDevice;
+ i = (bcdDevice >> 8);
+ if (i == 0)
+ pdx->s1401Type = TYPEU1401;
+ else if ((i >= 1) && (i <= 23))
+ pdx->s1401Type = i + 2;
+ else {
+ dev_err(&interface->dev, "%s: Unknown device. bcdDevice = %d\n",
+ __func__, bcdDevice);
+ goto error;
+ }
+ /* set up the endpoint information. We only care about the number of EP as */
+ /* we know that we are dealing with a 1401 device. */
+ iface_desc = interface->cur_altsetting;
+ pdx->nPipes = iface_desc->desc.bNumEndpoints;
+ dev_info(&interface->dev, "1401Type=%d with %d End Points\n",
+ pdx->s1401Type, pdx->nPipes);
+ if ((pdx->nPipes < 3) || (pdx->nPipes > 4))
+ goto error;
+
+ /* Allocate the URBs we hold for performing transfers */
+ pdx->pUrbCharOut = usb_alloc_urb(0, GFP_KERNEL); /* character output URB */
+ pdx->pUrbCharIn = usb_alloc_urb(0, GFP_KERNEL); /* character input URB */
+ pdx->pStagedUrb = usb_alloc_urb(0, GFP_KERNEL); /* block transfer URB */
+ if (!pdx->pUrbCharOut || !pdx->pUrbCharIn || !pdx->pStagedUrb) {
+ dev_err(&interface->dev, "%s: URB alloc failed\n", __func__);
+ goto error;
+ }
+
+ pdx->pCoherStagedIO =
+ usb_alloc_coherent(pdx->udev, STAGED_SZ, GFP_KERNEL,
+ &pdx->pStagedUrb->transfer_dma);
+ pdx->pCoherCharOut =
+ usb_alloc_coherent(pdx->udev, OUTBUF_SZ, GFP_KERNEL,
+ &pdx->pUrbCharOut->transfer_dma);
+ pdx->pCoherCharIn =
+ usb_alloc_coherent(pdx->udev, INBUF_SZ, GFP_KERNEL,
+ &pdx->pUrbCharIn->transfer_dma);
+ if (!pdx->pCoherCharOut || !pdx->pCoherCharIn || !pdx->pCoherStagedIO) {
+ dev_err(&interface->dev, "%s: Coherent buffer alloc failed\n",
+ __func__);
+ goto error;
+ }
+
+ for (i = 0; i < pdx->nPipes; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+ pdx->epAddr[i] = endpoint->bEndpointAddress;
+ dev_info(&interface->dev, "Pipe %d, ep address %02x\n",
+ i, pdx->epAddr[i]);
+ if (((pdx->nPipes == 3) && (i == 0)) || /* if char input end point */
+ ((pdx->nPipes == 4) && (i == 1))) {
+ pdx->bInterval = endpoint->bInterval; /* save the endpoint interrupt interval */
+ dev_info(&interface->dev, "Pipe %d, bInterval = %d\n",
+ i, pdx->bInterval);
+ }
+ /* Detect USB2 by checking last ep size (64 if USB1) */
+ if (i == pdx->nPipes - 1) { /* if this is the last ep (bulk) */
+ pdx->bIsUSB2 =
+ le16_to_cpu(endpoint->wMaxPacketSize) > 64;
+ dev_info(&pdx->interface->dev, "USB%d\n",
+ pdx->bIsUSB2 + 1);
+ }
+ }
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, pdx);
+
+ /* we can register the device now, as it is ready */
+ retval = usb_register_dev(interface, &ced_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(&interface->dev,
+ "Not able to get a minor for this device\n");
+ usb_set_intfdata(interface, NULL);
+ goto error;
+ }
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev,
+ "USB CEDUSB device now attached to cedusb #%d\n",
+ interface->minor);
+ return 0;
+
+error:
+ if (pdx)
+ kref_put(&pdx->kref, ced_delete); /* frees allocated memory */
+ return retval;
+}
+
+static void ced_disconnect(struct usb_interface *interface)
+{
+ DEVICE_EXTENSION *pdx = usb_get_intfdata(interface);
+ int minor = interface->minor;
+ int i;
+
+ usb_set_intfdata(interface, NULL); /* remove the pdx from the interface */
+ usb_deregister_dev(interface, &ced_class); /* give back our minor device number */
+
+ mutex_lock(&pdx->io_mutex); /* stop more I/O starting while... */
+ ced_draw_down(pdx); /* ...wait for then kill any io */
+ for (i = 0; i < MAX_TRANSAREAS; ++i) {
+ int iErr = ClearArea(pdx, i); /* ...release any used memory */
+ if (iErr == U14ERR_UNLOCKFAIL)
+ dev_err(&pdx->interface->dev, "%s: Area %d was in used\n",
+ __func__, i);
+ }
+ pdx->interface = NULL; /* ...we kill off link to interface */
+ mutex_unlock(&pdx->io_mutex);
+
+ usb_kill_anchored_urbs(&pdx->submitted);
+
+ kref_put(&pdx->kref, ced_delete); /* decrement our usage count */
+
+ dev_info(&interface->dev, "USB cedusb #%d now disconnected\n", minor);
+}
+
+/* Wait for all the urbs we know of to be done with, then kill off any that */
+/* are left. NBNB we will need to have a mechanism to stop circular xfers */
+/* from trying to fire off more urbs. We will wait up to 3 seconds for Urbs */
+/* to be done. */
+void ced_draw_down(DEVICE_EXTENSION *pdx)
+{
+ int time;
+ dev_dbg(&pdx->interface->dev, "%s: called\n", __func__);
+
+ pdx->bInDrawDown = true;
+ time = usb_wait_anchor_empty_timeout(&pdx->submitted, 3000);
+ if (!time) { /* if we timed out we kill the urbs */
+ usb_kill_anchored_urbs(&pdx->submitted);
+ dev_err(&pdx->interface->dev, "%s: timed out\n", __func__);
+ }
+ pdx->bInDrawDown = false;
+}
+
+static int ced_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+ if (!pdx)
+ return 0;
+ ced_draw_down(pdx);
+
+ dev_dbg(&pdx->interface->dev, "%s: called\n", __func__);
+ return 0;
+}
+
+static int ced_resume(struct usb_interface *intf)
+{
+ DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+ if (!pdx)
+ return 0;
+ dev_dbg(&pdx->interface->dev, "%s: called\n", __func__);
+ return 0;
+}
+
+static int ced_pre_reset(struct usb_interface *intf)
+{
+ DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+ mutex_lock(&pdx->io_mutex);
+ ced_draw_down(pdx);
+ return 0;
+}
+
+static int ced_post_reset(struct usb_interface *intf)
+{
+ DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+ dev_dbg(&pdx->interface->dev, "%s\n", __func__);
+
+ /* we are sure no URBs are active - no locking needed */
+ pdx->errors = -EPIPE;
+ mutex_unlock(&pdx->io_mutex);
+
+ return 0;
+}
+
+static struct usb_driver ced_driver = {
+ .name = "cedusb",
+ .probe = ced_probe,
+ .disconnect = ced_disconnect,
+ .suspend = ced_suspend,
+ .resume = ced_resume,
+ .pre_reset = ced_pre_reset,
+ .post_reset = ced_post_reset,
+ .id_table = ced_table,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(ced_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/ced1401/usb1401.h b/drivers/staging/ced1401/usb1401.h
new file mode 100644
index 00000000000..ea0fe6398a0
--- /dev/null
+++ b/drivers/staging/ced1401/usb1401.h
@@ -0,0 +1,246 @@
+/* usb1401.h
+ Header file for the CED 1401 USB device driver for Linux
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute 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.
+*/
+#ifndef __USB1401_H__
+#define __USB1401_H__
+#include "use1401.h"
+#include "ced_ioctl.h"
+
+#ifndef UINT
+#define UINT unsigned int
+#endif
+
+/** Device type codes, but these don't need to be extended - a succession is assumed
+** These are set for usb from the bcdDevice field (suitably mangled). Future devices
+** will be added in order of device creation to the list, so the names here are just
+** to help use remember which device is which. The U14ERR_... values follow the same
+** pattern for modern devices.a
+**/
+#define TYPEUNKNOWN -1 /* dont know */
+#define TYPE1401 0 /* standard 1401 */
+#define TYPEPLUS 1 /* 1401 plus */
+#define TYPEU1401 2 /* u1401 */
+#define TYPEPOWER 3 /* Power1401 */
+#define TYPEU14012 4 /* u1401 mkII */
+#define TYPEPOWER2 5 /* Power1401 mk II */
+#define TYPEMICRO3 6 /* Micro1401-3 */
+#define TYPEPOWER3 7 /* Power1401-3 */
+
+/* Some useful defines of constants. DONT FORGET to change the version in the */
+/* resources whenever you change it here!. */
+#define DRIVERMAJREV 2 /* driver revision level major (match windows) */
+#define DRIVERMINREV 0 /* driver revision level minor */
+
+/* Definitions of the various block transfer command codes */
+#define TM_EXTTOHOST 8 /* extended tohost */
+#define TM_EXTTO1401 9 /* extended to1401 */
+
+/* Definitions of values in usbReqtype. Used in sorting out setup actions */
+#define H_TO_D 0x00
+#define D_TO_H 0x80
+#define VENDOR 0x40
+#define DEVREQ 0x00
+#define INTREQ 0x01
+#define ENDREQ 0x02
+
+/* Definition of values in usbRequest, again used to sort out setup */
+#define GET_STATUS 0x00
+#define CLEAR_FEATURE 0x01
+#define SET_FEATURE 0x03
+#define SET_ADDRESS 0x05
+#define GET_DESC 0x06
+#define SET_DESC 0x07
+#define GET_CONF 0x08
+#define SET_CONF 0x09
+#define GET_INTERFACE 0x0a
+#define SET_INTERFACE 0x0b
+#define SYNCH_FRAME 0x0c
+
+/* Definitions of the various debug command codes understood by the 1401. These */
+/* are used in various vendor-specific commands to achieve the desired effect */
+#define DB_GRAB 0x50 /* Grab is a NOP for USB */
+#define DB_FREE 0x51 /* Free is a NOP for the USB */
+#define DB_SETADD 0x52 /* Set debug address (double) */
+#define DB_SELFTEST 0x53 /* Start self test */
+#define DB_SETMASK 0x54 /* Set enable mask (double) */
+#define DB_SETDEF 0x55 /* Set default mask (double) */
+#define DB_PEEK 0x56 /* Peek address, save result */
+#define DB_POKE 0x57 /* Poke address with data (double) */
+#define DB_RAMPD 0x58 /* Ramp data at debug address */
+#define DB_RAMPA 0x59 /* Ramp address bus */
+#define DB_REPEATS 0x5A /* Set repeats for operations (double) */
+#define DB_WIDTH 0x5B /* Set width for operations (byte) */
+#define DB_DATA 0x5C /* Get 4-byte data read by PEEK */
+#define DB_CHARS 0x5D /* Send chars via EP0 control write */
+
+#define CR_CHAR 0x0D /* The carriage return character */
+#define CR_CHAR_80 0x8d /* and with bit 7 set */
+
+/* A structure holding information about a block of memory for use in circular transfers */
+typedef struct circBlk {
+ volatile UINT dwOffset; /* Offset within area of block start */
+ volatile UINT dwSize; /* Size of the block, in bytes (0 = unused) */
+} CIRCBLK;
+
+/* A structure holding all of the information about a transfer area - an area of */
+/* memory set up for use either as a source or destination in DMA transfers. */
+typedef struct transarea {
+ void *lpvBuff; /* User address of xfer area saved for completeness */
+ UINT dwBaseOffset; /* offset to start of xfer area in first page */
+ UINT dwLength; /* Length of xfer area, in bytes */
+ struct page **pPages; /* Points at array of locked down pages */
+ int nPages; /* number of pages that are locked down */
+ bool bUsed; /* Is this structure in use? */
+ bool bCircular; /* Is this area for circular transfers? */
+ bool bCircToHost; /* Flag for direction of circular transfer */
+ bool bEventToHost; /* Set event on transfer to host? */
+ int iWakeUp; /* Set 1 on event, cleared by TestEvent() */
+ UINT dwEventSt; /* Defines section within xfer area for... */
+ UINT dwEventSz; /* ...notification by the event SZ is 0 if unset */
+ CIRCBLK aBlocks[2]; /* Info on a pair of circular blocks */
+ wait_queue_head_t wqEvent; /* The wait queue for events in this area MUST BE LAST */
+} TRANSAREA;
+
+/* The DMADESC structure is used to hold information on the transfer in progress. It */
+/* is set up by ReadDMAInfo, using information sent by the 1401 in an escape sequence. */
+typedef struct dmadesc {
+ unsigned short wTransType; /* transfer type as TM_xxx above */
+ unsigned short wIdent; /* identifier word */
+ unsigned int dwSize; /* bytes to transfer */
+ unsigned int dwOffset; /* offset into transfer area for trans */
+ bool bOutWard; /* true when data is going TO 1401 */
+} DMADESC;
+
+#define INBUF_SZ 256 /* input buffer size */
+#define OUTBUF_SZ 256 /* output buffer size */
+#define STAGED_SZ 0x10000 /* size of coherent buffer for staged transfers */
+
+/* Structure to hold all of our device specific stuff. We are making this as similar as we */
+/* can to the Windows driver to help in our understanding of what is going on. */
+typedef struct _DEVICE_EXTENSION {
+ char inputBuffer[INBUF_SZ]; /* The two buffers */
+ char outputBuffer[OUTBUF_SZ]; /* accessed by the host functions */
+ volatile unsigned int dwNumInput; /* num of chars in input buffer */
+ volatile unsigned int dwInBuffGet; /* where to get from input buffer */
+ volatile unsigned int dwInBuffPut; /* where to put into input buffer */
+ volatile unsigned int dwNumOutput; /* num of chars in output buffer */
+ volatile unsigned int dwOutBuffGet; /* where to get from output buffer*/
+ volatile unsigned int dwOutBuffPut; /* where to put into output buffer*/
+
+ volatile bool bSendCharsPending; /* Flag to indicate sendchar active */
+ volatile bool bReadCharsPending; /* Flag to indicate a read is primed */
+ char *pCoherCharOut; /* special aligned buffer for chars to 1401 */
+ struct urb *pUrbCharOut; /* urb used for chars to 1401 */
+ char *pCoherCharIn; /* special aligned buffer for chars to host */
+ struct urb *pUrbCharIn; /* urb used for chars to host */
+
+ spinlock_t charOutLock; /* to protect the outputBuffer and outputting */
+ spinlock_t charInLock; /* to protect the inputBuffer and char reads */
+ __u8 bInterval; /* Interrupt end point interval */
+
+ volatile unsigned int dwDMAFlag; /* state of DMA */
+ TRANSAREA rTransDef[MAX_TRANSAREAS];/* transfer area info */
+ volatile DMADESC rDMAInfo; /* info on current DMA transfer */
+ volatile bool bXFerWaiting; /* Flag set if DMA transfer stalled */
+ volatile bool bInDrawDown; /* Flag that we want to halt transfers */
+
+ /* Parameters relating to a block read\write that is in progress. Some of these values */
+ /* are equivalent to values in rDMAInfo. The values here are those in use, while those */
+ /* in rDMAInfo are those received from the 1401 via an escape sequence. If another */
+ /* escape sequence arrives before the previous xfer ends, rDMAInfo values are updated while these */
+ /* are used to finish off the current transfer. */
+ volatile short StagedId; /* The transfer area id for this transfer */
+ volatile bool StagedRead; /* Flag TRUE for read from 1401, FALSE for write */
+ volatile unsigned int StagedLength; /* Total length of this transfer */
+ volatile unsigned int StagedOffset; /* Offset within memory area for transfer start */
+ volatile unsigned int StagedDone; /* Bytes transferred so far */
+ volatile bool bStagedUrbPending; /* Flag to indicate active */
+ char *pCoherStagedIO; /* buffer used for block transfers */
+ struct urb *pStagedUrb; /* The URB to use */
+ spinlock_t stagedLock; /* protects ReadWriteMem() and circular buffer stuff */
+
+ short s1401Type; /* type of 1401 attached */
+ short sCurrentState; /* current error state */
+ bool bIsUSB2; /* type of the interface we connect to */
+ bool bForceReset; /* Flag to make sure we get a real reset */
+ __u32 statBuf[2]; /* buffer for 1401 state info */
+
+ unsigned long ulSelfTestTime; /* used to timeout self test */
+
+ int nPipes; /* Should be 3 or 4 depending on 1401 usb chip */
+ int bPipeError[4]; /* set non-zero if an error on one of the pipe */
+ __u8 epAddr[4]; /* addresses of the 3/4 end points */
+
+ struct usb_device *udev; /* the usb device for this device */
+ struct usb_interface *interface; /* the interface for this device, NULL if removed */
+ struct usb_anchor submitted; /* in case we need to retract our submissions */
+ struct mutex io_mutex; /* synchronize I/O with disconnect, one user-mode caller at a time */
+
+ int errors; /* the last request tanked */
+ int open_count; /* count the number of openers */
+ spinlock_t err_lock; /* lock for errors */
+ struct kref kref;
+} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
+#define to_DEVICE_EXTENSION(d) container_of(d, DEVICE_EXTENSION, kref)
+
+/* Definitions of routimes used between compilation object files */
+/* in usb1401.c */
+extern int Allowi(DEVICE_EXTENSION *pdx);
+extern int SendChars(DEVICE_EXTENSION *pdx);
+extern void ced_draw_down(DEVICE_EXTENSION *pdx);
+extern int ReadWriteMem(DEVICE_EXTENSION *pdx, bool Read, unsigned short wIdent,
+ unsigned int dwOffs, unsigned int dwLen);
+
+/* in ced_ioc.c */
+extern int ClearArea(DEVICE_EXTENSION *pdx, int nArea);
+extern int SendString(DEVICE_EXTENSION *pdx, const char __user *pData, unsigned int n);
+extern int SendChar(DEVICE_EXTENSION *pdx, char c);
+extern int Get1401State(DEVICE_EXTENSION *pdx, __u32 *state, __u32 *error);
+extern int ReadWrite_Cancel(DEVICE_EXTENSION *pdx);
+extern bool Is1401(DEVICE_EXTENSION *pdx);
+extern bool QuickCheck(DEVICE_EXTENSION *pdx, bool bTestBuff, bool bCanReset);
+extern int Reset1401(DEVICE_EXTENSION *pdx);
+extern int GetChar(DEVICE_EXTENSION *pdx);
+extern int GetString(DEVICE_EXTENSION *pdx, char __user *pUser, int n);
+extern int SetTransfer(DEVICE_EXTENSION *pdx, struct transfer_area_desc __user *pTD);
+extern int UnsetTransfer(DEVICE_EXTENSION *pdx, int nArea);
+extern int SetEvent(DEVICE_EXTENSION *pdx, struct transfer_event __user *pTE);
+extern int Stat1401(DEVICE_EXTENSION *pdx);
+extern int LineCount(DEVICE_EXTENSION *pdx);
+extern int GetOutBufSpace(DEVICE_EXTENSION *pdx);
+extern int GetTransfer(DEVICE_EXTENSION *pdx, TGET_TX_BLOCK __user *pGTB);
+extern int KillIO1401(DEVICE_EXTENSION *pdx);
+extern int BlkTransState(DEVICE_EXTENSION *pdx);
+extern int StateOf1401(DEVICE_EXTENSION *pdx);
+extern int StartSelfTest(DEVICE_EXTENSION *pdx);
+extern int CheckSelfTest(DEVICE_EXTENSION *pdx, TGET_SELFTEST __user *pGST);
+extern int TypeOf1401(DEVICE_EXTENSION *pdx);
+extern int TransferFlags(DEVICE_EXTENSION *pdx);
+extern int DbgPeek(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgPoke(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgRampData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgRampAddr(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgGetData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgStopLoop(DEVICE_EXTENSION *pdx);
+extern int SetCircular(DEVICE_EXTENSION *pdx, struct transfer_area_desc __user *pTD);
+extern int GetCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user *pCB);
+extern int FreeCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user *pCB);
+extern int WaitEvent(DEVICE_EXTENSION *pdx, int nArea, int msTimeOut);
+extern int TestEvent(DEVICE_EXTENSION *pdx, int nArea);
+#endif
diff --git a/drivers/staging/ced1401/use1401.h b/drivers/staging/ced1401/use1401.h
new file mode 100644
index 00000000000..b7997c9835c
--- /dev/null
+++ b/drivers/staging/ced1401/use1401.h
@@ -0,0 +1,288 @@
+/****************************************************************************
+** use1401.h
+** Copyright (C) Cambridge Electronic Design Ltd, 1992-2010
+** Authors: Paul Cox, Tim Bergel, Greg Smith
+** See CVS for revisions.
+**
+** Because the size of a long is different between 32-bit and 64-bit on some
+** systems, we avoid this in this interface.
+****************************************************************************/
+#ifndef __USE1401_H__
+#define __USE1401_H__
+#include "machine.h"
+
+/* Some definitions to make things compatible. If you want to use Use1401 directly */
+/* from a Windows program you should define U14_NOT_DLL, in which case you also */
+/* MUST make sure that your application startup code calls U14InitLib(). */
+/* DLL_USE1401 is defined when you are building the Use1401 dll, not otherwise. */
+#ifdef _IS_WINDOWS_
+#ifndef U14_NOT_DLL
+#ifdef DLL_USE1401
+#define U14API(retType) (retType DllExport __stdcall)
+#else
+#define U14API(retType) (retType DllImport __stdcall)
+#endif
+#endif
+
+#define U14ERRBASE -500
+#define U14LONG long
+#endif
+
+#ifdef LINUX
+#define U14ERRBASE -1000
+#define U14LONG int
+#endif
+
+#ifdef _QT
+#ifndef U14_NOT_DLL
+#undef U14API
+#define U14API(retType) (retType __declspec(dllimport) __stdcall)
+#endif
+#undef U14LONG
+#define U14LONG int
+#endif
+
+#ifndef U14API
+#define U14API(retType) retType
+#endif
+
+#ifndef U14LONG
+#define U14LONG long
+#endif
+
+/* Error codes: We need them here as user space can see them. */
+#define U14ERR_NOERROR 0 /* no problems */
+
+/* Device error codes, but these don't need to be extended - a succession is assumed */
+#define U14ERR_STD 4 /* standard 1401 connected */
+#define U14ERR_U1401 5 /* u1401 connected */
+#define U14ERR_PLUS 6 /* 1401 plus connected */
+#define U14ERR_POWER 7 /* Power1401 connected */
+#define U14ERR_U14012 8 /* u1401 mkII connected */
+#define U14ERR_POWER2 9
+#define U14ERR_U14013 10
+#define U14ERR_POWER3 11
+
+/* NBNB Error numbers need shifting as some linux error codes start at 512 */
+#define U14ERR(n) (n+U14ERRBASE)
+#define U14ERR_OFF U14ERR(0) /* 1401 there but switched off */
+#define U14ERR_NC U14ERR(-1) /* 1401 not connected */
+#define U14ERR_ILL U14ERR(-2) /* if present it is ill */
+#define U14ERR_NOIF U14ERR(-3) /* I/F card missing */
+#define U14ERR_TIME U14ERR(-4) /* 1401 failed to come ready */
+#define U14ERR_BADSW U14ERR(-5) /* I/F card bad switches */
+#define U14ERR_PTIME U14ERR(-6) /* 1401plus failed to come ready */
+#define U14ERR_NOINT U14ERR(-7) /* couldn't grab the int vector */
+#define U14ERR_INUSE U14ERR(-8) /* 1401 is already in use */
+#define U14ERR_NODMA U14ERR(-9) /* couldn't get DMA channel */
+#define U14ERR_BADHAND U14ERR(-10) /* handle provided was bad */
+#define U14ERR_BAD1401NUM U14ERR(-11) /* 1401 number provided was bad */
+
+#define U14ERR_NO_SUCH_FN U14ERR(-20) /* no such function */
+#define U14ERR_NO_SUCH_SUBFN U14ERR(-21) /* no such sub function */
+#define U14ERR_NOOUT U14ERR(-22) /* no room in output buffer */
+#define U14ERR_NOIN U14ERR(-23) /* no input in buffer */
+#define U14ERR_STRLEN U14ERR(-24) /* string longer than buffer */
+#define U14ERR_ERR_STRLEN U14ERR(-24) /* string longer than buffer */
+#define U14ERR_LOCKFAIL U14ERR(-25) /* failed to lock memory */
+#define U14ERR_UNLOCKFAIL U14ERR(-26) /* failed to unlock memory */
+#define U14ERR_ALREADYSET U14ERR(-27) /* area already set up */
+#define U14ERR_NOTSET U14ERR(-28) /* area not set up */
+#define U14ERR_BADAREA U14ERR(-29) /* illegal area number */
+#define U14ERR_FAIL U14ERR(-30) /* we failed for some other reason*/
+
+#define U14ERR_NOFILE U14ERR(-40) /* command file not found */
+#define U14ERR_READERR U14ERR(-41) /* error reading command file */
+#define U14ERR_UNKNOWN U14ERR(-42) /* unknown command */
+#define U14ERR_HOSTSPACE U14ERR(-43) /* not enough host space to load */
+#define U14ERR_LOCKERR U14ERR(-44) /* could not lock resource/command*/
+#define U14ERR_CLOADERR U14ERR(-45) /* CLOAD command failed */
+
+#define U14ERR_TOXXXERR U14ERR(-60) /* tohost/1401 failed */
+#define U14ERR_NO386ENH U14ERR(-80) /* not 386 enhanced mode */
+#define U14ERR_NO1401DRIV U14ERR(-81) /* no device driver */
+#define U14ERR_DRIVTOOOLD U14ERR(-82) /* device driver too old */
+
+#define U14ERR_TIMEOUT U14ERR(-90) /* timeout occurred */
+
+#define U14ERR_BUFF_SMALL U14ERR(-100) /* buffer for getstring too small */
+#define U14ERR_CBALREADY U14ERR(-101) /* there is already a callback */
+#define U14ERR_BADDEREG U14ERR(-102) /* bad parameter to deregcallback */
+#define U14ERR_NOMEMORY U14ERR(-103) /* no memory for allocation */
+
+#define U14ERR_DRIVCOMMS U14ERR(-110) /* failed talking to driver */
+#define U14ERR_OUTOFMEMORY U14ERR(-111) /* needed memory and couldnt get it*/
+
+/* / 1401 type codes. */
+#define U14TYPE1401 0 /* standard 1401 */
+#define U14TYPEPLUS 1 /* 1401 plus */
+#define U14TYPEU1401 2 /* u1401 */
+#define U14TYPEPOWER 3 /* power1401 */
+#define U14TYPEU14012 4 /* u1401 mk II */
+#define U14TYPEPOWER2 5 /* power1401 mk II */
+#define U14TYPEU14013 6 /* u1401-3 */
+#define U14TYPEPOWER3 7 /* power1401-3 */
+#define U14TYPEUNKNOWN -1 /* dont know */
+
+/* Transfer flags to allow driver capabilities to be interrogated */
+
+/* Constants for transfer flags */
+#define U14TF_USEDMA 1 /* Transfer flag for use DMA */
+#define U14TF_MULTIA 2 /* Transfer flag for multi areas */
+#define U14TF_FIFO 4 /* for FIFO interface card */
+#define U14TF_USB2 8 /* for USB2 interface and 1401 */
+#define U14TF_NOTIFY 16 /* for event notifications */
+#define U14TF_SHORT 32 /* for PCI can short cycle */
+#define U14TF_PCI2 64 /* for new PCI card 1401-70 */
+#define U14TF_CIRCTH 128 /* Circular-mode to host */
+#define U14TF_DIAG 256 /* Diagnostics/debug functions */
+#define U14TF_CIRC14 512 /* Circular-mode to 1401 */
+
+/* Definitions of element sizes for DMA transfers - to allow byte-swapping */
+#define ESZBYTES 0 /* BYTE element size value */
+#define ESZWORDS 1 /* unsigned short element size value */
+#define ESZLONGS 2 /* long element size value */
+#define ESZUNKNOWN 0 /* unknown element size value */
+
+/* These define required access types for the debug/diagnostics function */
+#define BYTE_SIZE 1 /* 8-bit access */
+#define WORD_SIZE 2 /* 16-bit access */
+#define LONG_SIZE 3 /* 32-bit access */
+
+/* Stuff used by U14_GetTransfer */
+#define GET_TX_MAXENTRIES 257 /* (max length / page size + 1) */
+
+#ifdef _IS_WINDOWS_
+#pragma pack(1)
+
+typedef struct /* used for U14_GetTransfer results */
+{ /* Info on a single mapped block */
+ U14LONG physical;
+ U14LONG size;
+} TXENTRY;
+
+typedef struct TGetTxBlock /* used for U14_GetTransfer results */
+{ /* matches structure in VXD */
+ U14LONG size;
+ U14LONG linear;
+ short seg;
+ short reserved;
+ short avail; /* number of available entries */
+ short used; /* number of used entries */
+ TXENTRY entries[GET_TX_MAXENTRIES]; /* Array of mapped block info */
+} TGET_TX_BLOCK;
+
+typedef TGET_TX_BLOCK *LPGET_TX_BLOCK;
+
+#pragma pack()
+#endif
+
+#ifdef LINUX
+typedef struct /* used for U14_GetTransfer results */
+{ /* Info on a single mapped block */
+ long long physical;
+ long size;
+} TXENTRY;
+
+typedef struct TGetTxBlock /* used for U14_GetTransfer results */
+{ /* matches structure in VXD */
+ long long linear; /* linear address */
+ long size; /* total size of the mapped area, holds id when called */
+ short seg; /* segment of the address for Win16 */
+ short reserved;
+ short avail; /* number of available entries */
+ short used; /* number of used entries */
+ TXENTRY entries[GET_TX_MAXENTRIES]; /* Array of mapped block info */
+} TGET_TX_BLOCK;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+U14API(int) U14WhenToTimeOut(short hand); /* when to timeout in ms */
+U14API(short) U14PassedTime(int iTime); /* non-zero if iTime passed */
+
+U14API(short) U14LastErrCode(short hand);
+
+U14API(short) U14Open1401(short n1401);
+U14API(short) U14Close1401(short hand);
+U14API(short) U14Reset1401(short hand);
+U14API(short) U14ForceReset(short hand);
+U14API(short) U14TypeOf1401(short hand);
+U14API(short) U14NameOf1401(short hand, char *pBuf, unsigned short wMax);
+
+U14API(short) U14Stat1401(short hand);
+U14API(short) U14CharCount(short hand);
+U14API(short) U14LineCount(short hand);
+
+U14API(short) U14SendString(short hand, const char *pString);
+U14API(short) U14GetString(short hand, char *pBuffer, unsigned short wMaxLen);
+U14API(short) U14SendChar(short hand, char cChar);
+U14API(short) U14GetChar(short hand, char *pcChar);
+
+U14API(short) U14LdCmd(short hand, const char *command);
+U14API(unsigned int) U14Ld(short hand, const char *vl, const char *str);
+
+U14API(short) U14SetTransArea(short hand, unsigned short wArea, void *pvBuff,
+ unsigned int dwLength, short eSz);
+U14API(short) U14UnSetTransfer(short hand, unsigned short wArea);
+U14API(short) U14SetTransferEvent(short hand, unsigned short wArea, BOOL bEvent,
+ BOOL bToHost, unsigned int dwStart, unsigned int dwLength);
+U14API(int) U14TestTransferEvent(short hand, unsigned short wArea);
+U14API(int) U14WaitTransferEvent(short hand, unsigned short wArea, int msTimeOut);
+U14API(short) U14GetTransfer(short hand, TGET_TX_BLOCK *pTransBlock);
+
+U14API(short) U14ToHost(short hand, char *pAddrHost, unsigned int dwSize, unsigned int dw1401,
+ short eSz);
+U14API(short) U14To1401(short hand, const char *pAddrHost, unsigned int dwSize, unsigned int dw1401,
+ short eSz);
+
+U14API(short) U14SetCircular(short hand, unsigned short wArea, BOOL bToHost, void *pvBuff,
+ unsigned int dwLength);
+
+U14API(int) U14GetCircBlk(short hand, unsigned short wArea, unsigned int *pdwOffs);
+U14API(int) U14FreeCircBlk(short hand, unsigned short wArea, unsigned int dwOffs, unsigned int dwSize,
+ unsigned int *pdwOffs);
+
+U14API(short) U14StrToLongs(const char *pszBuff, U14LONG *palNums, short sMaxLongs);
+U14API(short) U14LongsFrom1401(short hand, U14LONG *palBuff, short sMaxLongs);
+
+U14API(void) U14SetTimeout(short hand, int lTimeout);
+U14API(int) U14GetTimeout(short hand);
+U14API(short) U14OutBufSpace(short hand);
+U14API(int) U14BaseAddr1401(short hand);
+U14API(int) U14DriverVersion(short hand);
+U14API(int) U14DriverType(short hand);
+U14API(short) U14DriverName(short hand, char *pBuf, unsigned short wMax);
+U14API(short) U14GetUserMemorySize(short hand, unsigned int *pMemorySize);
+U14API(short) U14KillIO1401(short hand);
+
+U14API(short) U14BlkTransState(short hand);
+U14API(short) U14StateOf1401(short hand);
+
+U14API(short) U14Grab1401(short hand);
+U14API(short) U14Free1401(short hand);
+U14API(short) U14Peek1401(short hand, unsigned int dwAddr, int nSize, int nRepeats);
+U14API(short) U14Poke1401(short hand, unsigned int dwAddr, unsigned int dwValue, int nSize, int nRepeats);
+U14API(short) U14Ramp1401(short hand, unsigned int dwAddr, unsigned int dwDef, unsigned int dwEnable, int nSize, int nRepeats);
+U14API(short) U14RampAddr(short hand, unsigned int dwDef, unsigned int dwEnable, int nSize, int nRepeats);
+U14API(short) U14StopDebugLoop(short hand);
+U14API(short) U14GetDebugData(short hand, U14LONG *plValue);
+
+U14API(short) U14StartSelfTest(short hand);
+U14API(short) U14CheckSelfTest(short hand, U14LONG *pData);
+U14API(short) U14TransferFlags(short hand);
+U14API(void) U14GetErrorString(short nErr, char *pStr, unsigned short wMax);
+U14API(int) U14MonitorRev(short hand);
+U14API(void) U14CloseAll(void);
+
+U14API(short) U14WorkingSet(unsigned int dwMinKb, unsigned int dwMaxKb);
+U14API(int) U14InitLib(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* End of ifndef __USE1401_H__ */
+
diff --git a/drivers/staging/ced1401/use14_ioc.h b/drivers/staging/ced1401/use14_ioc.h
new file mode 100644
index 00000000000..42d2e4e6e9a
--- /dev/null
+++ b/drivers/staging/ced1401/use14_ioc.h
@@ -0,0 +1,299 @@
+/* use14_ioc.h
+** definitions of use1401 module stuff that is shared between use1401 and the driver.
+** Copyright (C) Cambridge Electronic Design Limited 2010
+** Author Greg P Smith
+************************************************************************************/
+#ifndef __USE14_IOC_H__
+#define __USE14_IOC_H__
+
+#define MAX_TRANSAREAS 8 /* The number of transfer areas supported by driver */
+
+#define i386
+#include "winioctl.h" /* needed so we can access driver */
+
+/*
+** Defines for IOCTL functions to ask driver to perform. These must be matched
+** in both use1401 and in the driver. The IOCTL code contains a command
+** identifier, plus other information about the device, the type of access
+** with which the file must have been opened, and the type of buffering.
+** The IOCTL function codes from 0x80 to 0xFF are for developer use.
+*/
+#define FILE_DEVICE_CED1401 0x8001
+ FNNUMBASE 0x800
+
+#define U14_OPEN1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_CLOSE1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+1, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SENDSTRING CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+2, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_RESET1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+3, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETCHAR CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+4, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SENDCHAR CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+5, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STAT1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+6, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_LINECOUNT CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+7, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETSTRING CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+8, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_REGCALLBACK CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+9, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETMONITORBUF CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+10, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SETTRANSFER CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+11, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_UNSETTRANSFER CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+12, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SETTRANSEVENT CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+13, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETOUTBUFSPACE CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+14, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETBASEADDRESS CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+15, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETDRIVERREVISION CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+16, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETTRANSFER CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+17, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_KILLIO1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+18, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_BLKTRANSSTATE CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+19, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_BYTECOUNT CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+20, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_ZEROBLOCKCOUNT CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+21, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STOPCIRCULAR CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+22, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STATEOF1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+23, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_REGISTERS1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+24, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GRAB1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+25, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_FREE1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+26, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STEP1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+27, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SET1401REGISTERS CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+28, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STEPTILL1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+29, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SETORIN CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+30, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_STARTSELFTEST CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+31, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_CHECKSELFTEST CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+32, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_TYPEOF1401 CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+33, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_TRANSFERFLAGS CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+34, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGPEEK CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+35, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGPOKE CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+36, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGRAMPDATA CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+37, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGRAMPADDR CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+38, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGGETDATA CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+39, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_DBGSTOPLOOP CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+40, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_FULLRESET CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+41, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_SETCIRCULAR CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+42, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_GETCIRCBLK CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+43, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+#define U14_FREECIRCBLK CTL_CODE(FILE_DEVICE_CED1401, \
+ FNNUMBASE+44, \
+ METHOD_BUFFERED, \
+ FILE_ANY_ACCESS)
+
+/*--------------- Structures that are shared with the driver ------------- */
+#pragma pack(1)
+
+typedef struct /* used for get/set standard 1401 registers */
+{
+ short sPC;
+ char A;
+ char X;
+ char Y;
+ char stat;
+ char rubbish;
+} T1401REGISTERS;
+
+typedef union /* to communicate with 1401 driver status & control funcs */
+{
+ char chrs[22];
+ short ints[11];
+ long longs[5];
+ T1401REGISTERS registers;
+} TCSBLOCK;
+
+typedef TCSBLOCK* LPTCSBLOCK;
+
+typedef struct paramBlk {
+ short sState;
+ TCSBLOCK csBlock;
+} PARAMBLK;
+
+typedef PARAMBLK* PPARAMBLK;
+
+struct transfer_area_desc /* Structure and type for SetTransArea */
+{
+ unsigned short wArea; /* number of transfer area to set up */
+ void FAR *lpvBuff; /* address of transfer area */
+ unsigned int dwLength; /* length of area to set up */
+ short eSize; /* size to move (for swapping on MAC) */
+};
+
+
+/* This is the structure used to set up a transfer area */
+typedef struct VXTransferDesc /* use1401.c and use1432x.x use only */
+{
+ unsigned short wArea; /* number of transfer area to set up */
+ unsigned short wAddrSel; /* 16 bit selector for area */
+ unsigned int dwAddrOfs; /* 32 bit offset for area start */
+ unsigned int dwLength; /* length of area to set up */
+} VXTRANSFERDESC;
+
+#pragma pack()
+
+#endif
diff --git a/drivers/staging/ced1401/userspace/use1401.c b/drivers/staging/ced1401/userspace/use1401.c
new file mode 100644
index 00000000000..7b8a2227fe5
--- /dev/null
+++ b/drivers/staging/ced1401/userspace/use1401.c
@@ -0,0 +1,3035 @@
+/****************************************************************************
+** use1401.c
+** Copyright (C) Cambridge Electronic Design Ltd, 1992-2010
+**
+** This program is free software; you can redistribute 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.
+**
+** Contact CED: Cambridge Electronic Design Limited, Science Park, Milton Road
+** Cambridge, CB6 0FE.
+** www.ced.co.uk
+** greg@ced.co.uk
+**
+** Title: USE1401.C
+** Version: 4.00
+** Author: Paul Cox, Tim Bergel, Greg Smith
+**
+** The code was vigorously pruned in DEC 2010 to remove the macintosh options
+** and to get rid of the 16-bit support. It has also been aligned with the
+** Linux version. See CVS for revisions. This will work for Win 9x onwards.
+****************************************************************************
+**
+** Notes on Windows interface to driver
+** ************************************
+**
+** Under Windows 9x and NT, Use1401 uses DeviceIoControl to get access to
+** the 1401 driver. This has parameters for the device handle, the function
+** code, an input pointer and byte count, an output pointer and byte count
+** and a pointer to a unsigned int to hold the output byte count. Note that input
+** and output are from the point-of-view of the driver, so the output stuff
+** is used to read values from the 1401, not send to the 1401. The use of
+** these parameters varies with the function in use and the operating
+** system; there are five separate DIOC calls SendString, GetString and
+** SetTransferArea all have their own specialised calls, the rest use the
+** Status1401 or Control1401 functions.
+**
+** There are two basic styles of DIOC call used, one for Win9x VxD drivers
+** and one for NT Kernel-mode and WDM drivers (see below for tables showing
+** the different parameters used. The array bUseNTDIOC[] selects between
+** these two calling styles.
+**
+** Function codes
+** In Win3.x, simple function codes from 0 to 40 were used, shifted left 8
+** bits with a sub-function code in the lower 8 bits. These were also used
+** in the Windows 95 driver, though we had to add 1 to the code value to
+** avoid problems (Open from CreateFile is zero), and the sub-function code
+** is now unused. We found that this gave some problems with Windows 98
+** as the function code values are reserved by microsoft, so we switched to
+** using the NT function codes instead. The NT codes are generated using the
+** CTL_CODE macro, essentially this gives 0x80012000 | (func << 2), where
+** func is the original 0 to 34 value. The driver will handle both types of
+** code and Use1432 only uses the NT codes if it knows the driver is new
+** enough. The array bUseNTCodes[] holds flags on the type of codes required.
+** GPS/TDB Dec 2010: we removed the bUseNTCodes array as this is always true
+** as we no longer support ancient versions.
+**
+** The CreateFile and CloseFile function calls are also handled
+** by DIOC, using the special function codes 0 and -1 respectively.
+**
+** Input pointer and buffer size
+** These are intended for data sent to the device driver. In nearly all cases
+** they are unused in calls to the Win95 driver, the NT driver uses them
+** for all information sent to the driver. The table below shows the pointer
+** and byte count used for the various calls:
+**
+** Win 95 Win NT
+** SendString NULL, 0 pStr, nStr
+** GetString NULL, 0 NULL, 0
+** SetTransferArea pBuf, nBuf (unused?) pDesc, nDesc
+** GetTransfer NULL, 0 NULL, 0
+** Status1401 NULL, 0 NULL, 0
+** Control1401 NULL, 0 pBlk, nBlk
+**
+** pStr and nStr are pointers to a char buffer and the buffer length for
+** string I/O, note that these are temporary buffers owned by the DLL, not
+** application memory, pBuf and nBuf are the transfer area buffer (I think
+** these are unused), pDesc and nDesc are the TRANSFERDESC structure, pBlk
+** and nBlk are the TCSBLOCK structure.
+**
+**
+** Output pointer and buffer size
+** These are intended for data read from the device driver. These are used
+** for almost all information sent to the Win95 driver, the NT driver uses
+** them for information read from the driver, chiefly the error code. The
+** table below shows the pointer and byte count used for the various calls:
+**
+** Win 95 Win NT
+** SendString pStr, nStr pPar, nPar
+** GetString pStr, nStr+2 pStr, nStr+2
+** SetTransferArea pDesc, nDesc pPar, nPar
+** GetTransfer pGet, nGet pGet, nGet
+** Status1401 pBlk, nBlk pPar, nPar
+** Control1401 pBlk, nBlk pPar, nPar
+**
+** pStr and nStr are pointers to a char buffer and the buffer length for
+** string I/O, the +2 for GetString refers to two spare bytes at the start
+** used to hold the string length and returning an error code for NT. Note
+** again that these are (and must be) DLL-owned temporary buffers. pPar
+** and nPar are a PARAM structure used in NT (it holds an error code and a
+** TCSBLOCK structure). pDesc and nDesc are the VXTRANSFERDESC structure,
+** pBlk and nBlk are the TCSBLOCK structure. pGet and nGet indicate the
+** TGET_TX_BLOCK structure used for GetTransfer.
+**
+**
+** The output byte count
+** Both drivers return the output buffer size here, regardless of the actual
+** bytes output. This is used to check that we did get through to the driver.
+**
+** Multiple 1401s
+** **************
+**
+** We have code that tries to support the use of multiple 1401s, but there
+** are problems: The lDriverVersion and lDriverType variables are global, not
+** per-1401 (a particular problem as the U14 functions that use them don't
+** have a hand parameter). In addition, the mechansim for finding a free
+** 1401 depends upon the 1401 device driver open operation failing if it's
+** already in use, which doesn't always happen, particularly with the VxDs.
+** The code in TryToOpen tries to fix this by relying on TYPEOF1401 to detect
+** the 1401-in-use state - the VxDs contain special code to help this. This is
+** working OK but multiple 1401 support works better with the Win2000 drivers.
+**
+** USB driver
+** **********
+**
+** The USB driver, which runs on both Win98 and NT2000, uses the NT-style
+** calling convention, both for the DIOC codes and the DIOC parameters. The
+** TryToOpen function has been altered to look for an NT driver first in
+** the appropriate circumstances, and to set the driver DIOC flags up in
+** the correct state.
+**
+** Adding a new 1401 type - now almost nothing to do
+** *************************************************
+**
+** The 1401 types are defined by a set of U14TYPExxxx codes in USE1401.H.
+** You should add a new one of these to keep things tidy for applications.
+**
+** DRIVERET_MAX (below) specifies the maximum allowed type code from the
+** 1401 driver; I have set this high to accommodate as yet undesigned 1401
+** types. Similarly, as long as the command file names follow the ARM,
+** ARN, ARO sequence, these are calculated by the ExtForType function, so
+** you don't need to do anything here either.
+**
+** Version number
+** **************
+** The new U14InitLib() function returns 0 if the OS is incapable of use,
+** otherwise is returns the version of the USE1401 library. This is done
+** in three parts: Major(31-24).Minor(23-16).Revision.(15-0) (brackets are
+** the bits used). The Major number starts at 2 for the first revision with
+** the U14InitLib() function. Changes to the Major version means that we
+** have broken backwards compatibility. Minor number changes mean that we
+** have added new functionality that does not break backwards compatibility.
+** we starts at 0. Revision changes mean we have fixed something. Each index
+** returns to 0 when a higher one changes.
+*/
+#define U14LIB_MAJOR 4
+#define U14LIB_MINOR 0
+#define U14LIB_REVISION 0
+#define U14LIB_VERSION ((U14LIB_MAJOR<<24) | (U14LIB_MINOR<<16) | U14LIB_REVISION)
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "USE1401.H"
+
+#ifdef _IS_WINDOWS_
+#include <io.h>
+#include <windows.h>
+#pragma warning(disable: 4100) /* Disable "Unused formal parameter" warning */
+#include <assert.h>
+#include "process.h"
+
+
+#define sprintf wsprintf
+#define PATHSEP '\\'
+#define PATHSEPSTR "\\"
+#define DEFCMDPATH "\\1401\\" // default command path if all else fails
+#define MINDRIVERMAJREV 1 // minimum driver revision level we need
+#define __packed // does nothing in Windows
+
+#include "use14_ioc.h" // links to device driver stuff
+#endif
+
+#ifdef LINUX
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sched.h>
+#include <libgen.h>
+#define PATHSEP '/'
+#define PATHSEPSTR "/"
+#define DEFCMDPATH "/var/1401/" // default command path if all else fails
+#define MINDRIVERMAJREV 2 // minimum driver revision level we need
+
+#include "ced_ioctl.h" // links to device driver stuff
+#endif
+
+#define MAX1401 8 // The number of 1401s that can be supported
+
+/*
+** These are the 1401 type codes returned by the driver, they are a slightly
+** odd sequence & start for reasons of compatibility with the DOS driver.
+** The maximum code value is the upper limit of 1401 device types.
+*/
+#define DRIVRET_STD 4 // Codes for 1401 types matching driver values
+#define DRIVRET_U1401 5 // This table does not need extending, as
+#define DRIVRET_PLUS 6 // we can calculate values now.
+#define DRIVRET_POWER 7 // but we need all of these values still
+#define DRIVRET_MAX 26 // Maximum tolerated code - future designs
+
+/*
+** These variables store data that will be used to generate the last
+** error string. For now, a string will hold the 1401 command file name.
+*/
+static char szLastName[20]; // additional text information
+
+/*
+** Information stored per handle. NBNB, driverType and DriverVersion used to be
+** only stored once for all handles... i.e. nonsensical. This change means that
+** three U14...() calls now include handles that were previously void. We have
+** set a constructor and a destructor call for the library (see the end) to
+** initialise important structures, or call use1401_load().
+*/
+static short asDriverType[MAX1401] = {0};
+static int lLastDriverVersion = U14ERR_NO1401DRIV;
+static int lLastDriverType = U14TYPEUNKNOWN;
+static int alDriverVersion[MAX1401]; // version/type of each driver
+static int alTimeOutPeriod[MAX1401]; // timeout time in milliseconds
+static short asLastRetCode[MAX1401]; // last code from a fn call
+static short asType1401[MAX1401] = {0}; // The type of the 1401
+static BOOL abGrabbed[MAX1401] = {0}; // Flag for grabbed, set true by grab1401
+static int iAttached = 0; // counts process attaches so can let go
+
+#ifdef _IS_WINDOWS_
+/****************************************************************************
+** Windows NT Specific Variables and internal types
+****************************************************************************/
+static HANDLE aHand1401[MAX1401] = {0}; // handles for 1401s
+static HANDLE aXferEvent[MAX1401] = {0}; // transfer events for the 1401s
+static LPVOID apAreas[MAX1401][MAX_TRANSAREAS]; // Locked areas
+static unsigned int auAreas[MAX1401][MAX_TRANSAREAS]; // Size of locked areas
+static BOOL bWindows9x = FALSE; // if we are Windows 95 or better
+#ifdef _WIN64
+#define USE_NT_DIOC(ind) TRUE
+#else
+static BOOL abUseNTDIOC[MAX1401]; // Use NT-style DIOC parameters */
+#define USE_NT_DIOC(ind) abUseNTDIOC[ind]
+#endif
+
+#endif
+
+#ifdef LINUX
+static int aHand1401[MAX1401] = {0}; // handles for 1401s
+#define INVALID_HANDLE_VALUE 0 // to avoid code differences
+#endif
+
+
+/*
+** The CmdHead relates to backwards compatibility with ancient Microsoft (and Sperry!)
+** versions of BASIC, where this header was needed so we could load a command into
+** memory.
+*/
+#pragma pack(1) // pack our structure
+typedef struct CmdHead // defines header block on command
+{ // for PC commands
+ char acBasic[5]; // BASIC information - needed to align things
+ unsigned short wBasicSz; // size as seen by BASIC
+ unsigned short wCmdSize; // size of the following info
+} __packed CMDHEAD;
+#pragma pack() // back to normal
+
+/*
+** The rest of the header looks like this...
+** int iRelPnt; relocation pointer... actual start
+** char acName[8]; string holding the command name
+** BYTE bMonRev; monitor revision level
+** BYTE bCmdRev; command revision level
+*/
+
+typedef CMDHEAD *LPCMDHEAD; // pointer to a command header
+
+#define MAXSTRLEN 255 // maximum string length we use
+#define TOHOST FALSE
+#define TO1401 TRUE
+
+static short CheckHandle(short h)
+{
+ if ((h < 0) || (h >= MAX1401)) // must be legal range...
+ return U14ERR_BADHAND;
+ if (aHand1401[h] <= 0) // must be open
+ return U14ERR_BADHAND;
+ return U14ERR_NOERROR;
+}
+
+#ifdef _IS_WINDOWS_
+/****************************************************************************
+** U14Status1401 Used for functions which do not pass any data in but
+** get data back
+****************************************************************************/
+static short U14Status1401(short sHand, LONG lCode, TCSBLOCK* pBlk)
+{
+ unsigned int dwBytes = 0;
+
+ if ((sHand < 0) || (sHand >= MAX1401)) /* Check parameters */
+ return U14ERR_BADHAND;
+#ifndef _WIN64
+ if (!USE_NT_DIOC(sHand))
+ { /* Windows 9x DIOC methods? */
+ if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, pBlk,sizeof(TCSBLOCK),&dwBytes,NULL))
+ return (short)((dwBytes>=sizeof(TCSBLOCK)) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS);
+ else
+ return (short)GetLastError();
+ }
+ else
+#endif
+ { /* Windows NT or USB driver */
+ PARAMBLK rWork;
+ rWork.sState = U14ERR_DRIVCOMMS;
+ if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, &rWork,sizeof(PARAMBLK),&dwBytes,NULL) &&
+ (dwBytes >= sizeof(PARAMBLK)))
+ {
+ *pBlk = rWork.csBlock;
+ return rWork.sState;
+ }
+ }
+
+ return U14ERR_DRIVCOMMS;
+}
+
+/****************************************************************************
+** U14Control1401 Used for functions which pass data in and only expect
+** an error code back
+****************************************************************************/
+static short U14Control1401(short sHand, LONG lCode, TCSBLOCK* pBlk)
+{
+ unsigned int dwBytes = 0;
+
+ if ((sHand < 0) || (sHand >= MAX1401)) /* Check parameters */
+ return U14ERR_BADHAND;
+
+#ifndef _WIN64
+ if (!USE_NT_DIOC(sHand))
+ { /* Windows 9x DIOC methods */
+ if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, pBlk, sizeof(TCSBLOCK), &dwBytes, NULL))
+ return (short)(dwBytes >= sizeof(TCSBLOCK) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS);
+ else
+ return (short)GetLastError();
+ }
+ else
+#endif
+ { /* Windows NT or later */
+ PARAMBLK rWork;
+ rWork.sState = U14ERR_DRIVCOMMS;
+ if (DeviceIoControl(aHand1401[sHand], lCode, pBlk, sizeof(TCSBLOCK), &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+ (dwBytes >= sizeof(PARAMBLK)))
+ return rWork.sState;
+ }
+
+ return U14ERR_DRIVCOMMS;
+}
+#endif
+
+/****************************************************************************
+** SafeTickCount
+** Gets time in approximately units of a millisecond.
+*****************************************************************************/
+static long SafeTickCount()
+{
+#ifdef _IS_WINDOWS_
+ return GetTickCount();
+#endif
+#ifdef LINUX
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (tv.tv_sec*1000 + tv.tv_usec/1000);
+#endif
+}
+
+/****************************************************************************
+** A utility routine to get the command file extension for a given type
+** of 1401. We assume the type code is vaguely legal.
+****************************************************************************/
+static int ExtForType(short sType, char* szExt)
+{
+ szExt[0] = 0; /* Default return is a blank string */
+ switch (sType)
+ {
+ case U14TYPE1401: strcpy(szExt, ".CMD"); break; // Standard 1401
+ case U14TYPEPLUS: strcpy(szExt, ".GXC"); break; // 1401 plus
+ default: // All others are in a predictable sequence
+ strcpy(szExt, ".ARM");
+ szExt[3] = (char)('M' + sType - U14TYPEU1401);
+ if (szExt[3] > 'Z') // Wrap round to ARA after ARZ
+ szExt[3] = (char)(szExt[3] - 26);
+ }
+ return 0;
+}
+
+/****************************************************************************
+** U14WhenToTimeOut
+** Returns the time to time out in time units suitable for the machine
+** we are running on ie millsecs for pc/linux, or Mac/
+****************************************************************************/
+U14API(int) U14WhenToTimeOut(short hand)
+{
+ int iNow = SafeTickCount();
+ if ((hand >= 0) && (hand < MAX1401))
+ iNow += alTimeOutPeriod[hand];
+ return iNow;
+}
+
+/****************************************************************************
+** U14PassedTime
+** Returns non zero if the timed passed in has been passed 0 if not
+****************************************************************************/
+U14API(short) U14PassedTime(int lCheckTime)
+{
+ return (short)((SafeTickCount()-lCheckTime) > 0);
+}
+
+/****************************************************************************
+** TranslateString
+** Tidies up string that U14GetString returns. Converts all the commas in a
+** string to spaces. Removes terminating CR character. May do more in future.
+****************************************************************************/
+static void TranslateString(char* pStr)
+{
+ int i = 0;
+ while (pStr[i])
+ {
+ if (pStr[i] == ',')
+ pStr[i] = ' '; /* convert comma to space */
+ ++i;
+ }
+
+ if ((i > 0) && (pStr[i-1] == '\n')) /* kill terminating LF */
+ pStr[i-1] = (char)0;
+}
+
+/****************************************************************************
+** U14StrToLongs
+** Converts a string to an array of longs and returns the number of values
+****************************************************************************/
+U14API(short) U14StrToLongs(const char* pszBuff, U14LONG *palNums, short sMaxLongs)
+{
+ unsigned short wChInd = 0; // index into source
+ short sLgInd = 0; // index into result longs
+
+ while (pszBuff[wChInd] && // until we get to end of string...
+ (sLgInd < sMaxLongs)) // ...or filled the buffer
+ {
+ // Why not use a C Library converter?
+ switch (pszBuff[wChInd])
+ {
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ BOOL bDone = FALSE; // true at end of number
+ int iSign = 1; // sign of number
+ long lValue = 0;
+
+ while ((!bDone) && pszBuff[wChInd])
+ {
+ switch (pszBuff[wChInd])
+ {
+ case '-':
+ iSign = -1; // swap sign
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ lValue *= 10; // move to next digit base 10
+ lValue += ((int)pszBuff[wChInd]-(int)'0');
+ break;
+
+ default: // end of number
+ bDone = TRUE;
+ break;
+ }
+ wChInd++; // move onto next character
+ }
+ palNums[sLgInd] = lValue * iSign;
+ sLgInd++;
+ }
+ break;
+
+ default:
+ wChInd++; // look at next char
+ break;
+ }
+ }
+ return (sLgInd);
+}
+
+
+/****************************************************************************
+** U14LongsFrom1401
+** Gets the next waiting line from the 1401 and converts it longs
+** Returns the number of numbers read or an error.
+****************************************************************************/
+U14API(short) U14LongsFrom1401(short hand, U14LONG *palBuff, short sMaxLongs)
+{
+ char szWork[MAXSTRLEN];
+ short sResult = U14GetString(hand, szWork, MAXSTRLEN);/* get reply from 1401 */
+ if (sResult == U14ERR_NOERROR) /* if no error convert */
+ sResult = U14StrToLongs(szWork, palBuff, sMaxLongs);
+ return sResult;
+}
+
+/****************************************************************************
+** U14CheckErr
+** Sends the ERR command to the 1401 and gets the result. Returns 0, a
+** negative error code, or the first error value.
+****************************************************************************/
+U14API(short) U14CheckErr(short hand)
+{
+ short sResult = U14SendString(hand, ";ERR;");
+ if (sResult == U14ERR_NOERROR)
+ {
+ U14LONG er[3];
+ sResult = U14LongsFrom1401(hand, er, 3);
+ if (sResult > 0)
+ {
+ sResult = (short)er[0]; /* Either zero or an error value */
+#ifdef _DEBUG
+ if (er[0] != 0)
+ {
+ char szMsg[50];
+ sprintf(szMsg, "U14CheckErr returned %d,%d\n", er[0], er[1]);
+ OutputDebugString(szMsg);
+ }
+#endif
+ }
+ else
+ {
+ if (sResult == 0)
+ sResult = U14ERR_TIMEOUT; /* No numbers equals timeout */
+ }
+ }
+
+ return sResult;
+}
+
+/****************************************************************************
+** U14LastErrCode
+** Returns the last code from the driver. This is for Windows where all calls
+** go through the Control and Status routines, so we can save any error.
+****************************************************************************/
+U14API(short) U14LastErrCode(short hand)
+{
+ if ((hand < 0) || (hand >= MAX1401))
+ return U14ERR_BADHAND;
+ return asLastRetCode[hand];
+}
+
+/****************************************************************************
+** U14SetTimeout
+** Set the timeout period for 1401 comms in milliseconds
+****************************************************************************/
+U14API(void) U14SetTimeout(short hand, int lTimeOut)
+{
+ if ((hand < 0) || (hand >= MAX1401))
+ return;
+ alTimeOutPeriod[hand] = lTimeOut;
+}
+
+/****************************************************************************
+** U14GetTimeout
+** Get the timeout period for 1401 comms in milliseconds
+****************************************************************************/
+U14API(int) U14GetTimeout(short hand)
+{
+ if ((hand < 0) || (hand >= MAX1401))
+ return U14ERR_BADHAND;
+ return alTimeOutPeriod[hand];
+}
+
+/****************************************************************************
+** U14OutBufSpace
+** Return the space in the output buffer, or an error.
+****************************************************************************/
+U14API(short) U14OutBufSpace(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_GETOUTBUFSPACE,&csBlock);
+ if (sErr == U14ERR_NOERROR)
+ sErr = csBlock.ints[0];
+ return sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_GetOutBufSpace(aHand1401[hand]) : sErr;
+#endif
+}
+
+
+/****************************************************************************
+** U14BaseAddr1401
+** Returns the 1401 base address or an error code. Meaningless nowadays
+****************************************************************************/
+U14API(int) U14BaseAddr1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ int iError = U14Status1401(hand, U14_GETBASEADDRESS,&csBlock);
+ if (iError == U14ERR_NOERROR)
+ iError = csBlock.longs[0];
+ return iError;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_GetBaseAddress(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14StateOf1401
+** Return error state, either NOERROR or a negative code.
+****************************************************************************/
+U14API(short) U14StateOf1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_STATEOF1401, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ {
+ sErr = csBlock.ints[0]; // returned 1401 state
+ if ((sErr >= DRIVRET_STD) && (sErr <= DRIVRET_MAX))
+ sErr = U14ERR_NOERROR;
+ }
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ sErr = (short)CED_StateOf1401(aHand1401[hand]);
+ if ((sErr >= DRIVRET_STD) && (sErr <= DRIVRET_MAX))
+ sErr = U14ERR_NOERROR;
+ }
+#endif
+ return sErr;
+}
+
+/****************************************************************************
+** U14DriverVersion
+** Returns the driver version. Hi word is major revision, low word is minor.
+** If you pass in a silly handle (like -1), we return the version of the last
+** driver we know of (to cope with PCI and no 1401 attached).
+****************************************************************************/
+U14API(int) U14DriverVersion(short hand)
+{
+ return CheckHandle(hand) != U14ERR_NOERROR ? lLastDriverVersion : alDriverVersion[hand];
+}
+
+/****************************************************************************
+** U14DriverType
+** Returns the driver type. The type, 0=ISA/NU-Bus, 1=PCI, 2=USB, 3=HSS
+** If you pass in a silly handle (like -1), we return the type of the last
+** driver we know of (to cope with PCI and no 1401 attached).
+****************************************************************************/
+U14API(int) U14DriverType(short hand)
+{
+ return CheckHandle(hand) != U14ERR_NOERROR ? lLastDriverType : asDriverType[hand];
+}
+
+/****************************************************************************
+** U14DriverName
+** Returns the driver type as 3 character (ISA, PCI, USB or HSS))
+****************************************************************************/
+U14API(short) U14DriverName(short hand, char* pBuf, unsigned short wMax)
+{
+ char* pName;
+ *pBuf = 0; // Start off with a blank string
+ switch (U14DriverType(hand)) // Results according to type
+ {
+ case 0: pName = "ISA"; break;
+ case 1: pName = "PCI"; break;
+ case 2: pName = "USB"; break;
+ case 3: pName = "HSS"; break;
+ default: pName = "???"; break;
+ }
+ strncpy(pBuf, pName, wMax); // Copy the correct name to return
+
+ return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** U14BlkTransState
+** Returns 0 no transfer in progress, 1 transfer in progress or an error code
+****************************************************************************/
+U14API(short) U14BlkTransState(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_BLKTRANSSTATE, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ sErr = csBlock.ints[0];
+ return sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_BlkTransState(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14Grab1401
+** Take control of the 1401 for diagnostics purposes. USB does nothing.
+****************************************************************************/
+U14API(short) U14Grab1401(short hand)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+#ifdef _IS_WINDOWS_
+ if (abGrabbed[hand]) // 1401 should not have been grabbed
+ sErr = U14ERR_ALREADYSET; // Error code defined for this
+ else
+ {
+ TCSBLOCK csBlock;
+ sErr = U14Control1401(hand, U14_GRAB1401, &csBlock);
+ }
+#endif
+#ifdef LINUX
+ // 1401 should not have been grabbed
+ sErr = abGrabbed[hand] ? U14ERR_ALREADYSET : CED_Grab1401(aHand1401[hand]);
+#endif
+ if (sErr == U14ERR_NOERROR)
+ abGrabbed[hand] = TRUE;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14Free1401
+****************************************************************************/
+U14API(short) U14Free1401(short hand)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+#ifdef _IS_WINDOWS_
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+ TCSBLOCK csBlock;
+ sErr = U14Control1401(hand, U14_FREE1401, &csBlock);
+ }
+ else
+ sErr = U14ERR_NOTSET;
+#endif
+#ifdef LINUX
+ // 1401 should not have been grabbed
+ sErr = abGrabbed[hand] ? CED_Free1401(aHand1401[hand]) : U14ERR_NOTSET;
+#endif
+ if (sErr == U14ERR_NOERROR)
+ abGrabbed[hand] = FALSE;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14Peek1401
+** DESCRIPTION Cause the 1401 to do one or more peek operations.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+** is called. After the peek is done, use U14GetDebugData to retrieve
+** the results of the peek.
+****************************************************************************/
+U14API(short) U14Peek1401(short hand, unsigned int dwAddr, int nSize, int nRepeats)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ csBlock.longs[0] = (long)dwAddr;
+ csBlock.longs[1] = nSize;
+ csBlock.longs[2] = nRepeats;
+ sErr = U14Control1401(hand, U14_DBGPEEK, &csBlock);
+#endif
+#ifdef LINUX
+ TDBGBLOCK dbb;
+ dbb.iAddr = (int)dwAddr;
+ dbb.iWidth = nSize;
+ dbb.iRepeats = nRepeats;
+ sErr = CED_DbgPeek(aHand1401[hand], &dbb);
+#endif
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14Poke1401
+** DESCRIPTION Cause the 1401 to do one or more poke operations.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+** is called.
+****************************************************************************/
+U14API(short) U14Poke1401(short hand, unsigned int dwAddr, unsigned int dwValue,
+ int nSize, int nRepeats)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ csBlock.longs[0] = (long)dwAddr;
+ csBlock.longs[1] = nSize;
+ csBlock.longs[2] = nRepeats;
+ csBlock.longs[3] = (long)dwValue;
+ sErr = U14Control1401(hand, U14_DBGPOKE, &csBlock);
+#endif
+#ifdef LINUX
+ TDBGBLOCK dbb;
+ dbb.iAddr = (int)dwAddr;
+ dbb.iWidth = nSize;
+ dbb.iRepeats= nRepeats;
+ dbb.iData = (int)dwValue;
+ sErr = CED_DbgPoke(aHand1401[hand], &dbb);
+#endif
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14Ramp1401
+** DESCRIPTION Cause the 1401 to loop, writing a ramp to a location.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop.
+****************************************************************************/
+U14API(short) U14Ramp1401(short hand, unsigned int dwAddr, unsigned int dwDef, unsigned int dwEnable,
+ int nSize, int nRepeats)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ csBlock.longs[0] = (long)dwAddr;
+ csBlock.longs[1] = (long)dwDef;
+ csBlock.longs[2] = (long)dwEnable;
+ csBlock.longs[3] = nSize;
+ csBlock.longs[4] = nRepeats;
+ sErr = U14Control1401(hand, U14_DBGRAMPDATA, &csBlock);
+#endif
+#ifdef LINUX
+ TDBGBLOCK dbb;
+ dbb.iAddr = (int)dwAddr;
+ dbb.iDefault = (int)dwDef;
+ dbb.iMask = (int)dwEnable;
+ dbb.iWidth = nSize;
+ dbb.iRepeats = nRepeats;
+ sErr = CED_DbgRampAddr(aHand1401[hand], &dbb);
+#endif
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14RampAddr
+** DESCRIPTION Cause the 1401 to loop, reading from a ramping location.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+****************************************************************************/
+U14API(short) U14RampAddr(short hand, unsigned int dwDef, unsigned int dwEnable,
+ int nSize, int nRepeats)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ csBlock.longs[0] = (long)dwDef;
+ csBlock.longs[1] = (long)dwEnable;
+ csBlock.longs[2] = nSize;
+ csBlock.longs[3] = nRepeats;
+ sErr = U14Control1401(hand, U14_DBGRAMPADDR, &csBlock);
+#endif
+#ifdef LINUX
+ TDBGBLOCK dbb;
+ dbb.iDefault = (int)dwDef;
+ dbb.iMask = (int)dwEnable;
+ dbb.iWidth = nSize;
+ dbb.iRepeats = nRepeats;
+ sErr = CED_DbgRampAddr(aHand1401[hand], &dbb);
+#endif
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14StopDebugLoop
+** DESCRIPTION Stops a peek\poke\ramp that, with repeats set to zero,
+** will otherwise continue forever.
+****************************************************************************/
+U14API(short) U14StopDebugLoop(short hand)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+#ifdef _IS_WINDOWS_
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+ TCSBLOCK csBlock;
+ sErr = U14Control1401(hand, U14_DBGSTOPLOOP, &csBlock);
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+#endif
+#ifdef LINUX
+ sErr = abGrabbed[hand] ? CED_DbgStopLoop(aHand1401[hand]) : U14ERR_NOTSET;
+#endif
+ return sErr;
+}
+
+/****************************************************************************
+** U14GetDebugData
+** DESCRIPTION Returns the result from a previous peek operation.
+****************************************************************************/
+U14API(short) U14GetDebugData(short hand, U14LONG* plValue)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ if (abGrabbed[hand]) // 1401 should have been grabbed
+ {
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ sErr = U14Status1401(hand, U14_DBGGETDATA, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ *plValue = csBlock.longs[0]; // Return the data
+#endif
+#ifdef LINUX
+ TDBGBLOCK dbb;
+ sErr = CED_DbgGetData(aHand1401[hand], &dbb);
+ if (sErr == U14ERR_NOERROR)
+ *plValue = dbb.iData; /* Return the data */
+#endif
+ }
+ else
+ sErr = U14ERR_NOTSET;
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14StartSelfTest
+****************************************************************************/
+U14API(short) U14StartSelfTest(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ return U14Control1401(hand, U14_STARTSELFTEST, &csBlock);
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_StartSelfTest(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14CheckSelfTest
+****************************************************************************/
+U14API(short) U14CheckSelfTest(short hand, U14LONG *pData)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_CHECKSELFTEST, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ {
+ pData[0] = csBlock.longs[0]; /* Return the results to user */
+ pData[1] = csBlock.longs[1];
+ pData[2] = csBlock.longs[2];
+ }
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR) /* Check parameters */
+ {
+ TGET_SELFTEST gst;
+ sErr = CED_CheckSelfTest(aHand1401[hand], &gst);
+ if (sErr == U14ERR_NOERROR)
+ {
+ pData[0] = gst.code; /* Return the results to user */
+ pData[1] = gst.x;
+ pData[2] = gst.y;
+ }
+ }
+#endif
+ return sErr;
+}
+
+/****************************************************************************
+** U14GetUserMemorySize
+****************************************************************************/
+U14API(short) U14GetUserMemorySize(short hand, unsigned int *pMemorySize)
+{
+ // The original 1401 used a different command for getting the size
+ short sErr = U14SendString(hand, (asType1401[hand] == U14TYPE1401) ? "MEMTOP;" : "MEMTOP,?;");
+ *pMemorySize = 0; /* if we get error then leave size set at 0 */
+ if (sErr == U14ERR_NOERROR)
+ {
+ U14LONG alLimits[4];
+ sErr = U14LongsFrom1401(hand, alLimits, 4);
+ if (sErr > 0) /* +ve sErr is the number of values read */
+ {
+ sErr = U14ERR_NOERROR; /* All OK, flag success */
+ if (asType1401[hand] == U14TYPE1401) /* result for standard */
+ *pMemorySize = alLimits[0] - alLimits[1]; /* memtop-membot */
+ else
+ *pMemorySize = alLimits[0]; /* result for plus or u1401 */
+ }
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14TypeOf1401
+** Returns the type of the 1401, maybe unknown
+****************************************************************************/
+U14API(short) U14TypeOf1401(short hand)
+{
+ if ((hand < 0) || (hand >= MAX1401)) /* Check parameters */
+ return U14ERR_BADHAND;
+ else
+ return asType1401[hand];
+}
+
+/****************************************************************************
+** U14NameOf1401
+** Returns the type of the 1401 as a string, blank if unknown
+****************************************************************************/
+U14API(short) U14NameOf1401(short hand, char* pBuf, unsigned short wMax)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ char* pName;
+ switch (asType1401[hand]) // Results according to type
+ {
+ case U14TYPE1401: pName = "Std 1401"; break;
+ case U14TYPEPLUS: pName = "1401plus"; break;
+ case U14TYPEU1401: pName = "micro1401"; break;
+ case U14TYPEPOWER: pName = "Power1401"; break;
+ case U14TYPEU14012:pName = "Micro1401 mk II"; break;
+ case U14TYPEPOWER2:pName = "Power1401 mk II"; break;
+ case U14TYPEU14013:pName = "Micro1401-3"; break;
+ case U14TYPEPOWER3:pName = "Power1401-3"; break;
+ default: pName = "Unknown";
+ }
+ strncpy(pBuf, pName, wMax);
+ }
+ return sErr;
+}
+
+/****************************************************************************
+** U14TransferFlags
+** Returns the driver block transfer flags.
+** Bits can be set - see U14TF_ constants in use1401.h
+*****************************************************************************/
+U14API(short) U14TransferFlags(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_TRANSFERFLAGS, &csBlock);
+ return (sErr == U14ERR_NOERROR) ? (short)csBlock.ints[0] : sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_TransferFlags(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** GetDriverVersion
+** Actually reads driver version from the device driver.
+** Hi word is major revision, low word is minor revision.
+** Assumes that hand has been checked. Also codes driver type in bits 24 up.
+*****************************************************************************/
+static int GetDriverVersion(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ int iErr = U14Status1401(hand, U14_GETDRIVERREVISION, &csBlock);
+ if (iErr == U14ERR_NOERROR)
+ iErr = csBlock.longs[0];
+ return iErr;
+#endif
+#ifdef LINUX
+ return CED_GetDriverRevision(aHand1401[hand]);
+#endif
+}
+
+/****************************************************************************
+** U14MonitorRev
+** Returns the 1401 monitor revision number.
+** The number returned is the minor revision - the part after the
+** decimal point - plus the major revision times 1000.
+*****************************************************************************/
+U14API(int) U14MonitorRev(short hand)
+{
+ int iRev = 0;
+ int iErr = CheckHandle(hand);
+ if (iErr != U14ERR_NOERROR) // Check open and in use
+ return iErr;
+
+ if (asType1401[hand] >= U14TYPEPOWER2) // The Power2 onwards can give us the monitor
+ { // revision directly for all versions
+ iErr = U14SendString(hand, "INFO,S,28;");
+ if (iErr == U14ERR_NOERROR)
+ {
+ U14LONG lVals[2]; // Read a single number being the revision
+ iErr = U14LongsFrom1401(hand, lVals, 1);
+ if (iErr > 0)
+ {
+ iErr = U14ERR_NOERROR;
+ iRev = lVals[0]; // This is the minor part of the revision
+ iRev += asType1401[hand] * 10000;
+ }
+ }
+ }
+ else
+ { /* Do it the hard way for older hardware */
+ iErr = U14SendString(hand, ";CLIST;"); /* ask for command levels */
+ if (iErr == U14ERR_NOERROR)
+ {
+ while (iErr == U14ERR_NOERROR)
+ {
+ char wstr[50];
+ iErr = U14GetString(hand, wstr, 45);
+ if (iErr == U14ERR_NOERROR)
+ {
+ char *pstr = strstr(wstr,"RESET"); /* Is this the RESET command? */
+ if ((pstr == wstr) && (wstr[5] == ' '))
+ {
+ char *pstr2;
+ size_t l;
+ pstr += 6; /* Move past RESET and followinmg char */
+ l = strlen(pstr); /* The length of text remaining */
+ while (((pstr[l-1] == ' ') || (pstr[l-1] == 13)) && (l > 0))
+ {
+ pstr[l-1] = 0; /* Tidy up string at the end */
+ l--; /* by removing spaces and CRs */
+ }
+ pstr2 = strchr(pstr, '.'); /* Find the decimal point */
+ if (pstr2 != NULL) /* If we found the DP */
+ {
+ *pstr2 = 0; /* End pstr string at DP */
+ pstr2++; /* Now past the decimal point */
+ iRev = atoi(pstr2); /* Get the number after point */
+ }
+ iRev += (atoi(pstr) * 1000); /* Add first bit * 1000 */
+ }
+ if ((strlen(wstr) < 3) && (wstr[0] == ' '))
+ break; /* Spot the last line of results */
+ }
+ }
+ }
+ }
+ if (iErr == U14ERR_NOERROR) /* Return revision if no error */
+ iErr = iRev;
+
+ return iErr;
+}
+
+/****************************************************************************
+** U14TryToOpen Tries to open the 1401 number passed
+** Note : This will succeed with NT driver even if no I/F card or
+** 1401 switched off, so we check state and close the driver
+** if the state is unsatisfactory in U14Open1401.
+****************************************************************************/
+#ifdef _IS_WINDOWS_
+#define U14NAMEOLD "\\\\.\\CED_140%d"
+#define U14NAMENEW "\\\\.\\CED%d"
+static short U14TryToOpen(int n1401, long* plRetVal, short* psHandle)
+{
+ short sErr = U14ERR_NOERROR;
+ HANDLE hDevice = INVALID_HANDLE_VALUE;
+ unsigned int dwErr = 0;
+ int nFirst, nLast, nDev = 0; /* Used for the search for a 1401 */
+ BOOL bOldName = FALSE; /* start by looking for a modern driver */
+
+ if (n1401 == 0) /* If we need to look for a 1401 */
+ {
+ nFirst = 1; /* Set the search range */
+ nLast = MAX1401; /* through all the possible 1401s */
+ }
+ else
+ nFirst = nLast = n1401; /* Otherwise just one 1401 */
+
+ while (hDevice == INVALID_HANDLE_VALUE) /* Loop to try for a 1401 */
+ {
+ for (nDev = nFirst; nDev <= nLast; nDev++)
+ {
+ char szDevName[40]; /* name of the device to open */
+ sprintf(szDevName, bOldName ? U14NAMEOLD : U14NAMENEW, nDev);
+ hDevice = CreateFile(szDevName, GENERIC_WRITE | GENERIC_READ,
+ 0, 0, /* Unshared mode does nothing as this is a device */
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (hDevice != INVALID_HANDLE_VALUE)/* Check 1401 if opened */
+ {
+ TCSBLOCK csBlock;
+ assert(aHand1401[nDev-1] == INVALID_HANDLE_VALUE); // assert if already open
+ aHand1401[nDev-1] = hDevice; /* Save handle for now */
+
+#ifndef _WIN64
+ // Use DIOC method if not windows 9x or if using new device name
+ abUseNTDIOC[nDev-1] = (BOOL)(!bWindows9x || !bOldName);
+#endif
+ sErr = U14Status1401((short)(nDev-1), U14_TYPEOF1401, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ {
+ *plRetVal = csBlock.ints[0];
+ if (csBlock.ints[0] == U14ERR_INUSE)/* Prevent multi opens */
+ {
+ CloseHandle(hDevice); /* treat as open failure */
+ hDevice = INVALID_HANDLE_VALUE;
+ aHand1401[nDev-1] = INVALID_HANDLE_VALUE;
+ sErr = U14ERR_INUSE;
+ }
+ else
+ break; /* Exit from for loop on success */
+ }
+ else
+ {
+ CloseHandle(hDevice); /* Give up if func fails */
+ hDevice = INVALID_HANDLE_VALUE;
+ aHand1401[nDev-1] = INVALID_HANDLE_VALUE;
+ }
+ }
+ else
+ {
+ unsigned int dwe = GetLastError(); /* Get error code otherwise */
+ if ((dwe != ERROR_FILE_NOT_FOUND) || (dwErr == 0))
+ dwErr = dwe; /* Ignore repeats of 'not found' */
+ }
+ }
+
+ if ((hDevice == INVALID_HANDLE_VALUE) &&/* No device found, and... */
+ (bWindows9x) && /* ...old names are allowed, and... */
+ (bOldName == FALSE)) /* ...not tried old names yet */
+ bOldName = TRUE; /* Set flag and go round again */
+ else
+ break; /* otherwise that's all folks */
+ }
+
+ if (hDevice != INVALID_HANDLE_VALUE) /* If we got our device open */
+ *psHandle = (short)(nDev-1); /* return 1401 number opened */
+ else
+ {
+ if (dwErr == ERROR_FILE_NOT_FOUND) /* Sort out the error codes */
+ sErr = U14ERR_NO1401DRIV; /* if file not found */
+ else if (dwErr == ERROR_NOT_SUPPORTED)
+ sErr = U14ERR_DRIVTOOOLD; /* if DIOC not supported */
+ else if (dwErr == ERROR_ACCESS_DENIED)
+ sErr = U14ERR_INUSE;
+ else
+ sErr = U14ERR_DRIVCOMMS; /* otherwise assume comms problem */
+ }
+ return sErr;
+}
+#endif
+#ifdef LINUX
+static short U14TryToOpen(int n1401, long* plRetVal, short* psHandle)
+{
+ short sErr = U14ERR_NOERROR;
+ int fh = 0; // will be 1401 handle
+ int iErr = 0;
+ int nFirst, nLast, nDev = 0; // Used for the search for a 1401
+
+ if (n1401 == 0) // If we need to look for a 1401
+ {
+ nFirst = 1; /* Set the search range */
+ nLast = MAX1401; /* through all the possible 1401s */
+ }
+ else
+ nFirst = nLast = n1401; /* Otherwise just one 1401 */
+
+ for (nDev = nFirst; nDev <= nLast; nDev++)
+ {
+ char szDevName[40]; // name of the device to open
+ sprintf(szDevName,"/dev/cedusb/%d", nDev-1);
+ fh = open(szDevName, O_RDWR); // can only be opened once at a time
+ if (fh > 0) // Check 1401 if opened
+ {
+ int iType1401 = CED_TypeOf1401(fh); // get 1401 type
+ aHand1401[nDev-1] = fh; // Save handle for now
+ if (iType1401 >= 0)
+ {
+ *plRetVal = iType1401;
+ break; // Exit from for loop on success
+ }
+ else
+ {
+ close(fh); // Give up if func fails
+ fh = 0;
+ aHand1401[nDev-1] = 0;
+ }
+ }
+ else
+ {
+ if (((errno != ENODEV) && (errno != ENOENT)) || (iErr == 0))
+ iErr = errno; // Ignore repeats of 'not found'
+ }
+ }
+
+
+ if (fh) // If we got our device open
+ *psHandle = (short)(nDev-1); // return 1401 number opened
+ else
+ {
+ if ((iErr == ENODEV) || (iErr == ENOENT)) // Sort out the error codes
+ sErr = U14ERR_NO1401DRIV; // if file not found
+ else if (iErr == EBUSY)
+ sErr = U14ERR_INUSE;
+ else
+ sErr = U14ERR_DRIVCOMMS; // otherwise assume comms problem
+ }
+
+ return sErr;
+}
+#endif
+/****************************************************************************
+** U14Open1401
+** Tries to get the 1401 for use by this application
+*****************************************************************************/
+U14API(short) U14Open1401(short n1401)
+{
+ long lRetVal = -1;
+ short sErr;
+ short hand = 0;
+
+ if ((n1401 < 0) || (n1401 > MAX1401)) // must check the 1401 number
+ return U14ERR_BAD1401NUM;
+
+ szLastName[0] = 0; /* initialise the error info string */
+
+ sErr = U14TryToOpen(n1401, &lRetVal, &hand);
+ if (sErr == U14ERR_NOERROR)
+ {
+ long lDriverVersion = GetDriverVersion(hand); /* get driver revision */
+ long lDriverRev = -1;
+ if (lDriverVersion >= 0) /* can use it if all OK */
+ {
+ lLastDriverType = (lDriverVersion >> 24) & 0x000000FF;
+ asDriverType[hand] = (short)lLastDriverType; /* Drv type */
+ lLastDriverVersion = lDriverVersion & 0x00FFFFFF;
+ alDriverVersion[hand] = lLastDriverVersion; /* Actual version */
+ lDriverRev = ((lDriverVersion>>16) & 0x00FF); /* use hi word */
+ }
+ else
+ {
+ U14Close1401(hand); /* If there is a problem we should close */
+ return (short)lDriverVersion; /* and return the error code */
+ }
+
+ if (lDriverRev < MINDRIVERMAJREV) /* late enough version? */
+ {
+ U14Close1401(hand); /* If there is a problem we should close */
+ return U14ERR_DRIVTOOOLD; /* too old */
+ }
+
+ asLastRetCode[hand] = U14ERR_NOERROR; /* Initialise this 1401s info */
+ abGrabbed[hand] = FALSE; /* we are not in single step mode */
+ U14SetTimeout(hand, 3000); /* set 3 seconds as default timeout */
+
+ switch (lRetVal)
+ {
+ case DRIVRET_STD: asType1401[hand] = U14TYPE1401; break; /* Some we do by hand */
+ case DRIVRET_U1401:asType1401[hand] = U14TYPEU1401; break;
+ case DRIVRET_PLUS: asType1401[hand] = U14TYPEPLUS; break;
+ default: // For the power upwards, we can calculate the codes
+ if ((lRetVal >= DRIVRET_POWER) && (lRetVal <= DRIVRET_MAX))
+ asType1401[hand] = (short)(lRetVal - (DRIVRET_POWER - U14TYPEPOWER));
+ else
+ asType1401[hand] = U14TYPEUNKNOWN;
+ break;
+ }
+ U14KillIO1401(hand); /* resets the 1401 buffers */
+
+ if (asType1401[hand] != U14TYPEUNKNOWN) /* If all seems OK so far */
+ {
+ sErr = U14CheckErr(hand); /* we can check 1401 comms now */
+ if (sErr != 0) /* If this failed to go OK */
+ U14Reset1401(hand); /* Reset the 1401 to try to sort it out */
+ }
+
+ sErr = U14StateOf1401(hand);/* Get the state of the 1401 for return */
+ if (sErr == U14ERR_NOERROR)
+ sErr = hand; /* return the handle if no problem */
+ else
+ U14Close1401(hand); /* If there is a problem we should close */
+ }
+
+ return sErr;
+}
+
+
+/****************************************************************************
+** U14Close1401
+** Closes the 1401 so someone else can use it.
+****************************************************************************/
+U14API(short) U14Close1401(short hand)
+{
+ int j;
+ int iAreaMask = 0; // Mask for active areas
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR) // Check open and in use
+ return sErr;
+
+ for (j = 0; j<MAX_TRANSAREAS; ++j)
+ {
+ TGET_TX_BLOCK gtb;
+ int iReturn = U14GetTransfer(hand, &gtb); // get area information
+ if (iReturn == U14ERR_NOERROR) // ignore if any problem
+ if (gtb.used)
+ iAreaMask |= (1 << j); // set a bit for each used area
+ }
+
+ if (iAreaMask) // if any areas are in use
+ {
+ U14Reset1401(hand); // in case an active transfer running
+ for (j = 0; j < MAX_TRANSAREAS; ++j) // Locate locked areas
+ if (iAreaMask & (1 << j)) // And kill off any transfers
+ U14UnSetTransfer(hand, (unsigned short)j);
+ }
+
+#ifdef _IS_WINDOWS_
+ if (aXferEvent[hand]) // if this 1401 has an open event handle
+ {
+ CloseHandle(aXferEvent[hand]); // close down the handle
+ aXferEvent[hand] = NULL; // and mark it as gone
+ }
+
+ if (CloseHandle(aHand1401[hand]))
+#endif
+#ifdef LINUX
+ if (close(aHand1401[hand]) == 0) // make sure that close works
+#endif
+ {
+ aHand1401[hand] = INVALID_HANDLE_VALUE;
+ asType1401[hand] = U14TYPEUNKNOWN;
+ return U14ERR_NOERROR;
+ }
+ else
+ return U14ERR_BADHAND; /* BUGBUG GetLastError() ? */
+}
+
+/**************************************************************************
+**
+** Look for open 1401s and attempt to close them down. 32-bit windows only.
+**************************************************************************/
+U14API(void) U14CloseAll(void)
+{
+ int i;
+ for (i = 0; i < MAX1401; i++) // Tidy up and make safe
+ if (aHand1401[i] != INVALID_HANDLE_VALUE)
+ U14Close1401((short)i); // Last ditch close 1401
+}
+
+/****************************************************************************
+** U14Reset1401
+** Resets the 1401
+****************************************************************************/
+U14API(short) U14Reset1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ return U14Control1401(hand, U14_RESET1401, &csBlock);
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_Reset1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14ForceReset
+** Sets the 1401 full reset flag, so that next call to Reset1401 will
+** always cause a genuine reset.
+*****************************************************************************/
+U14API(short) U14ForceReset(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ return U14Control1401(hand, U14_FULLRESET, &csBlock);
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_FullReset(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14KillIO1401
+** Removes any pending IO from the buffers.
+*****************************************************************************/
+U14API(short) U14KillIO1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ return U14Control1401(hand, U14_KILLIO1401, &csBlock);
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_KillIO1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+
+/****************************************************************************
+** U14SendString
+** Send characters to the 1401
+*****************************************************************************/
+U14API(short) U14SendString(short hand, const char* pString)
+{
+ int nChars; // length we are sending
+ long lTimeOutTicks; // when to time out
+ BOOL bSpaceToSend; // space to send yet
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+
+ nChars = (int)strlen(pString); // get string length we want to send
+ if (nChars > MAXSTRLEN)
+ return U14ERR_STRLEN; // String too long
+
+#ifdef _IS_WINDOWS_
+ // To get here we must wait for the buffer to have some space
+ lTimeOutTicks = U14WhenToTimeOut(hand);
+ do
+ {
+ bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+ }
+ while (!bSpaceToSend && !U14PassedTime(lTimeOutTicks));
+
+ if (!bSpaceToSend) /* Last-ditch attempt to avoid timeout */
+ { /* This can happen with anti-virus or network activity! */
+ int i;
+ for (i = 0; (i < 4) && (!bSpaceToSend); ++i)
+ {
+ Sleep(25); /* Give other threads a chance for a while */
+ bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+ }
+ }
+
+ if (asLastRetCode[hand] == U14ERR_NOERROR) /* no errors? */
+ {
+ if (bSpaceToSend)
+ {
+ PARAMBLK rData;
+ unsigned int dwBytes;
+ char tstr[MAXSTRLEN+5]; /* Buffer for chars */
+
+ if ((hand < 0) || (hand >= MAX1401))
+ sErr = U14ERR_BADHAND;
+ else
+ {
+ strcpy(tstr, pString); /* Into local buf */
+#ifndef _WIN64
+ if (!USE_NT_DIOC(hand)) /* Using WIN 95 driver access? */
+ {
+ int iOK = DeviceIoControl(aHand1401[hand], (unsigned int)U14_SENDSTRING,
+ NULL, 0, tstr, nChars,
+ &dwBytes, NULL);
+ if (iOK)
+ sErr = (dwBytes >= (unsigned int)nChars) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS;
+ else
+ sErr = (short)GetLastError();
+ }
+ else
+#endif
+ {
+ int iOK = DeviceIoControl(aHand1401[hand],(unsigned int)U14_SENDSTRING,
+ tstr, nChars,
+ &rData,sizeof(PARAMBLK),&dwBytes,NULL);
+ if (iOK && (dwBytes >= sizeof(PARAMBLK)))
+ sErr = rData.sState;
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+
+ if (sErr != U14ERR_NOERROR) // If we have had a comms error
+ U14ForceReset(hand); // make sure we get real reset
+ }
+
+ return sErr;
+
+ }
+ else
+ {
+ U14ForceReset(hand); // make sure we get real reset
+ return U14ERR_TIMEOUT;
+ }
+ }
+ else
+ return asLastRetCode[hand];
+#endif
+#ifdef LINUX
+ // Just try to send it and see what happens!
+ sErr = CED_SendString(aHand1401[hand], pString, nChars);
+ if (sErr != U14ERR_NOOUT) // if any result except "no room in output"...
+ {
+ if (sErr != U14ERR_NOERROR) // if a problem...
+ U14ForceReset(hand); // ...make sure we get real reset next time
+ return sErr; // ... we are done as nothing we can do
+ }
+
+ // To get here we must wait for the buffer to have some space
+ lTimeOutTicks = U14WhenToTimeOut(hand);
+ do
+ {
+ bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+ if (!bSpaceToSend)
+ sched_yield(); // let others have fun while we wait
+ }
+ while (!bSpaceToSend && !U14PassedTime(lTimeOutTicks));
+
+ if (asLastRetCode[hand] == U14ERR_NOERROR) /* no errors? */
+ {
+ if (bSpaceToSend)
+ {
+ sErr = CED_SendString(aHand1401[hand], pString, nChars);
+ if (sErr != U14ERR_NOERROR) // If we have had a comms error
+ U14ForceReset(hand); // make sure we get real reset
+ return sErr;
+ }
+ else
+ {
+ U14ForceReset(hand); // make sure we get real reset
+ return U14ERR_TIMEOUT;
+ }
+ }
+ else
+ return asLastRetCode[hand];
+#endif
+}
+
+/****************************************************************************
+** U14SendChar
+** Send character to the 1401
+*****************************************************************************/
+U14API(short) U14SendChar(short hand, char cChar)
+{
+#ifdef _IS_WINDOWS_
+ char sz[2]=" "; // convert to a string and send
+ sz[0] = cChar;
+ sz[1] = 0;
+ return(U14SendString(hand, sz)); // String routines are better
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_SendChar(aHand1401[hand], cChar) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetString
+** Get a string from the 1401. Returns a null terminated string.
+** The string is all the characters up to the next CR in the buffer
+** or the end of the buffer if that comes first. This only returns text
+** if there is a CR in the buffer. The terminating CR character is removed.
+** wMaxLen Is the size of the buffer and must be at least 2 or an error.
+** Returns U14ERR_NOERR if OK with the result in the string or a negative
+** error code. Any error from the device causes us to set up for
+** a full reset.
+****************************************************************************/
+U14API(short) U14GetString(short hand, char* pBuffer, unsigned short wMaxLen)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR) // If an error...
+ return sErr; // ...bail out!
+
+#ifdef _IS_WINDOWS_
+ if (wMaxLen>1) // we need space for terminating 0
+ {
+ BOOL bLineToGet; // true when a line to get
+ long lTimeOutTicks = U14WhenToTimeOut(hand);
+ do
+ bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+ while (!bLineToGet && !U14PassedTime(lTimeOutTicks));
+
+ if (!bLineToGet) /* Last-ditch attempt to avoid timeout */
+ { /* This can happen with anti-virus or network activity! */
+ int i;
+ for (i = 0; (i < 4) && (!bLineToGet); ++i)
+ {
+ Sleep(25); /* Give other threads a chance for a while */
+ bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+ }
+ }
+
+ if (bLineToGet)
+ {
+ if (asLastRetCode[hand] == U14ERR_NOERROR) /* all ok so far */
+ {
+ unsigned int dwBytes = 0;
+ *((unsigned short *)pBuffer) = wMaxLen; /* set up length */
+#ifndef _WIN64
+ if (!USE_NT_DIOC(hand)) /* Win 95 DIOC here ? */
+ {
+ char tstr[MAXSTRLEN+5]; /* Buffer for Win95 chars */
+ int iOK;
+
+ if (wMaxLen > MAXSTRLEN) /* Truncate length */
+ wMaxLen = MAXSTRLEN;
+
+ *((unsigned short *)tstr) = wMaxLen; /* set len */
+
+ iOK = DeviceIoControl(aHand1401[hand],(unsigned int)U14_GETSTRING,
+ NULL, 0, tstr, wMaxLen+sizeof(short),
+ &dwBytes, NULL);
+ if (iOK) /* Device IO control OK ? */
+ {
+ if (dwBytes >= 0) /* If driver OK */
+ {
+ strcpy(pBuffer, tstr);
+ sErr = U14ERR_NOERROR;
+ }
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+ else
+ {
+ sErr = (short)GetLastError();
+ if (sErr > 0) /* Errors are -ve */
+ sErr = (short)-sErr;
+ }
+ }
+ else
+#endif
+ { /* Here for NT, the DLL must own the buffer */
+ HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE,wMaxLen+sizeof(short));
+ if (hMem)
+ {
+ char* pMem = (char*)GlobalLock(hMem);
+ if (pMem)
+ {
+ int iOK = DeviceIoControl(aHand1401[hand],(unsigned int)U14_GETSTRING,
+ NULL, 0, pMem, wMaxLen+sizeof(short),
+ &dwBytes, NULL);
+ if (iOK) /* Device IO control OK ? */
+ {
+ if (dwBytes >= wMaxLen)
+ {
+ strcpy(pBuffer, pMem+sizeof(short));
+ sErr = *((SHORT*)pMem);
+ }
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+ else
+ sErr = U14ERR_DRIVCOMMS;
+
+ GlobalUnlock(hMem);
+ }
+ else
+ sErr = U14ERR_OUTOFMEMORY;
+
+ GlobalFree(hMem);
+ }
+ else
+ sErr = U14ERR_OUTOFMEMORY;
+ }
+
+ if (sErr == U14ERR_NOERROR) // If all OK...
+ TranslateString(pBuffer); // ...convert any commas to spaces
+ else // If we have had a comms error...
+ U14ForceReset(hand); // ...make sure we get real reset
+
+ }
+ else
+ sErr = asLastRetCode[hand];
+ }
+ else
+ {
+ sErr = U14ERR_TIMEOUT;
+ U14ForceReset(hand); // make sure we get real reset
+ }
+ }
+ else
+ sErr = U14ERR_BUFF_SMALL;
+ return sErr;
+#endif
+#ifdef LINUX
+ if (wMaxLen>1) // we need space for terminating 0
+ {
+ BOOL bLineToGet; // true when a line to get
+ long lTimeOutTicks = U14WhenToTimeOut(hand);
+ do
+ {
+ bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+ if (!bLineToGet)
+ sched_yield();
+
+ }
+ while (!bLineToGet && !U14PassedTime(lTimeOutTicks));
+
+ if (bLineToGet)
+ {
+ sErr = CED_GetString(aHand1401[hand], pBuffer, wMaxLen-1); // space for terminator
+ if (sErr >=0) // if we were OK...
+ {
+ if (sErr >= wMaxLen) // this should NOT happen unless
+ sErr = U14ERR_DRIVCOMMS; // ...driver Comms are very bad
+ else
+ {
+ pBuffer[sErr] = 0; // OK, so terminate the string...
+ TranslateString(pBuffer); // ...and convert commas to spaces.
+ }
+ }
+
+ if (sErr < U14ERR_NOERROR) // If we have had a comms error
+ U14ForceReset(hand); // make sure we get real reset
+ }
+ else
+ {
+ sErr = U14ERR_TIMEOUT;
+ U14ForceReset(hand); // make sure we get real reset
+ }
+ }
+ else
+ sErr = U14ERR_BUFF_SMALL;
+
+ return sErr >= U14ERR_NOERROR ? U14ERR_NOERROR : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetChar
+** Get a character from the 1401. CR returned as CR.
+*****************************************************************************/
+U14API(short) U14GetChar(short hand, char* pcChar)
+{
+#ifdef _IS_WINDOWS_
+ char sz[2]; // read a very short string
+ short sErr = U14GetString(hand, sz, 2); // read one char and nul terminate it
+ *pcChar = sz[0]; // copy to result, NB char translate done by GetString
+ if (sErr == U14ERR_NOERROR)
+ { // undo translate of CR to zero
+ if (*pcChar == '\0') // by converting back
+ *pcChar = '\n'; // What a nasty thing to have to do
+ }
+ return sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR) // Check parameters
+ return sErr;
+ sErr = CED_GetChar(aHand1401[hand]); // get one char, if available
+ if (sErr >= 0)
+ {
+ *pcChar = (char)sErr; // return if it we have one
+ return U14ERR_NOERROR; // say all OK
+ }
+ else
+ return sErr;
+#endif
+}
+
+/****************************************************************************
+** U14Stat1401
+** Returns 0 for no lines or error or non zero for something waiting
+****************************************************************************/
+U14API(short) U14Stat1401(short hand)
+{
+ return ((short)(U14LineCount(hand) > 0));
+}
+
+/****************************************************************************
+** U14CharCount
+** Returns the number of characters in the input buffer
+*****************************************************************************/
+U14API(short) U14CharCount(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_STAT1401, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ sErr = csBlock.ints[0];
+ return sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_Stat1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14LineCount
+** Returns the number of CR characters in the input buffer
+*****************************************************************************/
+U14API(short) U14LineCount(short hand)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14Status1401(hand, U14_LINECOUNT, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ sErr = csBlock.ints[0];
+ return sErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_LineCount(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetErrorString
+** Converts error code supplied to a decent descriptive string.
+** NOTE: This function may use some extra information stored
+** internally in the DLL. This information is stored on a
+** per-process basis, but it might be altered if you call
+** other functions after getting an error and before using
+** this function.
+****************************************************************************/
+U14API(void) U14GetErrorString(short nErr, char* pStr, unsigned short wMax)
+{
+ char wstr[150];
+
+ switch (nErr) /* Basically, we do this with a switch block */
+ {
+ case U14ERR_OFF:
+ sprintf(wstr, "The 1401 is apparently switched off (code %d)", nErr);
+ break;
+
+ case U14ERR_NC:
+ sprintf(wstr, "The 1401 is not connected to the interface card (code %d)", nErr);
+ break;
+
+ case U14ERR_ILL:
+ sprintf(wstr, "The 1401 is not working correctly (code %d)", nErr);
+ break;
+
+ case U14ERR_NOIF:
+ sprintf(wstr, "The 1401 interface card was not detected (code %d)", nErr);
+ break;
+
+ case U14ERR_TIME:
+ sprintf(wstr, "The 1401 fails to become ready for use (code %d)", nErr);
+ break;
+
+ case U14ERR_BADSW:
+ sprintf(wstr, "The 1401 interface card jumpers are incorrect (code %d)", nErr);
+ break;
+
+ case U14ERR_NOINT:
+ sprintf(wstr, "The 1401 interrupt is not available for use (code %d)", nErr);
+ break;
+
+ case U14ERR_INUSE:
+ sprintf(wstr, "The 1401 is already in use by another program (code %d)", nErr);
+ break;
+
+ case U14ERR_NODMA:
+ sprintf(wstr, "The 1401 DMA channel is not available for use (code %d)", nErr);
+ break;
+
+ case U14ERR_BADHAND:
+ sprintf(wstr, "The application supplied an incorrect 1401 handle (code %d)", nErr);
+ break;
+
+ case U14ERR_BAD1401NUM:
+ sprintf(wstr, "The application used an incorrect 1401 number (code %d)", nErr);
+ break;
+
+ case U14ERR_NO_SUCH_FN:
+ sprintf(wstr, "The code passed to the 1401 driver is invalid (code %d)", nErr);
+ break;
+
+ case U14ERR_NO_SUCH_SUBFN:
+ sprintf(wstr, "The sub-code passed to the 1401 driver is invalid (code %d)", nErr);
+ break;
+
+ case U14ERR_NOOUT:
+ sprintf(wstr, "No room in buffer for characters for the 1401 (code %d)", nErr);
+ break;
+
+ case U14ERR_NOIN:
+ sprintf(wstr, "No characters from the 1401 are available (code %d)", nErr);
+ break;
+
+ case U14ERR_STRLEN:
+ sprintf(wstr, "A string sent to or read from the 1401 was too long (code %d)", nErr);
+ break;
+
+ case U14ERR_LOCKFAIL:
+ sprintf(wstr, "Failed to lock host memory for data transfer (code %d)", nErr);
+ break;
+
+ case U14ERR_UNLOCKFAIL:
+ sprintf(wstr, "Failed to unlock host memory after data transfer (code %d)", nErr);
+ break;
+
+ case U14ERR_ALREADYSET:
+ sprintf(wstr, "The transfer area used is already set up (code %d)", nErr);
+ break;
+
+ case U14ERR_NOTSET:
+ sprintf(wstr, "The transfer area used has not been set up (code %d)", nErr);
+ break;
+
+ case U14ERR_BADAREA:
+ sprintf(wstr, "The transfer area number is incorrect (code %d)", nErr);
+ break;
+
+ case U14ERR_NOFILE:
+ sprintf(wstr, "The command file %s could not be opened (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_READERR:
+ sprintf(wstr, "The command file %s could not be read (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_UNKNOWN:
+ sprintf(wstr, "The %s command resource could not be found (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_HOSTSPACE:
+ sprintf(wstr, "Unable to allocate memory for loading command %s (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_LOCKERR:
+ sprintf(wstr, "Unable to lock memory for loading command %s (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_CLOADERR:
+ sprintf(wstr, "Error in loading command %s, bad command format (code %d)", szLastName, nErr);
+ break;
+
+ case U14ERR_TOXXXERR:
+ sprintf(wstr, "Error detected after data transfer to or from the 1401 (code %d)", nErr);
+ break;
+
+ case U14ERR_NO386ENH:
+ sprintf(wstr, "Windows 3.1 is not running in 386 enhanced mode (code %d)", nErr);
+ break;
+
+ case U14ERR_NO1401DRIV:
+ sprintf(wstr, "The 1401 device driver cannot be found (code %d)\nUSB: check plugged in and powered\nOther: not installed?", nErr);
+ break;
+
+ case U14ERR_DRIVTOOOLD:
+ sprintf(wstr, "The 1401 device driver is too old for use (code %d)", nErr);
+ break;
+
+ case U14ERR_TIMEOUT:
+ sprintf(wstr, "Character transmissions to the 1401 timed-out (code %d)", nErr);
+ break;
+
+ case U14ERR_BUFF_SMALL:
+ sprintf(wstr, "Buffer for text from the 1401 was too small (code %d)", nErr);
+ break;
+
+ case U14ERR_CBALREADY:
+ sprintf(wstr, "1401 monitor callback already set up (code %d)", nErr);
+ break;
+
+ case U14ERR_BADDEREG:
+ sprintf(wstr, "1401 monitor callback deregister invalid (code %d)", nErr);
+ break;
+
+ case U14ERR_DRIVCOMMS:
+ sprintf(wstr, "1401 device driver communications failed (code %d)", nErr);
+ break;
+
+ case U14ERR_OUTOFMEMORY:
+ sprintf(wstr, "Failed to allocate or lock memory for text from the 1401 (code %d)", nErr);
+ break;
+
+ default:
+ sprintf(wstr, "1401 error code %d returned; this code is unknown", nErr);
+ break;
+
+ }
+ if ((unsigned short)strlen(wstr) >= wMax-1) /* Check for string being too long */
+ wstr[wMax-1] = 0; /* and truncate it if so */
+ strcpy(pStr, wstr); /* Return the error string */
+}
+
+/***************************************************************************
+** U14GetTransfer
+** Get a TGET_TX_BLOCK describing a transfer area (held in the block)
+***************************************************************************/
+U14API(short) U14GetTransfer(short hand, TGET_TX_BLOCK *pTransBlock)
+{
+ short sErr = CheckHandle(hand);
+#ifdef _IS_WINDOWS_
+ if (sErr == U14ERR_NOERROR)
+ {
+ unsigned int dwBytes = 0;
+ BOOL bOK = DeviceIoControl(aHand1401[hand], (unsigned int)U14_GETTRANSFER, NULL, 0, pTransBlock,
+ sizeof(TGET_TX_BLOCK), &dwBytes, NULL);
+
+ if (bOK && (dwBytes >= sizeof(TGET_TX_BLOCK)))
+ sErr = U14ERR_NOERROR;
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+ return sErr;
+#endif
+#ifdef LINUX
+ return (sErr == U14ERR_NOERROR) ? CED_GetTransfer(aHand1401[hand], pTransBlock) : sErr;
+#endif
+}
+/////////////////////////////////////////////////////////////////////////////
+// U14WorkingSet
+// For Win32 only, adjusts process working set so that minimum is at least
+// dwMinKb and maximum is at least dwMaxKb.
+// Return value is zero if all went OK, or a code from 1 to 3 indicating the
+// cause of the failure:
+//
+// 1 unable to access process (insufficient rights?)
+// 2 unable to read process working set
+// 3 unable to set process working set - bad parameters?
+U14API(short) U14WorkingSet(unsigned int dwMinKb, unsigned int dwMaxKb)
+{
+#ifdef _IS_WINDOWS_
+ short sRetVal = 0; // 0 means all is OK
+ HANDLE hProcess;
+ unsigned int dwVer = GetVersion();
+ if (dwVer & 0x80000000) // is this not NT?
+ return 0; // then give up right now
+
+ // Now attempt to get information on working set size
+ hProcess = OpenProcess(STANDARD_RIGHTS_REQUIRED |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_SET_QUOTA,
+ FALSE, _getpid());
+ if (hProcess)
+ {
+ SIZE_T dwMinSize,dwMaxSize;
+ if (GetProcessWorkingSetSize(hProcess, &dwMinSize, &dwMaxSize))
+ {
+ unsigned int dwMin = dwMinKb << 10; // convert from kb to bytes
+ unsigned int dwMax = dwMaxKb << 10;
+
+ // if we get here, we have managed to read the current size
+ if (dwMin > dwMinSize) // need to change sizes?
+ dwMinSize = dwMin;
+
+ if (dwMax > dwMaxSize)
+ dwMaxSize = dwMax;
+
+ if (!SetProcessWorkingSetSize(hProcess, dwMinSize, dwMaxSize))
+ sRetVal = 3; // failed to change size
+ }
+ else
+ sRetVal = 2; // failed to read original size
+
+ CloseHandle(hProcess);
+ }
+ else
+ sRetVal = 1; // failed to get handle
+
+ return sRetVal;
+#endif
+#ifdef LINUX
+ if (dwMinKb | dwMaxKb)
+ {
+ // to stop compiler moaning
+ }
+ return U14ERR_NOERROR;
+#endif
+}
+
+/****************************************************************************
+** U14UnSetTransfer Cancels a transfer area
+** wArea The index of a block previously used in by SetTransfer
+*****************************************************************************/
+U14API(short) U14UnSetTransfer(short hand, unsigned short wArea)
+{
+ short sErr = CheckHandle(hand);
+#ifdef _IS_WINDOWS_
+ if (sErr == U14ERR_NOERROR)
+ {
+ TCSBLOCK csBlock;
+ csBlock.ints[0] = (short)wArea; /* Area number into control block */
+ sErr = U14Control1401(hand, U14_UNSETTRANSFER, &csBlock); /* Free area */
+
+ VirtualUnlock(apAreas[hand][wArea], auAreas[hand][wArea]);/* Unlock */
+ apAreas[hand][wArea] = NULL; /* Clear locations */
+ auAreas[hand][wArea] = 0;
+ }
+ return sErr;
+#endif
+#ifdef LINUX
+ return (sErr == U14ERR_NOERROR) ? CED_UnsetTransfer(aHand1401[hand], wArea) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14SetTransArea Sets an area up to be used for transfers
+** unsigned short wArea The area number to set up
+** void *pvBuff The address of the buffer for the data.
+** unsigned int dwLength The length of the buffer for the data
+** short eSz The element size (used for byte swapping on the Mac)
+****************************************************************************/
+U14API(short) U14SetTransArea(short hand, unsigned short wArea, void *pvBuff,
+ unsigned int dwLength, short eSz)
+{
+ struct transfer_area_desc td;
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+ if (wArea >= MAX_TRANSAREAS) // Is this a valid area number
+ return U14ERR_BADAREA;
+
+#ifdef _IS_WINDOWS_
+ assert(apAreas[hand][wArea] == NULL);
+ assert(auAreas[hand][wArea] == 0);
+
+ apAreas[hand][wArea] = pvBuff; /* Save data for later */
+ auAreas[hand][wArea] = dwLength;
+
+ if (!VirtualLock(pvBuff, dwLength)) /* Lock using WIN32 calls */
+ {
+ apAreas[hand][wArea] = NULL; /* Clear locations */
+ auAreas[hand][wArea] = 0;
+ return U14ERR_LOCKERR; /* VirtualLock failed */
+ }
+#ifndef _WIN64
+ if (!USE_NT_DIOC(hand)) /* Use Win 9x DIOC? */
+ {
+ unsigned int dwBytes;
+ VXTRANSFERDESC vxDesc; /* Structure to pass to VXD */
+ vxDesc.wArea = wArea; /* Copy across simple params */
+ vxDesc.dwLength = dwLength;
+
+ // Check we are not asking an old driver for more than area 0
+ if ((wArea != 0) && (U14DriverVersion(hand) < 0x00010002L))
+ sErr = U14ERR_DRIVTOOOLD;
+ else
+ {
+ vxDesc.dwAddrOfs = (unsigned int)pvBuff; /* 32 bit offset */
+ vxDesc.wAddrSel = 0;
+
+ if (DeviceIoControl(aHand1401[hand], (unsigned int)U14_SETTRANSFER,
+ pvBuff,dwLength, /* Will translate pointer */
+ &vxDesc,sizeof(VXTRANSFERDESC),
+ &dwBytes,NULL))
+ {
+ if (dwBytes >= sizeof(VXTRANSFERDESC)) /* Driver OK ? */
+ sErr = U14ERR_NOERROR;
+ else
+ sErr = U14ERR_DRIVCOMMS; /* Else never got there */
+ }
+ else
+ sErr = (short)GetLastError();
+ }
+ }
+ else
+#endif
+ {
+ PARAMBLK rWork;
+ unsigned int dwBytes;
+ td.wArea = wArea; /* Pure NT - put data into struct */
+ td.lpvBuff = pvBuff;
+ td.dwLength = dwLength;
+ td.eSize = 0; // Dummy element size
+
+ if (DeviceIoControl(aHand1401[hand],(unsigned int)U14_SETTRANSFER,
+ &td,sizeof(struct transfer_area_desc),
+ &rWork,sizeof(PARAMBLK),&dwBytes,NULL))
+ {
+ if (dwBytes >= sizeof(PARAMBLK)) // maybe error from driver?
+ sErr = rWork.sState; // will report any error
+ else
+ sErr = U14ERR_DRIVCOMMS; // Else never got there
+ }
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+
+ if (sErr != U14ERR_NOERROR)
+ {
+ if (sErr != U14ERR_LOCKERR) // unless lock failed...
+ VirtualUnlock(pvBuff, dwLength); // ...release the lock
+ apAreas[hand][wArea] = NULL; // Clear locations
+ auAreas[hand][wArea] = 0;
+ }
+
+ return sErr;
+#endif
+#ifdef LINUX
+ // The strange cast is so that it works in 64 and 32-bit linux as long is 64-bits
+ // in the 64 bit version.
+ td.lpvBuff = (long long)((unsigned long)pvBuff);
+ td.wAreaNum = wArea;
+ td.dwLength = dwLength;
+ td.eSize = eSz; // Dummy element size
+ return CED_SetTransfer(aHand1401[hand], &td);
+#endif
+}
+
+/****************************************************************************
+** U14SetTransferEvent Sets an event for notification of application
+** wArea The transfer area index, from 0 to MAXAREAS-1
+** bEvent True to create an event, false to remove it
+** bToHost Set 0 for notification on to1401 tranfers, 1 for
+** notification of transfers to the host PC
+** dwStart The offset of the sub-area of interest
+** dwLength The size of the sub-area of interest
+**
+** The device driver will set the event supplied to the signalled state
+** whenever a DMA transfer to/from the specified area is completed. The
+** transfer has to be in the direction specified by bToHost, and overlap
+** that part of the whole transfer area specified by dwStart and dwLength.
+** It is important that this function is called with bEvent false to release
+** the event once 1401 activity is finished.
+**
+** Returns 1 if an event handle exists, 0 if all OK and no event handle or
+** a negative code for an error.
+****************************************************************************/
+U14API(short) U14SetTransferEvent(short hand, unsigned short wArea, BOOL bEvent,
+ BOOL bToHost, unsigned int dwStart, unsigned int dwLength)
+{
+#ifdef _IS_WINDOWS_
+ TCSBLOCK csBlock;
+ short sErr = U14TransferFlags(hand); // see if we can handle events
+ if (sErr >= U14ERR_NOERROR) // check handle is OK
+ {
+ bEvent = bEvent && ((sErr & U14TF_NOTIFY) != 0); // remove request if we cannot do events
+ if (wArea >= MAX_TRANSAREAS) // Check a valid area...
+ return U14ERR_BADAREA; // ...and bail of not
+
+ // We can hold an event for each area, so see if we need to change the
+ // state of the event.
+ if ((bEvent != 0) != (aXferEvent[hand] != 0)) // change of event state?
+ {
+ if (bEvent) // want one and none present
+ aXferEvent[hand] = CreateEvent(NULL, FALSE, FALSE, NULL);
+ else
+ {
+ CloseHandle(aXferEvent[hand]); // clear the existing event
+ aXferEvent[hand] = NULL; // and clear handle
+ }
+ }
+
+ // We have to store the parameters differently for 64-bit operations
+ // because a handle is 64 bits long. The drivers know of this and
+ // handle the information appropriately.
+#ifdef _WIN64
+ csBlock.longs[0] = wArea; // Pass paramaters into the driver...
+ if (bToHost != 0) // The direction flag is held in the
+ csBlock.longs[0] |= 0x10000; // upper word of the transfer area value
+ *((HANDLE*)&csBlock.longs[1]) = aXferEvent[hand]; // The event handle is 64-bits
+ csBlock.longs[3] = dwStart; // Thankfully these two remain
+ csBlock.longs[4] = dwLength; // as unsigned 32-bit values
+#else
+ csBlock.longs[0] = wArea; // pass paramaters into the driver...
+ csBlock.longs[1] = (long)aXferEvent[hand]; // ...especially the event handle
+ csBlock.longs[2] = bToHost;
+ csBlock.longs[3] = dwStart;
+ csBlock.longs[4] = dwLength;
+#endif
+ sErr = U14Control1401(hand, U14_SETTRANSEVENT, &csBlock);
+ if (sErr == U14ERR_NOERROR)
+ sErr = (short)(aXferEvent[hand] != NULL); // report if we have a flag
+ }
+
+ return sErr;
+#endif
+#ifdef LINUX
+ TRANSFEREVENT te;
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+
+ if (wArea >= MAX_TRANSAREAS) // Is this a valid area number
+ return U14ERR_BADAREA;
+
+ te.wAreaNum = wArea; // copy parameters to the control block
+ te.wFlags = bToHost ? 1 : 0; // bit 0 sets the direction
+ te.dwStart = dwStart; // start offset of the event area
+ te.dwLength = dwLength; // size of the event area
+ te.iSetEvent = bEvent; // in Windows, this creates/destroys the event
+ return CED_SetEvent(aHand1401[hand], &te);
+#endif
+}
+
+/****************************************************************************
+** U14TestTransferEvent
+** Would a U14WaitTransferEvent() call return immediately? return 1 if so,
+** 0 if not or a negative code if a problem.
+****************************************************************************/
+U14API(int) U14TestTransferEvent(short hand, unsigned short wArea)
+{
+#ifdef _IS_WINDOWS_
+ int iErr = CheckHandle(hand);
+ if (iErr == U14ERR_NOERROR)
+ {
+ if (aXferEvent[hand]) // if a handle is set...
+ iErr = WaitForSingleObject(aXferEvent[hand], 0) == WAIT_OBJECT_0;
+ }
+ return iErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_TestEvent(aHand1401[hand], wArea) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14WaitTransferEvent
+** Wait for a transfer event with a timeout.
+** msTimeOut is 0 for an infinite wait, else it is the maximum time to wait
+** in milliseconds in range 0-0x00ffffff.
+** Returns If no event handle then return immediately. Else return 1 if
+** timed out or 0=event, and a negative code if a problem.
+****************************************************************************/
+U14API(int) U14WaitTransferEvent(short hand, unsigned short wArea, int msTimeOut)
+{
+#ifdef _IS_WINDOWS_
+ int iErr = CheckHandle(hand);
+ if (iErr == U14ERR_NOERROR)
+ {
+ if (aXferEvent[hand])
+ {
+ if (msTimeOut == 0)
+ msTimeOut = INFINITE;
+ iErr = WaitForSingleObject(aXferEvent[hand], msTimeOut) != WAIT_OBJECT_0;
+ }
+ else
+ iErr = TRUE; // say we timed out if no event
+ }
+ return iErr;
+#endif
+#ifdef LINUX
+ short sErr = CheckHandle(hand);
+ return (sErr == U14ERR_NOERROR) ? CED_WaitEvent(aHand1401[hand], wArea, msTimeOut) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14SetCircular Sets an area up for circular DMA transfers
+** unsigned short wArea The area number to set up
+** BOOL bToHost Sets the direction of data transfer
+** void *pvBuff The address of the buffer for the data
+** unsigned int dwLength The length of the buffer for the data
+****************************************************************************/
+U14API(short) U14SetCircular(short hand, unsigned short wArea, BOOL bToHost,
+ void *pvBuff, unsigned int dwLength)
+{
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+
+ if (wArea >= MAX_TRANSAREAS) /* Is this a valid area number */
+ return U14ERR_BADAREA;
+
+ if (!bToHost) /* For now, support tohost transfers only */
+ return U14ERR_BADAREA; /* best error code I can find */
+#ifdef _IS_WINDOWS_
+ assert(apAreas[hand][wArea] == NULL);
+ assert(auAreas[hand][wArea] == 0);
+
+ apAreas[hand][wArea] = pvBuff; /* Save data for later */
+ auAreas[hand][wArea] = dwLength;
+
+ if (!VirtualLock(pvBuff, dwLength)) /* Lock using WIN32 calls */
+ sErr = U14ERR_LOCKERR; /* VirtualLock failed */
+ else
+ {
+ PARAMBLK rWork;
+ unsigned int dwBytes;
+ struct transfer_area_desc txDesc;
+ txDesc.wArea = wArea; /* Pure NT - put data into struct */
+ txDesc.lpvBuff = pvBuff;
+ txDesc.dwLength = dwLength;
+ txDesc.eSize = (short)bToHost; /* Use this for direction flag */
+
+ if (DeviceIoControl(aHand1401[hand],(unsigned int)U14_SETCIRCULAR,
+ &txDesc, sizeof(struct transfer_area_desc),
+ &rWork, sizeof(PARAMBLK),&dwBytes,NULL))
+ {
+ if (dwBytes >= sizeof(PARAMBLK)) /* error from driver? */
+ sErr = rWork.sState; /* No, just return driver data */
+ else
+ sErr = U14ERR_DRIVCOMMS; /* Else never got there */
+ }
+ else
+ sErr = U14ERR_DRIVCOMMS;
+ }
+
+ if (sErr != U14ERR_NOERROR)
+ {
+ if (sErr != U14ERR_LOCKERR)
+ VirtualUnlock(pvBuff, dwLength); /* Release NT lock */
+ apAreas[hand][wArea] = NULL; /* Clear locations */
+ auAreas[hand][wArea] = 0;
+ }
+
+ return sErr;
+#endif
+#ifdef LINUX
+ else
+ {
+ struct transfer_area_desc td;
+ td.lpvBuff = (long long)((unsigned long)pvBuff);
+ td.wAreaNum = wArea;
+ td.dwLength = dwLength;
+ td.eSize = (short)bToHost; /* Use this for direction flag */
+ return CED_SetCircular(aHand1401[hand], &td);
+ }
+#endif
+}
+
+/****************************************************************************
+** Function GetCircBlk returns the size (& start offset) of the next
+** available block of circular data.
+****************************************************************************/
+U14API(int) U14GetCircBlk(short hand, unsigned short wArea, unsigned int *pdwOffs)
+{
+ int lErr = CheckHandle(hand);
+ if (lErr != U14ERR_NOERROR)
+ return lErr;
+
+ if (wArea >= MAX_TRANSAREAS) // Is this a valid area number?
+ return U14ERR_BADAREA;
+ else
+ {
+#ifdef _IS_WINDOWS_
+ PARAMBLK rWork;
+ TCSBLOCK csBlock;
+ unsigned int dwBytes;
+ csBlock.longs[0] = wArea; // Area number into control block
+ rWork.sState = U14ERR_DRIVCOMMS;
+ if (DeviceIoControl(aHand1401[hand], (unsigned int)U14_GETCIRCBLK, &csBlock, sizeof(TCSBLOCK), &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+ (dwBytes >= sizeof(PARAMBLK)))
+ lErr = rWork.sState;
+ else
+ lErr = U14ERR_DRIVCOMMS;
+
+ if (lErr == U14ERR_NOERROR) // Did everything go OK?
+ { // Yes, we can pass the results back
+ lErr = rWork.csBlock.longs[1]; // Return the block information
+ *pdwOffs = rWork.csBlock.longs[0]; // Offset is first in array
+ }
+#endif
+#ifdef LINUX
+ TCIRCBLOCK cb;
+ cb.nArea = wArea; // Area number into control block
+ cb.dwOffset = 0;
+ cb.dwSize = 0;
+ lErr = CED_GetCircBlock(aHand1401[hand], &cb);
+ if (lErr == U14ERR_NOERROR) // Did everything go OK?
+ { // Yes, we can pass the results back
+ lErr = cb.dwSize; // return the size
+ *pdwOffs = cb.dwOffset; // and the offset
+ }
+#endif
+ }
+ return lErr;
+}
+
+/****************************************************************************
+** Function FreeCircBlk marks the specified area of memory as free for
+** resuse for circular transfers and returns the size (& start
+** offset) of the next available block of circular data.
+****************************************************************************/
+U14API(int) U14FreeCircBlk(short hand, unsigned short wArea, unsigned int dwOffs, unsigned int dwSize,
+ unsigned int *pdwOffs)
+{
+ int lErr = CheckHandle(hand);
+ if (lErr != U14ERR_NOERROR)
+ return lErr;
+
+ if (wArea < MAX_TRANSAREAS) // Is this a valid area number
+ {
+#ifdef _IS_WINDOWS_
+ PARAMBLK rWork;
+ TCSBLOCK csBlock;
+ unsigned int dwBytes;
+ csBlock.longs[0] = wArea; // Area number into control block
+ csBlock.longs[1] = dwOffs;
+ csBlock.longs[2] = dwSize;
+ rWork.sState = U14ERR_DRIVCOMMS;
+ if (DeviceIoControl(aHand1401[hand], (unsigned int)U14_FREECIRCBLK, &csBlock, sizeof(TCSBLOCK),
+ &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+ (dwBytes >= sizeof(PARAMBLK)))
+ lErr = rWork.sState;
+ else
+ lErr = U14ERR_DRIVCOMMS;
+ if (lErr == U14ERR_NOERROR) // Did everything work OK?
+ { // Yes, we can pass the results back
+ lErr = rWork.csBlock.longs[1]; // Return the block information
+ *pdwOffs = rWork.csBlock.longs[0]; // Offset is first in array
+ }
+#endif
+#ifdef LINUX
+ TCIRCBLOCK cb;
+ cb.nArea = wArea; // Area number into control block
+ cb.dwOffset = dwOffs;
+ cb.dwSize = dwSize;
+
+ lErr = CED_FreeCircBlock(aHand1401[hand], &cb);
+ if (lErr == U14ERR_NOERROR) // Did everything work OK?
+ { // Yes, we can pass the results back
+ lErr = cb.dwSize; // Return the block information
+ *pdwOffs = cb.dwOffset; // Offset is first in array
+ }
+#endif
+ }
+ else
+ lErr = U14ERR_BADAREA;
+
+ return lErr;
+}
+
+/****************************************************************************
+** Transfer
+** Transfer moves data to 1401 or to host
+** Assumes memory is allocated and locked,
+** which it should be to get a pointer
+*****************************************************************************/
+static short Transfer(short hand, BOOL bTo1401, char* pData,
+ unsigned int dwSize, unsigned int dw1401, short eSz)
+{
+ char strcopy[MAXSTRLEN+1]; // to hold copy of work string
+ short sResult = U14SetTransArea(hand, 0, (void *)pData, dwSize, eSz);
+ if (sResult == U14ERR_NOERROR) // no error
+ {
+ sprintf(strcopy, // data offset is always 0
+ "TO%s,$%X,$%X,0;", bTo1401 ? "1401" : "HOST", dw1401, dwSize);
+
+ U14SendString(hand, strcopy); // send transfer string
+
+ sResult = U14CheckErr(hand); // Use ERR command to check for done
+ if (sResult > 0)
+ sResult = U14ERR_TOXXXERR; // If a 1401 error, use this code
+
+ U14UnSetTransfer(hand, 0);
+ }
+ return sResult;
+}
+
+/****************************************************************************
+** Function ToHost transfers data into the host from the 1401
+****************************************************************************/
+U14API(short) U14ToHost(short hand, char* pAddrHost, unsigned int dwSize,
+ unsigned int dw1401, short eSz)
+{
+ short sErr = CheckHandle(hand);
+ if ((sErr == U14ERR_NOERROR) && dwSize) // TOHOST is a constant
+ sErr = Transfer(hand, TOHOST, pAddrHost, dwSize, dw1401, eSz);
+ return sErr;
+}
+
+/****************************************************************************
+** Function To1401 transfers data into the 1401 from the host
+****************************************************************************/
+U14API(short) U14To1401(short hand, const char* pAddrHost,unsigned int dwSize,
+ unsigned int dw1401, short eSz)
+{
+ short sErr = CheckHandle(hand);
+ if ((sErr == U14ERR_NOERROR) && dwSize) // TO1401 is a constant
+ sErr = Transfer(hand, TO1401, (char*)pAddrHost, dwSize, dw1401, eSz);
+ return sErr;
+}
+
+/****************************************************************************
+** Function LdCmd Loads a command from a full path or just a file
+*****************************************************************************/
+#ifdef _IS_WINDOWS_
+#define file_exist(name) (_access(name, 0) != -1)
+#define file_open(name) _lopen(name, OF_READ)
+#define file_close(h) _lclose(h)
+#define file_seek(h, pos) _llseek(h, pos, FILE_BEGIN)
+#define file_read(h, buffer, size) (_lread(h, buffer, size) == size)
+#endif
+#ifdef LINUX
+#define file_exist(name) (access(name, F_OK) != -1)
+#define file_open(name) open(name, O_RDONLY)
+#define file_close(h) close(h)
+#define file_seek(h, pos) lseek(h, pos, SEEK_SET)
+#define file_read(h, buffer, size) (read(h, buffer, size) == (ssize_t)size)
+static unsigned int GetModuleFileName(void* dummy, char* buffer, int max)
+{
+ // The following works for Linux systems with a /proc file system.
+ char szProcPath[32];
+ sprintf(szProcPath, "/proc/%d/exe", getpid()); // attempt to read link
+ if (readlink(szProcPath, buffer, max) != -1)
+ {
+ dirname (buffer);
+ strcat (buffer, "/");
+ return strlen(buffer);
+ }
+ return 0;
+}
+#endif
+
+U14API(short) U14LdCmd(short hand, const char* command)
+{
+ char strcopy[MAXSTRLEN+1]; // to hold copy of work string
+ BOOL bGotIt = FALSE; // have we found the command file?
+ int iFHandle; // file handle of command
+#define FNSZ 260
+ char filnam[FNSZ]; // space to build name in
+ char szCmd[25]; // just the command name with extension
+
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+
+ if (strchr(command, '.') != NULL) // see if we have full name
+ {
+ if (file_exist(command)) // If the file exists
+ {
+ strcpy(filnam, command); // use name as is
+ bGotIt = TRUE; // Flag no more searching
+ }
+ else // not found, get file name for search
+ {
+ char* pStr = strrchr(command, PATHSEP); // Point to last separator
+ if (pStr != NULL) // Check we got it
+ {
+ pStr++; // move past the backslash
+ strcpy(szCmd, pStr); // copy file name as is
+ }
+ else
+ strcpy(szCmd, command); // use as is
+ }
+ }
+ else // File extension not supplied, so build the command file name
+ {
+ char szExt[8];
+ strcpy(szCmd, command); // Build command file name
+ ExtForType(asType1401[hand], szExt);// File extension string
+ strcat(szCmd, szExt); // add it to the end
+ }
+
+ // Next place to look is in the 1401 folder in the same place as the
+ // application was run from.
+ if (!bGotIt) // Still not got it?
+ {
+ unsigned int dwLen = GetModuleFileName(NULL, filnam, FNSZ); // Get app path
+ if (dwLen > 0) // and use it as path if found
+ {
+ char* pStr = strrchr(filnam, PATHSEP); // Point to last separator
+ if (pStr != NULL)
+ {
+ *(++pStr) = 0; // Terminate string there
+ if (strlen(filnam) < FNSZ-6) // make sure we have space
+ {
+ strcat(filnam, "1401" PATHSEPSTR); // add in 1401 subdir
+ strcat(filnam,szCmd);
+ bGotIt = (BOOL)file_exist(filnam); // See if file exists
+ }
+ }
+ }
+ }
+
+ // Next place to look is in whatever path is set by the 1401DIR environment
+ // variable, if it exists.
+ if (!bGotIt) // Need to do more searches?/
+ {
+ char* pStr = getenv("1401DIR"); // Try to find environment var
+ if (pStr != NULL) // and use it as path if found
+ {
+ strcpy(filnam, pStr); // Use path in environment
+ if (filnam[strlen(filnam)-1] != PATHSEP)// We need separator
+ strcat(filnam, PATHSEPSTR);
+ strcat(filnam, szCmd);
+ bGotIt = (BOOL)file_exist(filnam); // Got this one?
+ }
+ }
+
+ // Last place to look is the default location.
+ if (!bGotIt) // Need to do more searches?
+ {
+ strcpy(filnam, DEFCMDPATH); // Use default path
+ strcat(filnam, szCmd);
+ bGotIt = file_exist(filnam); // Got this one?
+ }
+
+ iFHandle = file_open(filnam);
+ if (iFHandle == -1)
+ sErr = U14ERR_NOFILE;
+ else
+ { // first read in the header block
+ CMDHEAD rCmdHead; // to hold the command header
+ if (file_read(iFHandle, &rCmdHead, sizeof(CMDHEAD)))
+ {
+ size_t nComSize = rCmdHead.wCmdSize;
+ char* pMem = malloc(nComSize);
+ if (pMem != NULL)
+ {
+ file_seek(iFHandle, sizeof(CMDHEAD));
+ if (file_read(iFHandle, pMem, (UINT)nComSize))
+ {
+ sErr = U14SetTransArea(hand, 0, (void *)pMem, (unsigned int)nComSize, ESZBYTES);
+ if (sErr == U14ERR_NOERROR)
+ {
+ sprintf(strcopy, "CLOAD,0,$%X;", (int)nComSize);
+ sErr = U14SendString(hand, strcopy);
+ if (sErr == U14ERR_NOERROR)
+ {
+ sErr = U14CheckErr(hand); // Use ERR to check for done
+ if (sErr > 0)
+ sErr = U14ERR_CLOADERR; // If an error, this code
+ }
+ U14UnSetTransfer(hand, 0); // release transfer area
+ }
+ }
+ else
+ sErr = U14ERR_READERR;
+ free(pMem);
+ }
+ else
+ sErr = U14ERR_HOSTSPACE; // memory allocate failed
+ }
+ else
+ sErr = U14ERR_READERR;
+
+ file_close(iFHandle); // close the file
+ }
+
+ return sErr;
+}
+
+
+/****************************************************************************
+** Ld
+** Loads a command into the 1401
+** Returns NOERROR code or a long with error in lo word and index of
+** command that failed in high word
+****************************************************************************/
+U14API(unsigned int) U14Ld(short hand, const char* vl, const char* str)
+{
+ unsigned int dwIndex = 0; // index to current command
+ long lErr = U14ERR_NOERROR; // what the error was that went wrong
+ char strcopy[MAXSTRLEN+1]; // stores unmodified str parameter
+ char szFExt[8]; // The command file extension
+ short sErr = CheckHandle(hand);
+ if (sErr != U14ERR_NOERROR)
+ return sErr;
+
+ ExtForType(asType1401[hand], szFExt); // File extension string
+ strcpy(strcopy, str); // to avoid changing original
+
+ // now break out one command at a time and see if loaded
+ if (*str) // if anything there
+ {
+ BOOL bDone = FALSE; // true when finished all commands
+ int iLoop1 = 0; // Point at start of string for command name
+ int iLoop2 = 0; // and at start of str parameter
+ do // repeat until end of str
+ {
+ char filnam[MAXSTRLEN+1]; // filename to use
+ char szFName[MAXSTRLEN+1]; // filename work string
+
+ if (!strcopy[iLoop1]) // at the end of the string?
+ bDone = TRUE; // set the finish flag
+
+ if (bDone || (strcopy[iLoop1] == ',')) // end of cmd?
+ {
+ U14LONG er[5]; // Used to read back error results
+ ++dwIndex; // Keep count of command number, first is 1
+ szFName[iLoop2]=(char)0; // null terminate name of command
+
+ strncpy(szLastName, szFName, sizeof(szLastName)); // Save for error info
+ szLastName[sizeof(szLastName)-1] = 0;
+ strncat(szLastName, szFExt, sizeof(szLastName)); // with extension included
+ szLastName[sizeof(szLastName)-1] = 0;
+
+ U14SendString(hand, szFName); // ask if loaded
+ U14SendString(hand, ";ERR;"); // add err return
+
+ lErr = U14LongsFrom1401(hand, er, 5);
+ if (lErr > 0)
+ {
+ lErr = U14ERR_NOERROR;
+ if (er[0] == 255) // if command not loaded at all
+ {
+ if (vl && *vl) // if we have a path name
+ {
+ strcpy(filnam, vl);
+ if (strchr("\\/:", filnam[strlen(filnam)-1]) == NULL)
+ strcat(filnam, PATHSEPSTR); // add separator if none found
+ strcat(filnam, szFName); // add the file name
+ strcat(filnam, szFExt); // and extension
+ }
+ else
+ strcpy(filnam, szFName); // simple name
+
+ lErr = U14LdCmd(hand, filnam); // load cmd
+ if (lErr != U14ERR_NOERROR) // spot any errors
+ bDone = TRUE; // give up if an error
+ }
+ }
+ else
+ bDone = TRUE; // give up if an error
+
+ iLoop2 = 0; // Reset pointer to command name string
+ ++iLoop1; // and move on through str parameter
+ }
+ else
+ szFName[iLoop2++] = strcopy[iLoop1++]; // no command end, so copy 1 char
+ }
+ while (!bDone);
+ }
+
+ if (lErr == U14ERR_NOERROR)
+ {
+ szLastName[0] = 0; // No error, so clean out command name here
+ return lErr;
+ }
+ else
+ return ((dwIndex<<16) | ((unsigned int)lErr & 0x0000FFFF));
+}
+
+// Initialise the library (if not initialised) and return the library version
+U14API(int) U14InitLib(void)
+{
+ int iRetVal = U14LIB_VERSION;
+ if (iAttached == 0) // only do this the first time please
+ {
+ int i;
+#ifdef _IS_WINDOWS_
+ int j;
+ unsigned int dwVersion = GetVersion();
+ bWindows9x = FALSE; // Assume not Win9x
+
+ if (dwVersion & 0x80000000) // if not windows NT
+ {
+ if ((LOBYTE(LOWORD(dwVersion)) < 4) && // if Win32s or...
+ (HIBYTE(LOWORD(dwVersion)) < 95)) // ...below Windows 95
+ iRetVal = 0; // We do not support this
+ else
+ bWindows9x = TRUE; // Flag we have Win9x
+ }
+#endif
+
+ for (i = 0; i < MAX1401; i++) // initialise the device area
+ {
+ aHand1401[i] = INVALID_HANDLE_VALUE; // Clear handle values
+ asType1401[i] = U14TYPEUNKNOWN; // and 1401 type codes
+ alTimeOutPeriod[i] = 3000; // 3 second timeouts
+#ifdef _IS_WINDOWS_
+#ifndef _WIN64
+ abUseNTDIOC[i] = (BOOL)!bWindows9x;
+#endif
+ aXferEvent[i] = NULL; // there are no Xfer events
+ for (j = 0; j < MAX_TRANSAREAS; j++) // Clear out locked area info
+ {
+ apAreas[i][j] = NULL;
+ auAreas[i][j] = 0;
+ }
+#endif
+ }
+ }
+ return iRetVal;
+}
+
+///--------------------------------------------------------------------------------
+/// Functions called when the library is loaded and unloaded to give us a chance to
+/// setup the library.
+
+
+#ifdef _IS_WINDOWS_
+#ifndef U14_NOT_DLL
+/****************************************************************************
+** FUNCTION: DllMain(HANDLE, unsigned int, LPVOID)
+** LibMain is called by Windows when the DLL is initialized, Thread Attached,
+** and other times. Refer to SDK documentation, as to the different ways this
+** may be called.
+****************************************************************************/
+INT APIENTRY DllMain(HANDLE hInst, unsigned int ul_reason_being_called, LPVOID lpReserved)
+{
+ int iRetVal = 1;
+
+ switch (ul_reason_being_called)
+ {
+ case DLL_PROCESS_ATTACH:
+ iRetVal = U14InitLib() > 0; // does nothing if iAttached != 0
+ ++iAttached; // count times attached
+ break;
+
+ case DLL_PROCESS_DETACH:
+ if (--iAttached == 0) // last man out?
+ U14CloseAll(); // release all open handles
+ break;
+ }
+ return iRetVal;
+
+ UNREFERENCED_PARAMETER(lpReserved);
+}
+#endif
+#endif
+#ifdef LINUX
+void __attribute__((constructor)) use1401_load(void)
+{
+ U14InitLib();
+ ++iAttached;
+}
+
+void __attribute__((destructor)) use1401_unload(void)
+{
+ if (--iAttached == 0) // last man out?
+ U14CloseAll(); // release all open handles
+}
+#endif
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
new file mode 100644
index 00000000000..a2f6957e7ee
--- /dev/null
+++ b/drivers/staging/comedi/Kconfig
@@ -0,0 +1,1293 @@
+config COMEDI
+ tristate "Data acquisition support (comedi)"
+ depends on m
+ ---help---
+ Enable support a wide range of data acquisition devices
+ for Linux.
+
+if COMEDI
+
+config COMEDI_DEBUG
+ bool "Comedi debugging"
+ ---help---
+ This is an option for use by developers; most people should
+ say N here. This enables comedi core and driver debugging.
+
+config COMEDI_DEFAULT_BUF_SIZE_KB
+ int "Comedi default initial asynchronous buffer size in KiB"
+ default "2048"
+ ---help---
+ This is the default asynchronous buffer size which is used for
+ commands running in the background in kernel space. This
+ defaults to 2048 KiB of memory so that a 16 channel card
+ running at 10 kHz has of 2-4 seconds of buffer.
+
+config COMEDI_DEFAULT_BUF_MAXSIZE_KB
+ int "Comedi default maximum asynchronous buffer size in KiB"
+ default "20480"
+ ---help---
+ This is the default maximum asynchronous buffer size which can
+ be requested by a userspace program without root privileges.
+ This is set to 20480 KiB so that a fast I/O card with 16
+ channels running at 100 kHz has 2-4 seconds of buffer.
+
+menuconfig COMEDI_MISC_DRIVERS
+ bool "Comedi misc drivers"
+ ---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
+ ---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
+ ---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"
+ ---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"
+ ---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"
+ ---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.
+
+config COMEDI_SSV_DNP
+ tristate "SSV Embedded Systems DIL/Net-PC support"
+ depends on X86_32 || COMPILE_TEST
+ ---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_MISC_DRIVERS
+
+menuconfig COMEDI_ISA_DRIVERS
+ bool "Comedi ISA and PC/104 drivers"
+ ---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
+
+config COMEDI_PCL711
+ tristate "Advantech PCL-711/711b and ADlink ACL-8112 ISA card support"
+ ---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
+ ---help---
+ Enable support for ISA and PC/104 based 8255 digital i/o boards. This
+ driver provides a legacy comedi driver wrapper for the generic 8255
+ support driver.
+
+ Supported boards include:
+ Advantech PCL-724 24 channels
+ Advantech PCL-722 144 (or 96) channels
+ Advantech PCL-731 48 channels
+ ADlink ACL-7122 144 (or 96) channels
+ ADlink ACL-7124 24 channels
+ ADlink PET-48DIO 48 channels
+ WinSystems PCM-IO48 48 channels (PC/104)
+
+ To compile this driver as a module, choose M here: the module will be
+ called pcl724.
+
+config COMEDI_PCL726
+ tristate "Advantech PCL-726 and compatible ISA card support"
+ ---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 "Simple Digital I/O board support (8-bit ports)"
+ ---help---
+ Enable support for various simple ISA or PC/104 Digital I/O boards.
+ These boards all use 8-bit I/O ports.
+
+ Advantech PCL-730 iso - 16 in/16 out ttl - 16 in/16 out
+ ICP ISO-730 iso - 16 in/16 out ttl - 16 in/16 out
+ ADlink ACL-7130 iso - 16 in/16 out ttl - 16 in/16 out
+ Advantech PCM-3730 iso - 8 in/8 out ttl - 16 in/16 out
+ Advantech PCL-725 iso - 8 in/8 out
+ ICP P8R8-DIO iso - 8 in/8 out
+ ADlink ACL-7225b iso - 16 in/16 out
+ ICP P16R16-DIO iso - 16 in/16 out
+ Advantech PCL-733 iso - 32 in
+ Advantech PCL-734 iso - 32 out
+ Diamond Systems OPMM-1616-XT iso - 16 in/16 out
+ Diamond Systems PEARL-MM-P iso - 16 out
+
+ 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 && ISA_DMA_API
+ select COMEDI_FC
+ ---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 && ISA_DMA_API
+ select COMEDI_FC
+ ---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 && ISA_DMA_API
+ select COMEDI_FC
+ ---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
+ ---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_AMPLC_DIO200_ISA
+ tristate "Amplicon PC212E/PC214E/PC215E/PC218E/PC272E"
+ select COMEDI_AMPLC_DIO200
+ ---help---
+ Enable support for Amplicon PC212E, PC214E, PC215E, PC218E and
+ PC272E ISA DIO boards
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_dio200.
+
+config COMEDI_AMPLC_PC236_ISA
+ tristate "Amplicon PC36AT DIO board support"
+ select COMEDI_AMPLC_PC236
+ ---help---
+ Enable support for Amplicon PC36AT ISA DIO board.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pc236.
+
+config COMEDI_AMPLC_PC263_ISA
+ tristate "Amplicon PC263 relay board support"
+ ---help---
+ Enable support for Amplicon PC263 ISA relay board. This board has
+ 16 reed relay output channels.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pc263.
+
+config COMEDI_RTI800
+ tristate "Analog Devices RTI-800/815 ISA card support"
+ ---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"
+ ---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_DAC02
+ tristate "Keithley Metrabyte DAC02 compatible ISA card support"
+ ---help---
+ Enable support for Keithley Metrabyte DAC02 compatible ISA cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called dac02.
+
+config COMEDI_DAS16M1
+ tristate "MeasurementComputing CIO-DAS16/M1DAS-16 ISA card support"
+ select COMEDI_8255
+ select COMEDI_FC
+ ---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_DAS08_ISA
+ tristate "DAS-08 compatible ISA and PC/104 card support"
+ select COMEDI_DAS08
+ ---help---
+ Enable support for Keithley Metrabyte/ComputerBoards DAS08
+ and compatible ISA and PC/104 cards:
+ Keithley Metrabyte/ComputerBoards DAS08, DAS08-PGM, DAS08-PGH,
+ DAS08-PGL, DAS08-AOH, DAS08-AOL, DAS08-AOM, DAS08/JR-AO,
+ DAS08/JR-16-AO, PC104-DAS08, DAS08/JR/16.
+
+ To compile this driver as a module, choose M here: the module will be
+ called das08_isa.
+
+config COMEDI_DAS16
+ tristate "DAS-16 compatible ISA and PC/104 card support"
+ depends on ISA_DMA_API
+ select COMEDI_8255
+ select COMEDI_FC
+ ---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
+ ---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 && ISA_DMA_API
+ select COMEDI_FC
+ ---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"
+ ---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"
+ ---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"
+ ---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"
+ ---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"
+ ---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"
+ ---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 && ISA_DMA_API
+ ---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"
+ ---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_UNIOXX5
+ tristate "Fastwel UNIOxx-5 analog and digital io board support"
+ ---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_FL512
+ tristate "FL512 ISA card support"
+ ---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
+ ---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"
+ ---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_II_PCI20KC
+ tristate "Intelligent Instruments PCI-20001C carrier support"
+ ---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_C6XDIGIO
+ tristate "Mechatronic Systems Inc. C6x_DIGIO DSP daughter card support"
+ ---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"
+ ---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"
+ ---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"
+ select COMEDI_FC
+ depends on VIRT_TO_BUS && ISA_DMA_API
+ ---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"
+ ---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"
+ select COMEDI_8255
+ select COMEDI_NI_TIO
+ select COMEDI_FC
+ ---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-MIO-16/AT-MIO-16D series ISA card support"
+ select COMEDI_8255
+ ---help---
+ Enable support for National Instruments AT-MIO-16/AT-MIO-16D cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_atmio16d.
+
+config COMEDI_NI_LABPC_ISA
+ tristate "NI Lab-PC and compatibles ISA support"
+ select COMEDI_NI_LABPC
+ select COMEDI_NI_LABPC_ISADMA if ISA_DMA_API && VIRT_TO_BUS
+ ---help---
+ Enable support for National Instruments Lab-PC and compatibles
+ Lab-PC-1200, Lab-PC-1200AI, Lab-PC+.
+ 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.
+
+config COMEDI_PCMAD
+ tristate "Winsystems PCM-A/D12 and PCM-A/D16 PC/104 board support"
+ ---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"
+ ---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"
+ ---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"
+ ---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"
+ ---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_S526
+ tristate "Sensoray s526 support"
+ ---help---
+ Enable support for Sensoray s526
+
+ To compile this driver as a module, choose M here: the module will be
+ called s526.
+
+endif # COMEDI_ISA_DRIVERS
+
+menuconfig COMEDI_PCI_DRIVERS
+ bool "Comedi PCI drivers"
+ depends on PCI
+ ---help---
+ Enable support for comedi PCI drivers.
+
+if COMEDI_PCI_DRIVERS
+
+config COMEDI_8255_PCI
+ tristate "Generic PCI based 8255 digital i/o board support"
+ select COMEDI_8255
+ ---help---
+ Enable support for PCI based 8255 digital i/o boards. This driver
+ provides a PCI wrapper around the generic 8255 driver.
+
+ Supported boards:
+ ADlink - PCI-7224, PCI-7248, and PCI-7296
+ Measurement Computing - PCI-DIO24, PCI-DIO24H, PCI-DIO48H and
+ PCI-DIO96H
+ National Instruments - PCI-DIO-96, PCI-DIO-96B, PXI-6508, PCI-6503,
+ PCI-6503B, PCI-6503X, and PXI-6503
+
+ To compile this driver as a module, choose M here: the module will
+ be called 8255_pci.
+
+config COMEDI_ADDI_WATCHDOG
+ tristate
+ ---help---
+ Provides support for the watchdog subdevice found on many ADDI-DATA
+ boards. This module will be automatically selected when needed. The
+ module will be called addi_watchdog.
+
+config COMEDI_ADDI_APCI_035
+ tristate "ADDI-DATA APCI_035 support"
+ ---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"
+ ---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"
+ ---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-1016/1516/2016 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---help---
+ Enable support for ADDI-DATA APCI-1016, APCI-1516 and APCI-2016 boards.
+ These are 16 channel, optically isolated, digital I/O boards. The 1516
+ and 2016 boards also have a watchdog for resetting the outputs to "0".
+
+ 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"
+ select COMEDI_ADDI_WATCHDOG
+ ---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"
+ ---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_2032
+ tristate "ADDI-DATA APCI_2032 support"
+ select COMEDI_ADDI_WATCHDOG
+ ---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"
+ select COMEDI_ADDI_WATCHDOG
+ ---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_3120
+ tristate "ADDI-DATA APCI_3120/3001 support"
+ depends on VIRT_TO_BUS
+ select COMEDI_FC
+ ---help---
+ Enable support for ADDI-DATA APCI_3120/3001 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"
+ ---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"
+ ---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"
+ ---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_PCI7X3X
+ tristate "ADLink PCI-723X/743X isolated digital i/o board support"
+ ---help---
+ Enable support for ADlink PCI-723X/743X isolated digital i/o boards.
+ Supported boards include the 32-channel PCI-7230 (16 in/16 out),
+ PCI-7233 (32 in), and PCI-7234 (32 out) as well as the 64-channel
+ PCI-7432 (32 in/32 out), PCI-7433 (64 in), and PCI-7434 (64 out).
+
+ To compile this driver as a module, choose M here: the module will be
+ called adl_pci7x3x.
+
+config COMEDI_ADL_PCI8164
+ tristate "ADLink PCI-8164 4 Axes Motion Control board support"
+ ---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
+ ---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
+ ---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"
+ select COMEDI_FC
+ ---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"
+ ---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_PCI1724
+ tristate "Advantech PCI-1724U support"
+ ---help---
+ Enable support for Advantech PCI-1724U cards. These are 32-channel
+ analog output cards with voltage and current loop output ranges and
+ 14-bit resolution.
+
+ To compile this driver as a module, choose M here: the module will be
+ called adv_pci1724.
+
+config COMEDI_ADV_PCI_DIO
+ tristate "Advantech PCI DIO card support"
+ select COMEDI_8255
+ ---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_PCI
+ tristate "Amplicon PCI215/PCI272/PCIe215/PCIe236/PCIe296 DIO support"
+ select COMEDI_AMPLC_DIO200
+ ---help---
+ Enable support for Amplicon PCI215, PCI272, PCIe215, PCIe236
+ and PCIe296 DIO boards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_dio200_pci.
+
+config COMEDI_AMPLC_PC236_PCI
+ tristate "Amplicon PCI236 DIO board support"
+ select COMEDI_AMPLC_PC236
+ ---help---
+ Enable support for Amplicon PCI236 DIO board.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pc236.
+
+config COMEDI_AMPLC_PC263_PCI
+ tristate "Amplicon PCI263 relay board support"
+ ---help---
+ Enable support for Amplicon PCI263 relay board. This is a PCI board
+ with 16 reed relay output channels.
+
+ To compile this driver as a module, choose M here: the module will be
+ called amplc_pci263.
+
+config COMEDI_AMPLC_PCI224
+ tristate "Amplicon PCI224 and PCI234 support"
+ select COMEDI_FC
+ ---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
+ ---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"
+ ---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_DAS08_PCI
+ tristate "DAS-08 PCI support"
+ select COMEDI_DAS08
+ ---help---
+ Enable support for PCI DAS-08 cards.
+
+ To compile this driver as a module, choose M here: the module will be
+ called das08_pci.
+
+config COMEDI_DT3000
+ tristate "Data Translation DT3000 series support"
+ select COMEDI_FC
+ ---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"
+ ---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_GSC_HPDI
+ tristate "General Standards PCI-HPDI32 / PMC-HPDI32 support"
+ select COMEDI_FC
+ ---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_MF6X4
+ tristate "Humusoft MF634 and MF624 DAQ Card support"
+ ---help---
+ This driver supports both Humusoft MF634 and MF624 Data acquisition
+ cards. The legacy Humusoft MF614 card is not supported.
+
+config COMEDI_ICP_MULTI
+ tristate "Inova ICP_MULTI support"
+ ---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_DAQBOARD2000
+ tristate "IOtech DAQboard/2000 support"
+ select COMEDI_8255
+ ---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"
+ ---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"
+ ---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
+ ---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
+ ---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
+ ---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_PCIMDAS
+ tristate "MeasurementComputing PCIM-DAS1602/16 support"
+ select COMEDI_8255
+ ---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
+ ---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"
+ ---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"
+ ---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"
+ ---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 HAS_DMA
+ select COMEDI_MITE
+ ---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 HAS_DMA
+ select COMEDI_NI_TIOCMD
+ ---help---
+ Enable support for National Instruments PCI-6601 (ni_660x), PCI-6602,
+ PXI-6602, PXI-6608 and PXI-6624.
+
+ 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 HAS_DMA
+ select COMEDI_MITE
+ ---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_LABPC_PCI
+ tristate "NI Lab-PC PCI-1200 support"
+ depends on HAS_DMA
+ select COMEDI_NI_LABPC
+ select COMEDI_MITE
+ ---help---
+ Enable support for National Instruments Lab-PC PCI-1200.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni_labpc_pci.
+
+config COMEDI_NI_PCIDIO
+ tristate "NI PCI-DIO32HS, PCI-6533, PCI-6534 support"
+ depends on HAS_DMA
+ select COMEDI_MITE
+ select COMEDI_8255
+ ---help---
+ Enable support for National Instruments PCI-DIO-32HS, PXI-6533,
+ PCI-6533 and PCI-6534
+
+ 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 HAS_DMA
+ select COMEDI_NI_TIOCMD
+ select COMEDI_8255
+ select COMEDI_FC
+ ---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"
+ ---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_S626
+ tristate "Sensoray 626 support"
+ select COMEDI_FC
+ ---help---
+ Enable support for Sensoray 626
+
+ To compile this driver as a module, choose M here: the module will be
+ called s626.
+
+config COMEDI_MITE
+ depends on HAS_DMA
+ select COMEDI_FC
+ tristate
+
+config COMEDI_NI_TIOCMD
+ tristate
+ depends on HAS_DMA
+ select COMEDI_NI_TIO
+ select COMEDI_MITE
+
+endif # COMEDI_PCI_DRIVERS
+
+menuconfig COMEDI_PCMCIA_DRIVERS
+ bool "Comedi PCMCIA drivers"
+ depends on PCMCIA
+ ---help---
+ Enable support for comedi PCMCIA drivers.
+
+if COMEDI_PCMCIA_DRIVERS
+
+config COMEDI_CB_DAS16_CS
+ tristate "CB DAS16 series PCMCIA support"
+ ---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
+ ---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"
+ ---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"
+ select COMEDI_8255
+ ---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"
+ select COMEDI_NI_LABPC
+ ---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"
+ select COMEDI_NI_TIO
+ select COMEDI_8255
+ select COMEDI_FC
+ ---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"
+ select COMEDI_FC
+ ---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
+ bool "Comedi USB drivers"
+ depends on USB
+ ---help---
+ Enable support for comedi USB drivers.
+
+if COMEDI_USB_DRIVERS
+
+config COMEDI_DT9812
+ tristate "DataTranslation DT9812 USB module support"
+ ---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"
+ ---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
+ ---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
+ ---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"
+ ---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
+
+config COMEDI_8255
+ tristate "Generic 8255 support"
+ ---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 based 8255 boards use the 8255_pci driver as a
+ wrapper around this driver.
+
+ To compile this driver as a module, choose M here: the module will be
+ called 8255.
+
+config COMEDI_FC
+ tristate
+
+config COMEDI_AMPLC_DIO200
+ tristate
+
+config COMEDI_AMPLC_PC236
+ tristate
+ select COMEDI_8255
+
+config COMEDI_DAS08
+ tristate
+ select COMEDI_8255
+
+config COMEDI_NI_LABPC
+ tristate
+ select COMEDI_8255
+ select COMEDI_FC
+
+config COMEDI_NI_LABPC_ISADMA
+ tristate
+
+config COMEDI_NI_TIO
+ tristate
+
+endif # COMEDI
diff --git a/drivers/staging/comedi/Makefile b/drivers/staging/comedi/Makefile
new file mode 100644
index 00000000000..fae2d909000
--- /dev/null
+++ b/drivers/staging/comedi/Makefile
@@ -0,0 +1,14 @@
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+comedi-y := comedi_fops.o range.o drivers.o \
+ comedi_buf.o
+comedi-$(CONFIG_COMEDI_PCI_DRIVERS) += comedi_pci.o
+comedi-$(CONFIG_COMEDI_PCMCIA_DRIVERS) += comedi_pcmcia.o
+comedi-$(CONFIG_COMEDI_USB_DRIVERS) += comedi_usb.o
+comedi-$(CONFIG_PROC_FS) += proc.o
+comedi-$(CONFIG_COMPAT) += comedi_compat32.o
+
+obj-$(CONFIG_COMEDI) += comedi.o
+
+obj-$(CONFIG_COMEDI) += kcomedilib/
+obj-$(CONFIG_COMEDI) += drivers/
diff --git a/drivers/staging/comedi/TODO b/drivers/staging/comedi/TODO
new file mode 100644
index 00000000000..b68fbdb5eeb
--- /dev/null
+++ b/drivers/staging/comedi/TODO
@@ -0,0 +1,11 @@
+TODO:
+ - checkpatch.pl cleanups
+ - Lindent
+ - remove all wrappers
+ - audit userspace interface
+ - cleanup the individual comedi drivers as well
+
+Please send patches to Greg Kroah-Hartman <greg@kroah.com> and
+copy:
+ Ian Abbott <abbotti@mev.co.uk>
+ H Hartley Sweeten <hsweeten@visionengravers.com>
diff --git a/drivers/staging/comedi/comedi.h b/drivers/staging/comedi/comedi.h
new file mode 100644
index 00000000000..6bbbe5b0895
--- /dev/null
+++ b/drivers/staging/comedi/comedi.h
@@ -0,0 +1,979 @@
+/*
+ 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 <ds@schleef.org>
+
+ 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.
+*/
+
+#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
+
+/*
+ * NOTE: 'comedi_config --init-data' is deprecated
+ *
+ * The following indexes in the config options were used by
+ * comedi_config to pass firmware blobs from user space to the
+ * comedi drivers. The request_firmware() hotplug interface is
+ * now used by all comedi drivers instead.
+ */
+
+/* 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,
+
+ INSN_CONFIG_SERIAL_CLOCK = 26, /*ALPHA*/
+ 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
+};
+
+/*
+ * Settings for INSN_CONFIG_DIGITAL_TRIG:
+ * data[0] = INSN_CONFIG_DIGITAL_TRIG
+ * data[1] = trigger ID
+ * data[2] = configuration operation
+ * data[3] = configuration parameter 1
+ * data[4] = configuration parameter 2
+ * data[5] = configuration parameter 3
+ *
+ * operation parameter 1 parameter 2 parameter 3
+ * --------------------------------- ----------- ----------- -----------
+ * COMEDI_DIGITAL_TRIG_DISABLE
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES left-shift rising-edges falling-edges
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS left-shift high-levels low-levels
+ *
+ * COMEDI_DIGITAL_TRIG_DISABLE returns the trigger to its default, inactive,
+ * unconfigured state.
+ *
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES sets the rising and/or falling edge inputs
+ * that each can fire the trigger.
+ *
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS sets a combination of high and/or low
+ * level inputs that can fire the trigger.
+ *
+ * "left-shift" is useful if the trigger has more than 32 inputs to specify the
+ * first input for this configuration.
+ *
+ * Some sequences of INSN_CONFIG_DIGITAL_TRIG instructions may have a (partly)
+ * accumulative effect, depending on the low-level driver. This is useful
+ * when setting up a trigger that has more than 32 inputs or has a combination
+ * of edge and level triggered inputs.
+ */
+enum comedi_digital_trig_op {
+ COMEDI_DIGITAL_TRIG_DISABLE = 0,
+ COMEDI_DIGITAL_TRIG_ENABLE_EDGES = 1,
+ COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = 2
+};
+
+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 *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/output */
+#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 */
+ /* the following are "enhanced" clock sources for PCIe models */
+ AMPLC_DIO_CLK_VCC, /* clock input HIGH */
+ AMPLC_DIO_CLK_GND, /* clock input LOW */
+ AMPLC_DIO_CLK_PAT_PRESENT, /* "pattern present" signal */
+ AMPLC_DIO_CLK_20MHZ /* 20 MHz internal clock */
+};
+
+/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
+ * timer subdevice on some Amplicon DIO PCIe boards (amplc_dio200 driver). */
+enum amplc_dio_ts_clock_src {
+ AMPLC_DIO_TS_CLK_1GHZ, /* 1 ns period with 20 ns granularity */
+ AMPLC_DIO_TS_CLK_1MHZ, /* 1 us period */
+ AMPLC_DIO_TS_CLK_1KHZ /* 1 ms period */
+};
+
+/* 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,
+ /* the following are "enhanced" gate sources for PCIe models */
+ AMPLC_DIO_GAT_NGATN = 6, /* negated per channel gate input */
+ AMPLC_DIO_GAT_OUTNM2, /* non-negated output of counter
+ channel minus 2 */
+ AMPLC_DIO_GAT_PAT_PRESENT, /* "pattern present" signal */
+ AMPLC_DIO_GAT_PAT_OCCURRED, /* "pattern occurred" latched */
+ AMPLC_DIO_GAT_PAT_GONE, /* "pattern gone away" latched */
+ AMPLC_DIO_GAT_NPAT_PRESENT, /* negated "pattern present" */
+ AMPLC_DIO_GAT_NPAT_OCCURRED, /* negated "pattern occurred" */
+ AMPLC_DIO_GAT_NPAT_GONE /* negated "pattern gone away" */
+};
+
+#endif /* _COMEDI_H */
diff --git a/drivers/staging/comedi/comedi_buf.c b/drivers/staging/comedi/comedi_buf.c
new file mode 100644
index 00000000000..df4a9c4bca3
--- /dev/null
+++ b/drivers/staging/comedi/comedi_buf.c
@@ -0,0 +1,511 @@
+/*
+ * comedi_buf.c
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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 <linux/vmalloc.h>
+#include <linux/slab.h>
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+#ifdef PAGE_KERNEL_NOCACHE
+#define COMEDI_PAGE_PROTECTION PAGE_KERNEL_NOCACHE
+#else
+#define COMEDI_PAGE_PROTECTION PAGE_KERNEL
+#endif
+
+static void comedi_buf_map_kref_release(struct kref *kref)
+{
+ struct comedi_buf_map *bm =
+ container_of(kref, struct comedi_buf_map, refcount);
+ struct comedi_buf_page *buf;
+ unsigned int i;
+
+ if (bm->page_list) {
+ for (i = 0; i < bm->n_pages; i++) {
+ buf = &bm->page_list[i];
+ clear_bit(PG_reserved,
+ &(virt_to_page(buf->virt_addr)->flags));
+ if (bm->dma_dir != DMA_NONE) {
+#ifdef CONFIG_HAS_DMA
+ dma_free_coherent(bm->dma_hw_dev,
+ PAGE_SIZE,
+ buf->virt_addr,
+ buf->dma_addr);
+#endif
+ } else {
+ free_page((unsigned long)buf->virt_addr);
+ }
+ }
+ vfree(bm->page_list);
+ }
+ if (bm->dma_dir != DMA_NONE)
+ put_device(bm->dma_hw_dev);
+ kfree(bm);
+}
+
+static void __comedi_buf_free(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_buf_map *bm;
+ unsigned long flags;
+
+ if (async->prealloc_buf) {
+ vunmap(async->prealloc_buf);
+ async->prealloc_buf = NULL;
+ async->prealloc_bufsz = 0;
+ }
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm = async->buf_map;
+ async->buf_map = NULL;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ comedi_buf_map_put(bm);
+}
+
+static void __comedi_buf_alloc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned n_pages)
+{
+ struct comedi_async *async = s->async;
+ struct page **pages = NULL;
+ struct comedi_buf_map *bm;
+ struct comedi_buf_page *buf;
+ unsigned long flags;
+ unsigned i;
+
+ if (!IS_ENABLED(CONFIG_HAS_DMA) && s->async_dma_dir != DMA_NONE) {
+ dev_err(dev->class_dev,
+ "dma buffer allocation not supported\n");
+ return;
+ }
+
+ bm = kzalloc(sizeof(*async->buf_map), GFP_KERNEL);
+ if (!bm)
+ return;
+
+ kref_init(&bm->refcount);
+ spin_lock_irqsave(&s->spin_lock, flags);
+ async->buf_map = bm;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+ bm->dma_dir = s->async_dma_dir;
+ if (bm->dma_dir != DMA_NONE)
+ /* Need ref to hardware device to free buffer later. */
+ bm->dma_hw_dev = get_device(dev->hw_dev);
+
+ bm->page_list = vzalloc(sizeof(*buf) * n_pages);
+ if (bm->page_list)
+ pages = vmalloc(sizeof(struct page *) * n_pages);
+
+ if (!pages)
+ return;
+
+ for (i = 0; i < n_pages; i++) {
+ buf = &bm->page_list[i];
+ if (bm->dma_dir != DMA_NONE)
+#ifdef CONFIG_HAS_DMA
+ buf->virt_addr = dma_alloc_coherent(bm->dma_hw_dev,
+ PAGE_SIZE,
+ &buf->dma_addr,
+ GFP_KERNEL |
+ __GFP_COMP);
+#else
+ break;
+#endif
+ else
+ buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);
+ if (!buf->virt_addr)
+ break;
+
+ set_bit(PG_reserved, &(virt_to_page(buf->virt_addr)->flags));
+
+ pages[i] = virt_to_page(buf->virt_addr);
+ }
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm->n_pages = i;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ /* vmap the prealloc_buf if all the pages were allocated */
+ if (i == n_pages)
+ async->prealloc_buf = vmap(pages, n_pages, VM_MAP,
+ COMEDI_PAGE_PROTECTION);
+
+ vfree(pages);
+}
+
+void comedi_buf_map_get(struct comedi_buf_map *bm)
+{
+ if (bm)
+ kref_get(&bm->refcount);
+}
+
+int comedi_buf_map_put(struct comedi_buf_map *bm)
+{
+ if (bm)
+ return kref_put(&bm->refcount, comedi_buf_map_kref_release);
+ return 1;
+}
+
+/* returns s->async->buf_map and increments its kref refcount */
+struct comedi_buf_map *
+comedi_buf_map_from_subdev_get(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_buf_map *bm = NULL;
+ unsigned long flags;
+
+ if (!async)
+ return NULL;
+
+ spin_lock_irqsave(&s->spin_lock, flags);
+ bm = async->buf_map;
+ /* only want it if buffer pages allocated */
+ if (bm && bm->n_pages)
+ comedi_buf_map_get(bm);
+ else
+ bm = NULL;
+ spin_unlock_irqrestore(&s->spin_lock, flags);
+
+ return bm;
+}
+
+bool comedi_buf_is_mmapped(struct comedi_subdevice *s)
+{
+ struct comedi_buf_map *bm = s->async->buf_map;
+
+ return bm && (atomic_read(&bm->refcount.refcount) > 1);
+}
+
+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 */
+ __comedi_buf_free(dev, s);
+
+ /* allocate new buffer */
+ if (new_size) {
+ unsigned n_pages = new_size >> PAGE_SHIFT;
+
+ __comedi_buf_alloc(dev, s, n_pages);
+
+ if (!async->prealloc_buf) {
+ /* allocation failed */
+ __comedi_buf_free(dev, s);
+ return -ENOMEM;
+ }
+ }
+ async->prealloc_bufsz = new_size;
+
+ return 0;
+}
+
+void comedi_buf_reset(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->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 unsigned int comedi_buf_write_n_available(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
+
+ return free_end - async->buf_write_alloc_count;
+}
+
+static unsigned int __comedi_buf_write_alloc(struct comedi_subdevice *s,
+ unsigned int nbytes,
+ int strict)
+{
+ struct comedi_async *async = s->async;
+ unsigned int available = comedi_buf_write_n_available(s);
+
+ if (nbytes > available)
+ nbytes = strict ? 0 : available;
+
+ async->buf_write_alloc_count += nbytes;
+
+ /*
+ * ensure the async buffer 'counts' are read and updated
+ * before we write data to the write-alloc'ed buffer space
+ */
+ smp_mb();
+
+ return nbytes;
+}
+
+/* allocates chunk for the writer from free buffer space */
+unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ return __comedi_buf_write_alloc(s, nbytes, 0);
+}
+EXPORT_SYMBOL_GPL(comedi_buf_write_alloc);
+
+/*
+ * munging is applied to data by core as it passes between user
+ * and kernel space
+ */
+static unsigned int comedi_buf_munge(struct comedi_subdevice *s,
+ unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int count = 0;
+ const unsigned num_sample_bytes = bytes_per_sample(s);
+
+ if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) {
+ async->munge_count += num_bytes;
+ count = num_bytes;
+ } else {
+ /* don't munge partial samples */
+ num_bytes -= num_bytes % num_sample_bytes;
+ while (count < num_bytes) {
+ int block_size = num_bytes - count;
+ unsigned int buf_end;
+
+ buf_end = async->prealloc_bufsz - async->munge_ptr;
+ if (block_size > buf_end)
+ block_size = buf_end;
+
+ s->munge(s->device, s,
+ async->prealloc_buf + async->munge_ptr,
+ block_size, async->munge_chan);
+
+ /*
+ * ensure data is munged in buffer before the
+ * async buffer munge_count is incremented
+ */
+ smp_wmb();
+
+ 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;
+ }
+ }
+
+ return count;
+}
+
+unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+
+ return async->buf_write_alloc_count - async->buf_write_count;
+}
+
+/* transfers a chunk from writer to filled buffer space */
+unsigned int comedi_buf_write_free(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int allocated = comedi_buf_write_n_allocated(s);
+
+ if (nbytes > allocated)
+ nbytes = allocated;
+
+ async->buf_write_count += nbytes;
+ async->buf_write_ptr += nbytes;
+ comedi_buf_munge(s, 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_GPL(comedi_buf_write_free);
+
+unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ unsigned num_bytes;
+
+ if (!async)
+ return 0;
+
+ num_bytes = async->munge_count - async->buf_read_count;
+
+ /*
+ * ensure the async buffer 'counts' are read before we
+ * attempt to read data from the buffer
+ */
+ smp_rmb();
+
+ return num_bytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_n_available);
+
+/* allocates a chunk for the reader from filled (and munged) buffer space */
+unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int available;
+
+ available = async->munge_count - async->buf_read_alloc_count;
+ if (nbytes > available)
+ nbytes = available;
+
+ async->buf_read_alloc_count += nbytes;
+
+ /*
+ * ensure the async buffer 'counts' are read before we
+ * attempt to read data from the read-alloc'ed buffer space
+ */
+ smp_rmb();
+
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_alloc);
+
+static unsigned int comedi_buf_read_n_allocated(struct comedi_async *async)
+{
+ return async->buf_read_alloc_count - async->buf_read_count;
+}
+
+/* transfers control of a chunk from reader to free buffer space */
+unsigned int comedi_buf_read_free(struct comedi_subdevice *s,
+ unsigned int nbytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int allocated;
+
+ /*
+ * ensure data has been read out of buffer before
+ * the async read count is incremented
+ */
+ smp_mb();
+
+ allocated = comedi_buf_read_n_allocated(async);
+ if (nbytes > allocated)
+ nbytes = allocated;
+
+ async->buf_read_count += nbytes;
+ async->buf_read_ptr += nbytes;
+ async->buf_read_ptr %= async->prealloc_bufsz;
+ return nbytes;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_read_free);
+
+int comedi_buf_put(struct comedi_subdevice *s, unsigned short x)
+{
+ struct comedi_async *async = s->async;
+ unsigned int n = __comedi_buf_write_alloc(s, sizeof(short), 1);
+
+ if (n < sizeof(short)) {
+ async->events |= COMEDI_CB_ERROR;
+ return 0;
+ }
+ *(unsigned short *)(async->prealloc_buf + async->buf_write_ptr) = x;
+ comedi_buf_write_free(s, sizeof(short));
+ return 1;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_put);
+
+int comedi_buf_get(struct comedi_subdevice *s, unsigned short *x)
+{
+ struct comedi_async *async = s->async;
+ unsigned int n = comedi_buf_read_n_available(s);
+
+ if (n < sizeof(short))
+ return 0;
+ comedi_buf_read_alloc(s, sizeof(short));
+ *x = *(unsigned short *)(async->prealloc_buf + async->buf_read_ptr);
+ comedi_buf_read_free(s, sizeof(short));
+ return 1;
+}
+EXPORT_SYMBOL_GPL(comedi_buf_get);
+
+void comedi_buf_memcpy_to(struct comedi_subdevice *s, unsigned int offset,
+ const void *data, unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ 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_GPL(comedi_buf_memcpy_to);
+
+void comedi_buf_memcpy_from(struct comedi_subdevice *s, unsigned int offset,
+ void *dest, unsigned int nbytes)
+{
+ void *src;
+ struct comedi_async *async = s->async;
+ 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_GPL(comedi_buf_memcpy_from);
diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c
new file mode 100644
index 00000000000..1e9da405d83
--- /dev/null
+++ b/drivers/staging/comedi/comedi_compat32.c
@@ -0,0 +1,452 @@
+/*
+ comedi/comedi_compat32.c
+ 32-bit ioctl compatibility for 64-bit comedi kernel module.
+
+ Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
+ Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 <linux/uaccess.h>
+#include <linux/compat.h>
+#include <linux/fs.h>
+#include "comedi.h"
+#include "comedi_compat32.h"
+
+#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->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);
+}
diff --git a/drivers/staging/comedi/comedi_compat32.h b/drivers/staging/comedi/comedi_compat32.h
new file mode 100644
index 00000000000..28e3c305903
--- /dev/null
+++ b/drivers/staging/comedi/comedi_compat32.h
@@ -0,0 +1,37 @@
+/*
+ comedi/comedi_compat32.h
+ 32-bit ioctl compatibility for 64-bit comedi kernel module.
+
+ Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
+ Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _COMEDI_COMPAT32_H
+#define _COMEDI_COMPAT32_H
+
+#ifdef CONFIG_COMPAT
+
+struct file;
+extern long comedi_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+
+#else /* CONFIG_COMPAT */
+
+#define comedi_compat_ioctl 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 00000000000..9d99fb3c18a
--- /dev/null
+++ b/drivers/staging/comedi/comedi_fops.c
@@ -0,0 +1,2661 @@
+/*
+ comedi/comedi_fops.c
+ comedi kernel module
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 "comedi_compat32.h"
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/vmalloc.h>
+#include <linux/fs.h>
+#include "comedidev.h"
+#include <linux/cdev.h>
+#include <linux/stat.h>
+
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include "comedi_internal.h"
+
+#define COMEDI_NUM_MINORS 0x100
+#define COMEDI_NUM_SUBDEVICE_MINORS \
+ (COMEDI_NUM_MINORS - COMEDI_NUM_BOARD_MINORS)
+
+static int comedi_num_legacy_minors;
+module_param(comedi_num_legacy_minors, int, S_IRUGO);
+MODULE_PARM_DESC(comedi_num_legacy_minors,
+ "number of comedi minor devices to reserve for non-auto-configured devices (default 0)"
+ );
+
+unsigned int comedi_default_buf_size_kb = CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB;
+module_param(comedi_default_buf_size_kb, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(comedi_default_buf_size_kb,
+ "default asynchronous buffer size in KiB (default "
+ __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_SIZE_KB) ")");
+
+unsigned int comedi_default_buf_maxsize_kb
+ = CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB;
+module_param(comedi_default_buf_maxsize_kb, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(comedi_default_buf_maxsize_kb,
+ "default maximum size of asynchronous buffer in KiB (default "
+ __MODULE_STRING(CONFIG_COMEDI_DEFAULT_BUF_MAXSIZE_KB) ")");
+
+static DEFINE_MUTEX(comedi_board_minor_table_lock);
+static struct comedi_device
+*comedi_board_minor_table[COMEDI_NUM_BOARD_MINORS];
+
+static DEFINE_MUTEX(comedi_subdevice_minor_table_lock);
+/* Note: indexed by minor - COMEDI_NUM_BOARD_MINORS. */
+static struct comedi_subdevice
+*comedi_subdevice_minor_table[COMEDI_NUM_SUBDEVICE_MINORS];
+
+static struct class *comedi_class;
+static struct cdev comedi_cdev;
+
+static void comedi_device_init(struct comedi_device *dev)
+{
+ kref_init(&dev->refcount);
+ spin_lock_init(&dev->spinlock);
+ mutex_init(&dev->mutex);
+ init_rwsem(&dev->attach_lock);
+ dev->minor = -1;
+}
+
+static void comedi_dev_kref_release(struct kref *kref)
+{
+ struct comedi_device *dev =
+ container_of(kref, struct comedi_device, refcount);
+
+ mutex_destroy(&dev->mutex);
+ put_device(dev->class_dev);
+ kfree(dev);
+}
+
+int comedi_dev_put(struct comedi_device *dev)
+{
+ if (dev)
+ return kref_put(&dev->refcount, comedi_dev_kref_release);
+ return 1;
+}
+EXPORT_SYMBOL_GPL(comedi_dev_put);
+
+static struct comedi_device *comedi_dev_get(struct comedi_device *dev)
+{
+ if (dev)
+ kref_get(&dev->refcount);
+ return dev;
+}
+
+static void comedi_device_cleanup(struct comedi_device *dev)
+{
+ struct module *driver_module = NULL;
+
+ if (dev == NULL)
+ return;
+ mutex_lock(&dev->mutex);
+ if (dev->attached)
+ driver_module = dev->driver->module;
+ comedi_device_detach(dev);
+ if (driver_module && dev->use_count)
+ module_put(driver_module);
+ mutex_unlock(&dev->mutex);
+}
+
+static bool comedi_clear_board_dev(struct comedi_device *dev)
+{
+ unsigned int i = dev->minor;
+ bool cleared = false;
+
+ mutex_lock(&comedi_board_minor_table_lock);
+ if (dev == comedi_board_minor_table[i]) {
+ comedi_board_minor_table[i] = NULL;
+ cleared = true;
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return cleared;
+}
+
+static struct comedi_device *comedi_clear_board_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_board_minor_table[minor];
+ comedi_board_minor_table[minor] = NULL;
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return dev;
+}
+
+static void comedi_free_board_dev(struct comedi_device *dev)
+{
+ if (dev) {
+ comedi_device_cleanup(dev);
+ if (dev->class_dev) {
+ device_destroy(comedi_class,
+ MKDEV(COMEDI_MAJOR, dev->minor));
+ }
+ comedi_dev_put(dev);
+ }
+}
+
+static struct comedi_subdevice
+*comedi_subdevice_from_minor(const struct comedi_device *dev, unsigned minor)
+{
+ struct comedi_subdevice *s;
+ unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+ BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ s = comedi_subdevice_minor_table[i];
+ if (s && s->device != dev)
+ s = NULL;
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ return s;
+}
+
+static struct comedi_device *comedi_dev_get_from_board_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+
+ BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_dev_get(comedi_board_minor_table[minor]);
+ mutex_unlock(&comedi_board_minor_table_lock);
+ return dev;
+}
+
+static struct comedi_device *comedi_dev_get_from_subdevice_minor(unsigned minor)
+{
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int i = minor - COMEDI_NUM_BOARD_MINORS;
+
+ BUG_ON(i >= COMEDI_NUM_SUBDEVICE_MINORS);
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ s = comedi_subdevice_minor_table[i];
+ dev = comedi_dev_get(s ? s->device : NULL);
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ return dev;
+}
+
+struct comedi_device *comedi_dev_get_from_minor(unsigned minor)
+{
+ if (minor < COMEDI_NUM_BOARD_MINORS)
+ return comedi_dev_get_from_board_minor(minor);
+ else
+ return comedi_dev_get_from_subdevice_minor(minor);
+}
+EXPORT_SYMBOL_GPL(comedi_dev_get_from_minor);
+
+static struct comedi_subdevice *
+comedi_read_subdevice(const struct comedi_device *dev, unsigned int minor)
+{
+ struct comedi_subdevice *s;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (s == NULL || (s->subdev_flags & SDF_CMD_READ))
+ return s;
+ }
+ return dev->read_subdev;
+}
+
+static struct comedi_subdevice *
+comedi_write_subdevice(const struct comedi_device *dev, unsigned int minor)
+{
+ struct comedi_subdevice *s;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ s = comedi_subdevice_from_minor(dev, minor);
+ if (s == NULL || (s->subdev_flags & SDF_CMD_WRITE))
+ return s;
+ }
+ return dev->write_subdev;
+}
+
+static int resize_async_buffer(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned new_size)
+{
+ struct comedi_async *async = s->async;
+ int retval;
+
+ if (new_size > async->max_bufsize)
+ return -EPERM;
+
+ if (s->busy) {
+ dev_dbg(dev->class_dev,
+ "subdevice is busy, cannot resize buffer\n");
+ return -EBUSY;
+ }
+ if (comedi_buf_is_mmapped(s)) {
+ dev_dbg(dev->class_dev,
+ "subdevice is mmapped, cannot resize buffer\n");
+ return -EBUSY;
+ }
+
+ /* 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;
+ }
+
+ dev_dbg(dev->class_dev, "subd %d buffer resized to %i bytes\n",
+ s->index, async->prealloc_bufsz);
+ return 0;
+}
+
+/* sysfs attribute files */
+
+static ssize_t max_read_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ size = s->async->max_bufsize / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_read_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ s->async->max_bufsize = size;
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_read_buffer_kb);
+
+static ssize_t read_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ size = s->async->prealloc_bufsz / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t read_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_read_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_READ) && s->async)
+ err = resize_async_buffer(dev, s, size);
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(read_buffer_kb);
+
+static ssize_t max_write_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ size = s->async->max_bufsize / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t max_write_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ s->async->max_bufsize = size;
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(max_write_buffer_kb);
+
+static ssize_t write_buffer_kb_show(struct device *csdev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size = 0;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ size = s->async->prealloc_bufsz / 1024;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", size);
+}
+
+static ssize_t write_buffer_kb_store(struct device *csdev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int minor = MINOR(csdev->devt);
+ struct comedi_device *dev;
+ struct comedi_subdevice *s;
+ unsigned int size;
+ int err;
+
+ err = kstrtouint(buf, 10, &size);
+ if (err)
+ return err;
+ if (size > (UINT_MAX / 1024))
+ return -EINVAL;
+ size *= 1024;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+ s = comedi_write_subdevice(dev, minor);
+ if (s && (s->subdev_flags & SDF_CMD_WRITE) && s->async)
+ err = resize_async_buffer(dev, s, size);
+ else
+ err = -EINVAL;
+ mutex_unlock(&dev->mutex);
+
+ comedi_dev_put(dev);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(write_buffer_kb);
+
+static struct attribute *comedi_dev_attrs[] = {
+ &dev_attr_max_read_buffer_kb.attr,
+ &dev_attr_read_buffer_kb.attr,
+ &dev_attr_max_write_buffer_kb.attr,
+ &dev_attr_write_buffer_kb.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(comedi_dev);
+
+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 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;
+}
+
+bool comedi_is_subdevice_running(struct comedi_subdevice *s)
+{
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ return (runflags & SRF_RUNNING) ? true : false;
+}
+EXPORT_SYMBOL_GPL(comedi_is_subdevice_running);
+
+static bool comedi_is_subdevice_in_error(struct comedi_subdevice *s)
+{
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ return (runflags & SRF_ERROR) ? true : false;
+}
+
+static bool comedi_is_subdevice_idle(struct comedi_subdevice *s)
+{
+ unsigned runflags = comedi_get_subdevice_runflags(s);
+
+ return (runflags & (SRF_ERROR | SRF_RUNNING)) ? false : true;
+}
+
+/**
+ * comedi_alloc_spriv() - Allocate memory for the subdevice private data.
+ * @s: comedi_subdevice struct
+ * @size: size of the memory to allocate
+ *
+ * This also sets the subdevice runflags to allow the core to automatically
+ * free the private data during the detach.
+ */
+void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size)
+{
+ s->private = kzalloc(size, GFP_KERNEL);
+ if (s->private)
+ s->runflags |= SRF_FREE_SPRIV;
+ return s->private;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_spriv);
+
+/*
+ This function restores a subdevice to an idle state.
+ */
+static 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_buf_reset(s);
+ async->inttrig = NULL;
+ kfree(async->cmd.chanlist);
+ async->cmd.chanlist = NULL;
+ s->busy = NULL;
+ wake_up_interruptible_all(&s->async->wait_head);
+ } else {
+ dev_err(dev->class_dev,
+ "BUG: (?) do_become_nonbusy called with async=NULL\n");
+ s->busy = NULL;
+ }
+}
+
+static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ int ret = 0;
+
+ if (comedi_is_subdevice_running(s) && s->cancel)
+ ret = s->cancel(dev, s);
+
+ do_become_nonbusy(dev, s);
+
+ return ret;
+}
+
+void comedi_device_cancel_all(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ if (!dev->attached)
+ return;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->async)
+ do_cancel(dev, s);
+ }
+}
+
+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 && comedi_buf_is_mmapped(s))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ 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;
+
+ 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(it)))
+ return -EFAULT;
+
+ it.board_name[COMEDI_NAMELEN - 1] = 0;
+
+ if (it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) {
+ dev_warn(dev->class_dev,
+ "comedi_config --init_data is deprecated\n");
+ return -EINVAL;
+ }
+
+ if (dev->minor >= comedi_num_legacy_minors)
+ /* don't re-use dynamically allocated comedi devices */
+ return -EBUSY;
+
+ /* This increments the driver module count on success. */
+ return comedi_device_attach(dev, &it);
+}
+
+/*
+ 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(bc)))
+ return -EFAULT;
+
+ if (bc.subdevice >= dev->n_subdevices)
+ return -EINVAL;
+
+ s = &dev->subdevices[bc.subdevice];
+ async = s->async;
+
+ if (!async) {
+ dev_dbg(dev->class_dev,
+ "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, 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(bc)))
+ 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)
+{
+ const unsigned minor = iminor(file_inode(file));
+ struct comedi_subdevice *s;
+ struct comedi_devinfo devinfo;
+
+ 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);
+
+ s = comedi_read_subdevice(dev, minor);
+ if (s)
+ devinfo.read_subdevice = s->index;
+ else
+ devinfo.read_subdevice = -1;
+
+ s = comedi_write_subdevice(dev, minor);
+ if (s)
+ devinfo.write_subdevice = s->index;
+ else
+ devinfo.write_subdevice = -1;
+
+ if (copy_to_user(arg, &devinfo, sizeof(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(*tmp), 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_is_subdevice_running(s))
+ 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 */
+ }
+
+ 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->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;
+ }
+
+ ret = copy_to_user(arg, tmp, dev->n_subdevices * sizeof(*tmp));
+
+ 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(it)))
+ 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)
+ return -EINVAL; /* flaglist not supported */
+
+ 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(bi)))
+ return -EFAULT;
+
+ if (bi.subdevice >= dev->n_subdevices)
+ return -EINVAL;
+
+ s = &dev->subdevices[bi.subdevice];
+
+ if (s->lock && s->lock != file)
+ return -EACCES;
+
+ async = s->async;
+
+ if (!async) {
+ dev_dbg(dev->class_dev,
+ "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(s, bi.bytes_read);
+ comedi_buf_read_free(s, bi.bytes_read);
+
+ if (comedi_is_subdevice_idle(s) &&
+ 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(s, bi.bytes_written);
+ comedi_buf_write_free(s, 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(bi)))
+ return -EFAULT;
+
+ return 0;
+}
+
+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;
+ case INSN_CONFIG_DIGITAL_TRIG:
+ if (insn->n == 6)
+ return 0;
+ break;
+ /* by default we allow the insn since we don't have checks for
+ * all possible cases yet */
+ default:
+ pr_warn("comedi: No check for data length of config insn id %i is implemented.\n",
+ data[0]);
+ pr_warn("comedi: Add a check to %s in %s.\n",
+ __func__, __FILE__);
+ pr_warn("comedi: Assuming n=%i is correct.\n", insn->n);
+ return 0;
+ }
+ 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) {
+ dev_dbg(dev->class_dev,
+ "%d not usable subdevice\n",
+ insn->subdev);
+ ret = -EINVAL;
+ break;
+ }
+ s = &dev->subdevices[insn->subdev];
+ if (!s->async) {
+ dev_dbg(dev->class_dev, "no async\n");
+ ret = -EINVAL;
+ break;
+ }
+ if (!s->async->inttrig) {
+ dev_dbg(dev->class_dev, "no inttrig\n");
+ ret = -EAGAIN;
+ break;
+ }
+ ret = s->async->inttrig(dev, s, data[0]);
+ if (ret >= 0)
+ ret = 1;
+ break;
+ default:
+ dev_dbg(dev->class_dev, "invalid insn\n");
+ ret = -EINVAL;
+ break;
+ }
+ } else {
+ /* a subdevice instruction */
+ unsigned int maxdata;
+
+ if (insn->subdev >= dev->n_subdevices) {
+ dev_dbg(dev->class_dev, "subdevice %d out of range\n",
+ insn->subdev);
+ ret = -EINVAL;
+ goto out;
+ }
+ s = &dev->subdevices[insn->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_dbg(dev->class_dev, "%d not usable subdevice\n",
+ insn->subdev);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* are we locked? (ioctl lock) */
+ if (s->lock && s->lock != file) {
+ dev_dbg(dev->class_dev, "device locked\n");
+ ret = -EACCES;
+ goto out;
+ }
+
+ ret = comedi_check_chanlist(s, 1, &insn->chanspec);
+ if (ret < 0) {
+ ret = -EINVAL;
+ dev_dbg(dev->class_dev, "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);
+ if (ret == -ETIMEDOUT) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d read instruction timed out\n",
+ s->index);
+ }
+ 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;
+ dev_dbg(dev->class_dev,
+ "bad data value(s)\n");
+ break;
+ }
+ }
+ if (ret == 0) {
+ ret = s->insn_write(dev, s, insn, data);
+ if (ret == -ETIMEDOUT) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d write instruction timed out\n",
+ s->index);
+ }
+ }
+ 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_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(insnlist)))
+ return -EFAULT;
+
+ data = kmalloc(sizeof(unsigned int) * MAX_SAMPLES, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ insns = kcalloc(insnlist.n_insns, sizeof(*insns), GFP_KERNEL);
+ if (!insns) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(insns, insnlist.insns,
+ sizeof(*insns) * insnlist.n_insns)) {
+ dev_dbg(dev->class_dev, "copy_from_user failed\n");
+ ret = -EFAULT;
+ goto error;
+ }
+
+ for (i = 0; i < insnlist.n_insns; i++) {
+ if (insns[i].n > MAX_SAMPLES) {
+ dev_dbg(dev->class_dev,
+ "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))) {
+ dev_dbg(dev->class_dev,
+ "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))) {
+ dev_dbg(dev->class_dev,
+ "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;
+}
+
+/*
+ * 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(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 int __comedi_get_user_cmd(struct comedi_device *dev,
+ struct comedi_cmd __user *arg,
+ struct comedi_cmd *cmd)
+{
+ struct comedi_subdevice *s;
+
+ if (copy_from_user(cmd, arg, sizeof(*cmd))) {
+ dev_dbg(dev->class_dev, "bad cmd address\n");
+ return -EFAULT;
+ }
+
+ if (cmd->subdev >= dev->n_subdevices) {
+ dev_dbg(dev->class_dev, "%d no such subdevice\n", cmd->subdev);
+ return -ENODEV;
+ }
+
+ s = &dev->subdevices[cmd->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_dbg(dev->class_dev, "%d not valid subdevice\n",
+ cmd->subdev);
+ return -EIO;
+ }
+
+ if (!s->do_cmd || !s->do_cmdtest || !s->async) {
+ dev_dbg(dev->class_dev,
+ "subdevice %d does not support commands\n",
+ cmd->subdev);
+ return -EIO;
+ }
+
+ /* make sure channel/gain list isn't too long */
+ if (cmd->chanlist_len > s->len_chanlist) {
+ dev_dbg(dev->class_dev, "channel/gain list too long %d > %d\n",
+ cmd->chanlist_len, s->len_chanlist);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __comedi_get_user_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int __user *user_chanlist,
+ struct comedi_cmd *cmd)
+{
+ unsigned int *chanlist;
+ int ret;
+
+ /* user_chanlist could be NULL for do_cmdtest ioctls */
+ if (!user_chanlist)
+ return 0;
+
+ chanlist = memdup_user(user_chanlist,
+ cmd->chanlist_len * sizeof(unsigned int));
+ if (IS_ERR(chanlist))
+ return PTR_ERR(chanlist);
+
+ /* make sure each element in channel/gain list is valid */
+ ret = comedi_check_chanlist(s, cmd->chanlist_len, chanlist);
+ if (ret < 0) {
+ kfree(chanlist);
+ return ret;
+ }
+
+ cmd->chanlist = chanlist;
+
+ return 0;
+}
+
+static int do_cmd_ioctl(struct comedi_device *dev,
+ struct comedi_cmd __user *arg, void *file)
+{
+ struct comedi_cmd cmd;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ unsigned int __user *user_chanlist;
+ int ret;
+
+ /* get the user's cmd and do some simple validation */
+ ret = __comedi_get_user_cmd(dev, arg, &cmd);
+ if (ret)
+ return ret;
+
+ /* save user's chanlist pointer so it can be restored later */
+ user_chanlist = (unsigned int __user *)cmd.chanlist;
+
+ s = &dev->subdevices[cmd.subdev];
+ async = s->async;
+
+ /* are we locked? (ioctl lock) */
+ if (s->lock && s->lock != file) {
+ dev_dbg(dev->class_dev, "subdevice locked\n");
+ return -EACCES;
+ }
+
+ /* are we busy? */
+ if (s->busy) {
+ dev_dbg(dev->class_dev, "subdevice busy\n");
+ return -EBUSY;
+ }
+
+ /* make sure channel/gain list isn't too short */
+ if (cmd.chanlist_len < 1) {
+ dev_dbg(dev->class_dev, "channel/gain list too short %u < 1\n",
+ cmd.chanlist_len);
+ return -EINVAL;
+ }
+
+ async->cmd = cmd;
+ async->cmd.data = NULL;
+
+ /* load channel/gain list */
+ ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &async->cmd);
+ if (ret)
+ goto cleanup;
+
+ ret = s->do_cmdtest(dev, s, &async->cmd);
+
+ if (async->cmd.flags & TRIG_BOGUS || ret) {
+ dev_dbg(dev->class_dev, "test returned %d\n", ret);
+ cmd = async->cmd;
+ /* restore chanlist pointer before copying back */
+ cmd.chanlist = (unsigned int __force *)user_chanlist;
+ cmd.data = NULL;
+ if (copy_to_user(arg, &cmd, sizeof(cmd))) {
+ dev_dbg(dev->class_dev, "fault writing cmd\n");
+ ret = -EFAULT;
+ goto cleanup;
+ }
+ ret = -EAGAIN;
+ goto cleanup;
+ }
+
+ if (!async->prealloc_bufsz) {
+ ret = -ENOMEM;
+ dev_dbg(dev->class_dev, "no buffer (?)\n");
+ goto cleanup;
+ }
+
+ comedi_buf_reset(s);
+
+ 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, SRF_ERROR | SRF_RUNNING, SRF_RUNNING);
+
+ /* set s->busy _after_ setting SRF_RUNNING flag to avoid race with
+ * comedi_read() or comedi_write() */
+ s->busy = file;
+ 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 cmd;
+ struct comedi_subdevice *s;
+ unsigned int __user *user_chanlist;
+ int ret;
+
+ /* get the user's cmd and do some simple validation */
+ ret = __comedi_get_user_cmd(dev, arg, &cmd);
+ if (ret)
+ return ret;
+
+ /* save user's chanlist pointer so it can be restored later */
+ user_chanlist = (unsigned int __user *)cmd.chanlist;
+
+ s = &dev->subdevices[cmd.subdev];
+
+ /* load channel/gain list */
+ ret = __comedi_get_user_chanlist(dev, s, user_chanlist, &cmd);
+ if (ret)
+ return ret;
+
+ ret = s->do_cmdtest(dev, s, &cmd);
+
+ /* restore chanlist pointer before copying back */
+ cmd.chanlist = (unsigned int __force *)user_chanlist;
+
+ if (copy_to_user(arg, &cmd, sizeof(cmd))) {
+ dev_dbg(dev->class_dev, "bad cmd address\n");
+ ret = -EFAULT;
+ }
+
+ 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;
+ int ret;
+
+ 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;
+
+ ret = do_cancel(dev, s);
+
+ return ret;
+}
+
+/*
+ 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 long comedi_unlocked_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ const unsigned minor = iminor(file_inode(file));
+ struct comedi_device *dev = file->private_data;
+ int rc;
+
+ mutex_lock(&dev->mutex);
+
+ /* Device config is special, because it must work on
+ * an unconfigured device. */
+ if (cmd == COMEDI_DEVCONFIG) {
+ if (minor >= COMEDI_NUM_BOARD_MINORS) {
+ /* Device config not appropriate on non-board minors. */
+ rc = -ENOTTY;
+ goto done;
+ }
+ rc = do_devconfig_ioctl(dev,
+ (struct comedi_devconfig __user *)arg);
+ if (rc == 0) {
+ if (arg == 0 &&
+ dev->minor >= comedi_num_legacy_minors) {
+ /* Successfully unconfigured a dynamically
+ * allocated device. Try and remove it. */
+ if (comedi_clear_board_dev(dev)) {
+ mutex_unlock(&dev->mutex);
+ comedi_free_board_dev(dev);
+ return rc;
+ }
+ }
+ }
+ goto done;
+ }
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ 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;
+}
+
+static void comedi_vm_open(struct vm_area_struct *area)
+{
+ struct comedi_buf_map *bm;
+
+ bm = area->vm_private_data;
+ comedi_buf_map_get(bm);
+}
+
+static void comedi_vm_close(struct vm_area_struct *area)
+{
+ struct comedi_buf_map *bm;
+
+ bm = area->vm_private_data;
+ comedi_buf_map_put(bm);
+}
+
+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_inode(file));
+ struct comedi_device *dev = file->private_data;
+ struct comedi_subdevice *s;
+ struct comedi_async *async;
+ struct comedi_buf_map *bm = NULL;
+ unsigned long start = vma->vm_start;
+ unsigned long size;
+ int n_pages;
+ int i;
+ int retval;
+
+ /*
+ * 'trylock' avoids circular dependency with current->mm->mmap_sem
+ * and down-reading &dev->attach_lock should normally succeed without
+ * contention unless the device is in the process of being attached
+ * or detached.
+ */
+ if (!down_read_trylock(&dev->attach_lock))
+ return -EAGAIN;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto done;
+ }
+
+ if (vma->vm_flags & VM_WRITE)
+ s = comedi_write_subdevice(dev, minor);
+ else
+ s = comedi_read_subdevice(dev, minor);
+ if (!s) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ async = s->async;
+ if (!async) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ if (vma->vm_pgoff != 0) {
+ dev_dbg(dev->class_dev, "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;
+
+ /* get reference to current buf map (if any) */
+ bm = comedi_buf_map_from_subdev_get(s);
+ if (!bm || n_pages > bm->n_pages) {
+ retval = -EINVAL;
+ goto done;
+ }
+ for (i = 0; i < n_pages; ++i) {
+ struct comedi_buf_page *buf = &bm->page_list[i];
+
+ if (remap_pfn_range(vma, start,
+ page_to_pfn(virt_to_page(buf->virt_addr)),
+ PAGE_SIZE, PAGE_SHARED)) {
+ retval = -EAGAIN;
+ goto done;
+ }
+ start += PAGE_SIZE;
+ }
+
+ vma->vm_ops = &comedi_vm_ops;
+ vma->vm_private_data = bm;
+
+ vma->vm_ops->open(vma);
+
+ retval = 0;
+done:
+ up_read(&dev->attach_lock);
+ comedi_buf_map_put(bm); /* put reference to buf map - okay if NULL */
+ return retval;
+}
+
+static unsigned int comedi_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+ const unsigned minor = iminor(file_inode(file));
+ struct comedi_device *dev = file->private_data;
+ struct comedi_subdevice *s;
+
+ mutex_lock(&dev->mutex);
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ goto done;
+ }
+
+ s = comedi_read_subdevice(dev, minor);
+ if (s && s->async) {
+ poll_wait(file, &s->async->wait_head, wait);
+ if (!s->busy || !comedi_is_subdevice_running(s) ||
+ comedi_buf_read_n_available(s) > 0)
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ s = comedi_write_subdevice(dev, minor);
+ if (s && s->async) {
+ unsigned int bps = bytes_per_sample(s);
+
+ poll_wait(file, &s->async->wait_head, wait);
+ comedi_buf_write_alloc(s, s->async->prealloc_bufsz);
+ if (!s->busy || !comedi_is_subdevice_running(s) ||
+ comedi_buf_write_n_allocated(s) >= bps)
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+done:
+ 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_inode(file));
+ struct comedi_device *dev = file->private_data;
+ bool on_wait_queue = false;
+ bool attach_locked;
+ unsigned int old_detach_count;
+
+ /* Protect against device detachment during operation. */
+ down_read(&dev->attach_lock);
+ attach_locked = true;
+ old_detach_count = dev->detach_count;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto out;
+ }
+
+ s = comedi_write_subdevice(dev, minor);
+ if (!s || !s->async) {
+ retval = -EIO;
+ goto out;
+ }
+
+ async = s->async;
+
+ if (!s->busy || !nbytes)
+ goto out;
+ if (s->busy != file) {
+ retval = -EACCES;
+ goto out;
+ }
+
+ add_wait_queue(&async->wait_head, &wait);
+ on_wait_queue = true;
+ while (nbytes > 0 && !retval) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (!comedi_is_subdevice_running(s)) {
+ if (count == 0) {
+ struct comedi_subdevice *new_s;
+
+ if (comedi_is_subdevice_in_error(s))
+ retval = -EPIPE;
+ else
+ retval = 0;
+ /*
+ * To avoid deadlock, cannot acquire dev->mutex
+ * while dev->attach_lock is held. Need to
+ * remove task from the async wait queue before
+ * releasing dev->attach_lock, as it might not
+ * be valid afterwards.
+ */
+ remove_wait_queue(&async->wait_head, &wait);
+ on_wait_queue = false;
+ up_read(&dev->attach_lock);
+ attach_locked = false;
+ mutex_lock(&dev->mutex);
+ /*
+ * Become non-busy unless things have changed
+ * behind our back. Checking dev->detach_count
+ * is unchanged ought to be sufficient (unless
+ * there have been 2**32 detaches in the
+ * meantime!), but check the subdevice pointer
+ * as well just in case.
+ */
+ new_s = comedi_write_subdevice(dev, minor);
+ if (dev->attached &&
+ old_detach_count == dev->detach_count &&
+ s == new_s && new_s->async == async)
+ do_become_nonbusy(dev, s);
+ mutex_unlock(&dev->mutex);
+ }
+ 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(s, async->prealloc_bufsz);
+ if (m > comedi_buf_write_n_allocated(s))
+ m = comedi_buf_write_n_allocated(s);
+ 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(s, n);
+
+ count += n;
+ nbytes -= n;
+
+ buf += n;
+ break; /* makes device work like a pipe */
+ }
+out:
+ if (on_wait_queue)
+ remove_wait_queue(&async->wait_head, &wait);
+ set_current_state(TASK_RUNNING);
+ if (attach_locked)
+ up_read(&dev->attach_lock);
+
+ 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_inode(file));
+ struct comedi_device *dev = file->private_data;
+ unsigned int old_detach_count;
+ bool become_nonbusy = false;
+ bool attach_locked;
+
+ /* Protect against device detachment during operation. */
+ down_read(&dev->attach_lock);
+ attach_locked = true;
+ old_detach_count = dev->detach_count;
+
+ if (!dev->attached) {
+ dev_dbg(dev->class_dev, "no driver attached\n");
+ retval = -ENODEV;
+ goto out;
+ }
+
+ s = comedi_read_subdevice(dev, minor);
+ if (!s || !s->async) {
+ retval = -EIO;
+ goto out;
+ }
+
+ async = s->async;
+ if (!s->busy || !nbytes)
+ goto out;
+ if (s->busy != file) {
+ retval = -EACCES;
+ goto out;
+ }
+
+ add_wait_queue(&async->wait_head, &wait);
+ while (nbytes > 0 && !retval) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ n = nbytes;
+
+ m = comedi_buf_read_n_available(s);
+ /* 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_is_subdevice_running(s)) {
+ if (comedi_is_subdevice_in_error(s))
+ retval = -EPIPE;
+ else
+ retval = 0;
+ become_nonbusy = true;
+ 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(s, n);
+ comedi_buf_read_free(s, n);
+
+ count += n;
+ nbytes -= n;
+
+ buf += n;
+ break; /* makes device work like a pipe */
+ }
+ remove_wait_queue(&async->wait_head, &wait);
+ set_current_state(TASK_RUNNING);
+ if (become_nonbusy || comedi_is_subdevice_idle(s)) {
+ struct comedi_subdevice *new_s;
+
+ /*
+ * To avoid deadlock, cannot acquire dev->mutex
+ * while dev->attach_lock is held.
+ */
+ up_read(&dev->attach_lock);
+ attach_locked = false;
+ mutex_lock(&dev->mutex);
+ /*
+ * Check device hasn't become detached behind our back.
+ * Checking dev->detach_count is unchanged ought to be
+ * sufficient (unless there have been 2**32 detaches in the
+ * meantime!), but check the subdevice pointer as well just in
+ * case.
+ */
+ new_s = comedi_read_subdevice(dev, minor);
+ if (dev->attached && old_detach_count == dev->detach_count &&
+ s == new_s && new_s->async == async) {
+ if (become_nonbusy ||
+ async->buf_read_count - async->buf_write_count == 0)
+ do_become_nonbusy(dev, s);
+ }
+ mutex_unlock(&dev->mutex);
+ }
+out:
+ if (attach_locked)
+ up_read(&dev->attach_lock);
+
+ return count ? count : retval;
+}
+
+static int comedi_open(struct inode *inode, struct file *file)
+{
+ const unsigned minor = iminor(inode);
+ struct comedi_device *dev = comedi_dev_get_from_minor(minor);
+ int rc;
+
+ if (!dev) {
+ pr_debug("invalid minor number\n");
+ return -ENODEV;
+ }
+
+ mutex_lock(&dev->mutex);
+ if (!dev->attached && !capable(CAP_NET_ADMIN)) {
+ dev_dbg(dev->class_dev, "not attached and not CAP_NET_ADMIN\n");
+ rc = -ENODEV;
+ goto out;
+ }
+ if (dev->attached && dev->use_count == 0) {
+ if (!try_module_get(dev->driver->module)) {
+ rc = -ENOSYS;
+ goto out;
+ }
+ if (dev->open) {
+ rc = dev->open(dev);
+ if (rc < 0) {
+ module_put(dev->driver->module);
+ goto out;
+ }
+ }
+ }
+
+ dev->use_count++;
+ file->private_data = dev;
+ rc = 0;
+
+out:
+ mutex_unlock(&dev->mutex);
+ if (rc)
+ comedi_dev_put(dev);
+ return rc;
+}
+
+static int comedi_fasync(int fd, struct file *file, int on)
+{
+ struct comedi_device *dev = file->private_data;
+
+ return fasync_helper(fd, file, on, &dev->async_queue);
+}
+
+static int comedi_close(struct inode *inode, struct file *file)
+{
+ struct comedi_device *dev = file->private_data;
+ struct comedi_subdevice *s = NULL;
+ int i;
+
+ 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) {
+ if (dev->close)
+ dev->close(dev);
+ module_put(dev->driver->module);
+ }
+
+ dev->use_count--;
+
+ mutex_unlock(&dev->mutex);
+ comedi_dev_put(dev);
+
+ return 0;
+}
+
+static 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,
+};
+
+void comedi_error(const struct comedi_device *dev, const char *s)
+{
+ dev_err(dev->class_dev, "%s: %s\n", dev->driver->driver_name, s);
+}
+EXPORT_SYMBOL_GPL(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;
+
+ if (!comedi_is_subdevice_running(s))
+ 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) {
+ 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);
+ }
+ s->async->events = 0;
+}
+EXPORT_SYMBOL_GPL(comedi_event);
+
+/* Note: the ->mutex is pre-locked on successful return */
+struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device)
+{
+ struct comedi_device *dev;
+ struct device *csdev;
+ unsigned i;
+
+ dev = kzalloc(sizeof(struct comedi_device), GFP_KERNEL);
+ if (dev == NULL)
+ return ERR_PTR(-ENOMEM);
+ comedi_device_init(dev);
+ comedi_set_hw_dev(dev, hardware_device);
+ mutex_lock(&dev->mutex);
+ mutex_lock(&comedi_board_minor_table_lock);
+ for (i = hardware_device ? comedi_num_legacy_minors : 0;
+ i < COMEDI_NUM_BOARD_MINORS; ++i) {
+ if (comedi_board_minor_table[i] == NULL) {
+ comedi_board_minor_table[i] = dev;
+ break;
+ }
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ if (i == COMEDI_NUM_BOARD_MINORS) {
+ mutex_unlock(&dev->mutex);
+ comedi_device_cleanup(dev);
+ comedi_dev_put(dev);
+ pr_err("comedi: error: ran out of minor numbers for board device files.\n");
+ return ERR_PTR(-EBUSY);
+ }
+ dev->minor = i;
+ csdev = device_create(comedi_class, hardware_device,
+ MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i);
+ if (!IS_ERR(csdev))
+ dev->class_dev = get_device(csdev);
+
+ /* Note: dev->mutex needs to be unlocked by the caller. */
+ return dev;
+}
+
+static void comedi_free_board_minor(unsigned minor)
+{
+ BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS);
+ comedi_free_board_dev(comedi_clear_board_minor(minor));
+}
+
+void comedi_release_hardware_device(struct device *hardware_device)
+{
+ int minor;
+ struct comedi_device *dev;
+
+ for (minor = comedi_num_legacy_minors; minor < COMEDI_NUM_BOARD_MINORS;
+ minor++) {
+ mutex_lock(&comedi_board_minor_table_lock);
+ dev = comedi_board_minor_table[minor];
+ if (dev && dev->hw_dev == hardware_device) {
+ comedi_board_minor_table[minor] = NULL;
+ mutex_unlock(&comedi_board_minor_table_lock);
+ comedi_free_board_dev(dev);
+ break;
+ }
+ mutex_unlock(&comedi_board_minor_table_lock);
+ }
+}
+
+int comedi_alloc_subdevice_minor(struct comedi_subdevice *s)
+{
+ struct comedi_device *dev = s->device;
+ struct device *csdev;
+ unsigned i;
+
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i) {
+ if (comedi_subdevice_minor_table[i] == NULL) {
+ comedi_subdevice_minor_table[i] = s;
+ break;
+ }
+ }
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ if (i == COMEDI_NUM_SUBDEVICE_MINORS) {
+ pr_err("comedi: error: ran out of minor numbers for subdevice files.\n");
+ return -EBUSY;
+ }
+ i += COMEDI_NUM_BOARD_MINORS;
+ s->minor = i;
+ csdev = device_create(comedi_class, dev->class_dev,
+ MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i",
+ dev->minor, s->index);
+ if (!IS_ERR(csdev))
+ s->class_dev = csdev;
+
+ return 0;
+}
+
+void comedi_free_subdevice_minor(struct comedi_subdevice *s)
+{
+ unsigned int i;
+
+ if (s == NULL)
+ return;
+ if (s->minor < 0)
+ return;
+
+ BUG_ON(s->minor >= COMEDI_NUM_MINORS);
+ BUG_ON(s->minor < COMEDI_NUM_BOARD_MINORS);
+
+ i = s->minor - COMEDI_NUM_BOARD_MINORS;
+ mutex_lock(&comedi_subdevice_minor_table_lock);
+ if (s == comedi_subdevice_minor_table[i])
+ comedi_subdevice_minor_table[i] = NULL;
+ mutex_unlock(&comedi_subdevice_minor_table_lock);
+ if (s->class_dev) {
+ device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor));
+ s->class_dev = NULL;
+ }
+}
+
+static void comedi_cleanup_board_minors(void)
+{
+ unsigned i;
+
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++)
+ comedi_free_board_minor(i);
+}
+
+static int __init comedi_init(void)
+{
+ int i;
+ int retval;
+
+ pr_info("comedi: version " COMEDI_RELEASE " - http://www.comedi.org\n");
+
+ if (comedi_num_legacy_minors < 0 ||
+ comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) {
+ pr_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;
+ }
+
+ 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)) {
+ pr_err("comedi: failed to create class\n");
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return PTR_ERR(comedi_class);
+ }
+
+ comedi_class->dev_groups = comedi_dev_groups;
+
+ /* XXX requires /proc interface */
+ comedi_proc_init();
+
+ /* create devices files for legacy/manual use */
+ for (i = 0; i < comedi_num_legacy_minors; i++) {
+ struct comedi_device *dev;
+
+ dev = comedi_alloc_board_minor(NULL);
+ if (IS_ERR(dev)) {
+ comedi_cleanup_board_minors();
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0),
+ COMEDI_NUM_MINORS);
+ return PTR_ERR(dev);
+ } else {
+ /* comedi_alloc_board_minor() locked the mutex */
+ mutex_unlock(&dev->mutex);
+ }
+ }
+
+ return 0;
+}
+module_init(comedi_init);
+
+static void __exit comedi_cleanup(void)
+{
+ int i;
+
+ comedi_cleanup_board_minors();
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i)
+ BUG_ON(comedi_board_minor_table[i]);
+ for (i = 0; i < COMEDI_NUM_SUBDEVICE_MINORS; ++i)
+ BUG_ON(comedi_subdevice_minor_table[i]);
+
+ class_destroy(comedi_class);
+ cdev_del(&comedi_cdev);
+ unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS);
+
+ comedi_proc_cleanup();
+}
+module_exit(comedi_cleanup);
+
+MODULE_AUTHOR("http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi core module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/comedi_internal.h b/drivers/staging/comedi/comedi_internal.h
new file mode 100644
index 00000000000..e978c223f5b
--- /dev/null
+++ b/drivers/staging/comedi/comedi_internal.h
@@ -0,0 +1,56 @@
+#ifndef _COMEDI_INTERNAL_H
+#define _COMEDI_INTERNAL_H
+
+#include <linux/types.h>
+
+/*
+ * various internal comedi stuff
+ */
+int do_rangeinfo_ioctl(struct comedi_device *dev,
+ struct comedi_rangeinfo __user *arg);
+struct comedi_device *comedi_alloc_board_minor(struct device *hardware_device);
+void comedi_release_hardware_device(struct device *hardware_device);
+int comedi_alloc_subdevice_minor(struct comedi_subdevice *s);
+void comedi_free_subdevice_minor(struct comedi_subdevice *s);
+
+int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned long new_size);
+void comedi_buf_reset(struct comedi_subdevice *s);
+bool comedi_buf_is_mmapped(struct comedi_subdevice *s);
+void comedi_buf_map_get(struct comedi_buf_map *bm);
+int comedi_buf_map_put(struct comedi_buf_map *bm);
+struct comedi_buf_map *comedi_buf_map_from_subdev_get(
+ struct comedi_subdevice *s);
+unsigned int comedi_buf_write_n_allocated(struct comedi_subdevice *s);
+void comedi_device_cancel_all(struct comedi_device *dev);
+
+extern unsigned int comedi_default_buf_size_kb;
+extern unsigned int comedi_default_buf_maxsize_kb;
+
+/* drivers.c */
+
+extern struct comedi_driver *comedi_drivers;
+extern struct mutex comedi_drivers_list_lock;
+
+int insn_inval(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+void comedi_device_detach(struct comedi_device *);
+int comedi_device_attach(struct comedi_device *, struct comedi_devconfig *);
+
+#ifdef CONFIG_PROC_FS
+
+/* proc.c */
+
+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
+
+#endif /* _COMEDI_INTERNAL_H */
diff --git a/drivers/staging/comedi/comedi_pci.c b/drivers/staging/comedi/comedi_pci.c
new file mode 100644
index 00000000000..abbc0e4f5c5
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pci.c
@@ -0,0 +1,146 @@
+/*
+ * comedi_pci.c
+ * Comedi PCI driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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 <linux/pci.h>
+
+#include "comedidev.h"
+
+/**
+ * comedi_to_pci_dev() - comedi_device pointer to pci_dev pointer.
+ * @dev: comedi_device struct
+ */
+struct pci_dev *comedi_to_pci_dev(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_pci_dev(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_pci_dev);
+
+/**
+ * comedi_pci_enable() - Enable the PCI device and request the regions.
+ * @dev: comedi_device struct
+ */
+int comedi_pci_enable(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ int rc;
+
+ if (!pcidev)
+ return -ENODEV;
+
+ rc = pci_enable_device(pcidev);
+ if (rc < 0)
+ return rc;
+
+ rc = pci_request_regions(pcidev, dev->board_name);
+ if (rc < 0)
+ pci_disable_device(pcidev);
+ else
+ dev->ioenabled = true;
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_enable);
+
+/**
+ * comedi_pci_disable() - Release the regions and disable the PCI device.
+ * @dev: comedi_device struct
+ */
+void comedi_pci_disable(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ if (pcidev && dev->ioenabled) {
+ pci_release_regions(pcidev);
+ pci_disable_device(pcidev);
+ }
+ dev->ioenabled = false;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_disable);
+
+/**
+ * comedi_pci_auto_config() - Configure/probe a comedi PCI driver.
+ * @pcidev: pci_dev struct
+ * @driver: comedi_driver struct
+ * @context: driver specific data, passed to comedi_auto_config()
+ *
+ * Typically called from the pci_driver (*probe) function.
+ */
+int comedi_pci_auto_config(struct pci_dev *pcidev,
+ struct comedi_driver *driver,
+ unsigned long context)
+{
+ return comedi_auto_config(&pcidev->dev, driver, context);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_auto_config);
+
+/**
+ * comedi_pci_auto_unconfig() - Unconfigure/remove a comedi PCI driver.
+ * @pcidev: pci_dev struct
+ *
+ * Typically called from the pci_driver (*remove) function.
+ */
+void comedi_pci_auto_unconfig(struct pci_dev *pcidev)
+{
+ comedi_auto_unconfig(&pcidev->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_auto_unconfig);
+
+/**
+ * comedi_pci_driver_register() - Register a comedi PCI driver.
+ * @comedi_driver: comedi_driver struct
+ * @pci_driver: pci_driver struct
+ *
+ * This function is used for the module_init() of comedi PCI drivers.
+ * Do not call it directly, use the module_comedi_pci_driver() helper
+ * macro instead.
+ */
+int comedi_pci_driver_register(struct comedi_driver *comedi_driver,
+ struct pci_driver *pci_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = pci_register_driver(pci_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_pci_driver_register);
+
+/**
+ * comedi_pci_driver_unregister() - Unregister a comedi PCI driver.
+ * @comedi_driver: comedi_driver struct
+ * @pci_driver: pci_driver struct
+ *
+ * This function is used for the module_exit() of comedi PCI drivers.
+ * Do not call it directly, use the module_comedi_pci_driver() helper
+ * macro instead.
+ */
+void comedi_pci_driver_unregister(struct comedi_driver *comedi_driver,
+ struct pci_driver *pci_driver)
+{
+ pci_unregister_driver(pci_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_pci_driver_unregister);
diff --git a/drivers/staging/comedi/comedi_pcmcia.c b/drivers/staging/comedi/comedi_pcmcia.c
new file mode 100644
index 00000000000..9d49d5d01ad
--- /dev/null
+++ b/drivers/staging/comedi/comedi_pcmcia.c
@@ -0,0 +1,156 @@
+/*
+ * comedi_pcmcia.c
+ * Comedi PCMCIA driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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 <linux/kernel.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "comedidev.h"
+
+/**
+ * comedi_to_pcmcia_dev() - comedi_device pointer to pcmcia_device pointer.
+ * @dev: comedi_device struct
+ */
+struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_pcmcia_dev(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_pcmcia_dev);
+
+static int comedi_pcmcia_conf_check(struct pcmcia_device *link,
+ void *priv_data)
+{
+ if (link->config_index == 0)
+ return -EINVAL;
+
+ return pcmcia_request_io(link);
+}
+
+/**
+ * comedi_pcmcia_enable() - Request the regions and enable the PCMCIA device.
+ * @dev: comedi_device struct
+ * @conf_check: optional callback to check the pcmcia_device configuration
+ *
+ * The comedi PCMCIA driver needs to set the link->config_flags, as
+ * appropriate for that driver, before calling this function in order
+ * to allow pcmcia_loop_config() to do its internal autoconfiguration.
+ */
+int comedi_pcmcia_enable(struct comedi_device *dev,
+ int (*conf_check)(struct pcmcia_device *, void *))
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ int ret;
+
+ if (!link)
+ return -ENODEV;
+
+ if (!conf_check)
+ conf_check = comedi_pcmcia_conf_check;
+
+ ret = pcmcia_loop_config(link, conf_check, NULL);
+ if (ret)
+ return ret;
+
+ return pcmcia_enable_device(link);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_enable);
+
+/**
+ * comedi_pcmcia_disable() - Disable the PCMCIA device and release the regions.
+ * @dev: comedi_device struct
+ */
+void comedi_pcmcia_disable(struct comedi_device *dev)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+
+ if (link)
+ pcmcia_disable_device(link);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_disable);
+
+/**
+ * comedi_pcmcia_auto_config() - Configure/probe a comedi PCMCIA driver.
+ * @link: pcmcia_device struct
+ * @driver: comedi_driver struct
+ *
+ * Typically called from the pcmcia_driver (*probe) function.
+ */
+int comedi_pcmcia_auto_config(struct pcmcia_device *link,
+ struct comedi_driver *driver)
+{
+ return comedi_auto_config(&link->dev, driver, 0);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_config);
+
+/**
+ * comedi_pcmcia_auto_unconfig() - Unconfigure/remove a comedi PCMCIA driver.
+ * @link: pcmcia_device struct
+ *
+ * Typically called from the pcmcia_driver (*remove) function.
+ */
+void comedi_pcmcia_auto_unconfig(struct pcmcia_device *link)
+{
+ comedi_auto_unconfig(&link->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_auto_unconfig);
+
+/**
+ * comedi_pcmcia_driver_register() - Register a comedi PCMCIA driver.
+ * @comedi_driver: comedi_driver struct
+ * @pcmcia_driver: pcmcia_driver struct
+ *
+ * This function is used for the module_init() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_pcmcia_driver() helper
+ * macro instead.
+ */
+int comedi_pcmcia_driver_register(struct comedi_driver *comedi_driver,
+ struct pcmcia_driver *pcmcia_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = pcmcia_register_driver(pcmcia_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_register);
+
+/**
+ * comedi_pcmcia_driver_unregister() - Unregister a comedi PCMCIA driver.
+ * @comedi_driver: comedi_driver struct
+ * @pcmcia_driver: pcmcia_driver struct
+ *
+ * This function is used for the module_exit() of comedi PCMCIA drivers.
+ * Do not call it directly, use the module_comedi_pcmcia_driver() helper
+ * macro instead.
+ */
+void comedi_pcmcia_driver_unregister(struct comedi_driver *comedi_driver,
+ struct pcmcia_driver *pcmcia_driver)
+{
+ pcmcia_unregister_driver(pcmcia_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_pcmcia_driver_unregister);
diff --git a/drivers/staging/comedi/comedi_usb.c b/drivers/staging/comedi/comedi_usb.c
new file mode 100644
index 00000000000..13f18bef609
--- /dev/null
+++ b/drivers/staging/comedi/comedi_usb.c
@@ -0,0 +1,116 @@
+/*
+ * comedi_usb.c
+ * Comedi USB driver specific functions.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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 <linux/usb.h>
+
+#include "comedidev.h"
+
+/**
+ * comedi_to_usb_interface() - comedi_device pointer to usb_interface pointer.
+ * @dev: comedi_device struct
+ */
+struct usb_interface *comedi_to_usb_interface(struct comedi_device *dev)
+{
+ return dev->hw_dev ? to_usb_interface(dev->hw_dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_usb_interface);
+
+/**
+ * comedi_to_usb_dev() - comedi_device pointer to usb_device pointer.
+ * @dev: comedi_device struct
+ */
+struct usb_device *comedi_to_usb_dev(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+
+ return intf ? interface_to_usbdev(intf) : NULL;
+}
+EXPORT_SYMBOL_GPL(comedi_to_usb_dev);
+
+/**
+ * comedi_usb_auto_config() - Configure/probe a comedi USB driver.
+ * @intf: usb_interface struct
+ * @driver: comedi_driver struct
+ * @context: driver specific data, passed to comedi_auto_config()
+ *
+ * Typically called from the usb_driver (*probe) function.
+ */
+int comedi_usb_auto_config(struct usb_interface *intf,
+ struct comedi_driver *driver,
+ unsigned long context)
+{
+ return comedi_auto_config(&intf->dev, driver, context);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_auto_config);
+
+/**
+ * comedi_pci_auto_unconfig() - Unconfigure/disconnect a comedi USB driver.
+ * @intf: usb_interface struct
+ *
+ * Typically called from the usb_driver (*disconnect) function.
+ */
+void comedi_usb_auto_unconfig(struct usb_interface *intf)
+{
+ comedi_auto_unconfig(&intf->dev);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_auto_unconfig);
+
+/**
+ * comedi_usb_driver_register() - Register a comedi USB driver.
+ * @comedi_driver: comedi_driver struct
+ * @usb_driver: usb_driver struct
+ *
+ * This function is used for the module_init() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_usb_driver() helper
+ * macro instead.
+ */
+int comedi_usb_driver_register(struct comedi_driver *comedi_driver,
+ struct usb_driver *usb_driver)
+{
+ int ret;
+
+ ret = comedi_driver_register(comedi_driver);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_register(usb_driver);
+ if (ret < 0) {
+ comedi_driver_unregister(comedi_driver);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_usb_driver_register);
+
+/**
+ * comedi_usb_driver_unregister() - Unregister a comedi USB driver.
+ * @comedi_driver: comedi_driver struct
+ * @usb_driver: usb_driver struct
+ *
+ * This function is used for the module_exit() of comedi USB drivers.
+ * Do not call it directly, use the module_comedi_usb_driver() helper
+ * macro instead.
+ */
+void comedi_usb_driver_unregister(struct comedi_driver *comedi_driver,
+ struct usb_driver *usb_driver)
+{
+ usb_deregister(usb_driver);
+ comedi_driver_unregister(comedi_driver);
+}
+EXPORT_SYMBOL_GPL(comedi_usb_driver_unregister);
diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h
new file mode 100644
index 00000000000..8f4e44bfbe0
--- /dev/null
+++ b/drivers/staging/comedi/comedidev.h
@@ -0,0 +1,540 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _COMEDIDEV_H
+#define _COMEDIDEV_H
+
+#include <linux/dma-mapping.h>
+#include <linux/mutex.h>
+#include <linux/spinlock_types.h>
+#include <linux/rwsem.h>
+#include <linux/kref.h>
+
+#include "comedi.h"
+
+#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 COMEDI_NUM_BOARD_MINORS 0x30
+
+struct comedi_subdevice {
+ struct comedi_device *device;
+ int index;
+ 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;
+
+ unsigned int io_bits;
+
+ unsigned int maxdata; /* if maxdata==0, use list */
+ const unsigned int *maxdata_list; /* list is channel specific */
+
+ 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_buf_map {
+ struct device *dma_hw_dev;
+ struct comedi_buf_page *page_list;
+ unsigned int n_pages;
+ enum dma_data_direction dma_dir;
+ struct kref refcount;
+};
+
+struct comedi_async {
+ void *prealloc_buf; /* pre-allocated buffer */
+ unsigned int prealloc_bufsz; /* buffer size, in bytes */
+ struct comedi_buf_map *buf_map; /* map of buffer pages */
+
+ unsigned int max_bufsize; /* maximum buffer size, bytes */
+
+ /* 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;
+
+ unsigned int cb_mask;
+
+ 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 *);
+ void (*detach)(struct comedi_device *);
+ int (*auto_attach)(struct comedi_device *, unsigned long);
+
+ /* 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;
+ unsigned int detach_count;
+ /* 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;
+ bool attached:1;
+ bool ioenabled:1;
+ spinlock_t spinlock;
+ struct mutex mutex;
+ struct rw_semaphore attach_lock;
+ struct kref refcount;
+
+ int n_subdevices;
+ struct comedi_subdevice *subdevices;
+
+ /* dumb */
+ unsigned long iobase;
+ unsigned long iolen;
+ 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);
+};
+
+static inline const void *comedi_board(const struct comedi_device *dev)
+{
+ return dev->board_ptr;
+}
+
+/*
+ * 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 *comedi_dev_get_from_minor(unsigned minor);
+int comedi_dev_put(struct comedi_device *dev);
+
+void init_polling(void);
+void cleanup_polling(void);
+void start_polling(struct comedi_device *);
+void stop_polling(struct comedi_device *);
+
+/* subdevice runflags */
+enum subdevice_runflags {
+ SRF_RT = 0x00000002,
+ /* indicates an COMEDI_CB_ERROR event has occurred since the last
+ * command was started */
+ SRF_ERROR = 0x00000004,
+ SRF_RUNNING = 0x08000000,
+ SRF_FREE_SPRIV = 0x80000000, /* free s->private on detach */
+};
+
+bool comedi_is_subdevice_running(struct comedi_subdevice *s);
+
+void *comedi_alloc_spriv(struct comedi_subdevice *s, size_t size);
+
+int comedi_check_chanlist(struct comedi_subdevice *s,
+ int n,
+ unsigned int *chanlist);
+
+/* 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_unipolar2_5;
+extern const struct comedi_lrange range_0_20mA;
+extern const struct comedi_lrange range_4_20mA;
+extern const struct comedi_lrange range_0_32mA;
+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];
+};
+
+static inline bool comedi_range_is_bipolar(struct comedi_subdevice *s,
+ unsigned int range)
+{
+ return s->range_table->range[range].min < 0;
+}
+
+static inline bool comedi_range_is_unipolar(struct comedi_subdevice *s,
+ unsigned int range)
+{
+ return s->range_table->range[range].min >= 0;
+}
+
+static inline bool comedi_chan_range_is_bipolar(struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int range)
+{
+ return s->range_table_list[chan]->range[range].min < 0;
+}
+
+static inline bool comedi_chan_range_is_unipolar(struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int range)
+{
+ return s->range_table_list[chan]->range[range].min >= 0;
+}
+
+/* munge between offset binary and two's complement values */
+static inline unsigned int comedi_offset_munge(struct comedi_subdevice *s,
+ unsigned int val)
+{
+ return val ^ s->maxdata ^ (s->maxdata >> 1);
+}
+
+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 set dev->hw_dev if you wish to dma directly into comedi's buffer.
+ * Also useful for retrieving a previously configured hardware device of
+ * known bus type. Set automatically for auto-configured devices.
+ * Automatically set to NULL when detaching hardware device.
+ */
+int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev);
+
+unsigned int comedi_buf_write_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_write_free(struct comedi_subdevice *s, unsigned int n);
+
+unsigned int comedi_buf_read_n_available(struct comedi_subdevice *s);
+unsigned int comedi_buf_read_alloc(struct comedi_subdevice *s, unsigned int n);
+unsigned int comedi_buf_read_free(struct comedi_subdevice *s, unsigned int n);
+
+int comedi_buf_put(struct comedi_subdevice *s, unsigned short x);
+int comedi_buf_get(struct comedi_subdevice *s, unsigned short *x);
+
+void comedi_buf_memcpy_to(struct comedi_subdevice *s, unsigned int offset,
+ const void *source, unsigned int num_bytes);
+void comedi_buf_memcpy_from(struct comedi_subdevice *s, unsigned int offset,
+ void *destination, unsigned int num_bytes);
+
+/* drivers.c - general comedi driver functions */
+
+#define COMEDI_TIMEOUT_MS 1000
+
+int comedi_timeout(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *,
+ int (*cb)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned long context),
+ unsigned long context);
+
+int comedi_dio_insn_config(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data,
+ unsigned int mask);
+unsigned int comedi_dio_update_state(struct comedi_subdevice *,
+ unsigned int *data);
+
+void *comedi_alloc_devpriv(struct comedi_device *, size_t);
+int comedi_alloc_subdevices(struct comedi_device *, int);
+
+int comedi_load_firmware(struct comedi_device *, struct device *,
+ const char *name,
+ int (*cb)(struct comedi_device *,
+ const u8 *data, size_t size,
+ unsigned long context),
+ unsigned long context);
+
+int __comedi_request_region(struct comedi_device *,
+ unsigned long start, unsigned long len);
+int comedi_request_region(struct comedi_device *,
+ unsigned long start, unsigned long len);
+void comedi_legacy_detach(struct comedi_device *);
+
+int comedi_auto_config(struct device *, struct comedi_driver *,
+ unsigned long context);
+void comedi_auto_unconfig(struct device *);
+
+int comedi_driver_register(struct comedi_driver *);
+void comedi_driver_unregister(struct comedi_driver *);
+
+/**
+ * module_comedi_driver() - Helper macro for registering a comedi driver
+ * @__comedi_driver: comedi_driver struct
+ *
+ * Helper macro for comedi drivers which do not do anything special in module
+ * init/exit. This eliminates a lot of boilerplate. Each module may only use
+ * this macro once, and calling it replaces module_init() and module_exit().
+ */
+#define module_comedi_driver(__comedi_driver) \
+ module_driver(__comedi_driver, comedi_driver_register, \
+ comedi_driver_unregister)
+
+#ifdef CONFIG_COMEDI_PCI_DRIVERS
+
+/* comedi_pci.c - comedi PCI driver specific functions */
+
+/*
+ * PCI Vendor IDs not in <linux/pci_ids.h>
+ */
+#define PCI_VENDOR_ID_KOLTER 0x1001
+#define PCI_VENDOR_ID_ICP 0x104c
+#define PCI_VENDOR_ID_DT 0x1116
+#define PCI_VENDOR_ID_IOTECH 0x1616
+#define PCI_VENDOR_ID_CONTEC 0x1221
+#define PCI_VENDOR_ID_RTD 0x1435
+#define PCI_VENDOR_ID_HUMUSOFT 0x186c
+
+struct pci_dev;
+struct pci_driver;
+
+struct pci_dev *comedi_to_pci_dev(struct comedi_device *);
+
+int comedi_pci_enable(struct comedi_device *);
+void comedi_pci_disable(struct comedi_device *);
+
+int comedi_pci_auto_config(struct pci_dev *, struct comedi_driver *,
+ unsigned long context);
+void comedi_pci_auto_unconfig(struct pci_dev *);
+
+int comedi_pci_driver_register(struct comedi_driver *, struct pci_driver *);
+void comedi_pci_driver_unregister(struct comedi_driver *, struct pci_driver *);
+
+/**
+ * module_comedi_pci_driver() - Helper macro for registering a comedi PCI driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pci_driver: pci_driver struct
+ *
+ * Helper macro for comedi PCI drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pci_driver(__comedi_driver, __pci_driver) \
+ module_driver(__comedi_driver, comedi_pci_driver_register, \
+ comedi_pci_driver_unregister, &(__pci_driver))
+
+#else
+
+/*
+ * Some of the comedi mixed ISA/PCI drivers call the PCI specific
+ * functions. Provide some dummy functions if CONFIG_COMEDI_PCI_DRIVERS
+ * is not enabled.
+ */
+
+static inline struct pci_dev *comedi_to_pci_dev(struct comedi_device *dev)
+{
+ return NULL;
+}
+
+static inline int comedi_pci_enable(struct comedi_device *dev)
+{
+ return -ENOSYS;
+}
+
+static inline void comedi_pci_disable(struct comedi_device *dev)
+{
+}
+
+#endif /* CONFIG_COMEDI_PCI_DRIVERS */
+
+#ifdef CONFIG_COMEDI_PCMCIA_DRIVERS
+
+/* comedi_pcmcia.c - comedi PCMCIA driver specific functions */
+
+struct pcmcia_driver;
+struct pcmcia_device;
+
+struct pcmcia_device *comedi_to_pcmcia_dev(struct comedi_device *);
+
+int comedi_pcmcia_enable(struct comedi_device *,
+ int (*conf_check)(struct pcmcia_device *, void *));
+void comedi_pcmcia_disable(struct comedi_device *);
+
+int comedi_pcmcia_auto_config(struct pcmcia_device *, struct comedi_driver *);
+void comedi_pcmcia_auto_unconfig(struct pcmcia_device *);
+
+int comedi_pcmcia_driver_register(struct comedi_driver *,
+ struct pcmcia_driver *);
+void comedi_pcmcia_driver_unregister(struct comedi_driver *,
+ struct pcmcia_driver *);
+
+/**
+ * module_comedi_pcmcia_driver() - Helper macro for registering a comedi PCMCIA driver
+ * @__comedi_driver: comedi_driver struct
+ * @__pcmcia_driver: pcmcia_driver struct
+ *
+ * Helper macro for comedi PCMCIA drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_pcmcia_driver(__comedi_driver, __pcmcia_driver) \
+ module_driver(__comedi_driver, comedi_pcmcia_driver_register, \
+ comedi_pcmcia_driver_unregister, &(__pcmcia_driver))
+
+#endif /* CONFIG_COMEDI_PCMCIA_DRIVERS */
+
+#ifdef CONFIG_COMEDI_USB_DRIVERS
+
+/* comedi_usb.c - comedi USB driver specific functions */
+
+struct usb_driver;
+struct usb_interface;
+
+struct usb_interface *comedi_to_usb_interface(struct comedi_device *);
+struct usb_device *comedi_to_usb_dev(struct comedi_device *);
+
+int comedi_usb_auto_config(struct usb_interface *, struct comedi_driver *,
+ unsigned long context);
+void comedi_usb_auto_unconfig(struct usb_interface *);
+
+int comedi_usb_driver_register(struct comedi_driver *, struct usb_driver *);
+void comedi_usb_driver_unregister(struct comedi_driver *, struct usb_driver *);
+
+/**
+ * module_comedi_usb_driver() - Helper macro for registering a comedi USB driver
+ * @__comedi_driver: comedi_driver struct
+ * @__usb_driver: usb_driver struct
+ *
+ * Helper macro for comedi USB drivers which do not do anything special
+ * in module init/exit. This eliminates a lot of boilerplate. Each
+ * module may only use this macro once, and calling it replaces
+ * module_init() and module_exit()
+ */
+#define module_comedi_usb_driver(__comedi_driver, __usb_driver) \
+ module_driver(__comedi_driver, comedi_usb_driver_register, \
+ comedi_usb_driver_unregister, &(__usb_driver))
+
+#endif /* CONFIG_COMEDI_USB_DRIVERS */
+
+#endif /* _COMEDIDEV_H */
diff --git a/drivers/staging/comedi/comedilib.h b/drivers/staging/comedi/comedilib.h
new file mode 100644
index 00000000000..56baf852ecf
--- /dev/null
+++ b/drivers/staging/comedi/comedilib.h
@@ -0,0 +1,35 @@
+/*
+ linux/include/comedilib.h
+ header file for kcomedilib
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#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_get_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int *io);
+int comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int io);
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+ unsigned int mask, unsigned int *bits,
+ unsigned int base_channel);
+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 00000000000..299726f39e2
--- /dev/null
+++ b/drivers/staging/comedi/drivers.c
@@ -0,0 +1,726 @@
+/*
+ module/drivers.c
+ functions for manipulating drivers
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 <linux/device.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/highmem.h> /* for SuSE brokenness */
+#include <linux/vmalloc.h>
+#include <linux/cdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+struct comedi_driver *comedi_drivers;
+DEFINE_MUTEX(comedi_drivers_list_lock);
+
+int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev)
+{
+ if (hw_dev == dev->hw_dev)
+ return 0;
+ if (dev->hw_dev != NULL)
+ return -EEXIST;
+ dev->hw_dev = get_device(hw_dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_set_hw_dev);
+
+static void comedi_clear_hw_dev(struct comedi_device *dev)
+{
+ put_device(dev->hw_dev);
+ dev->hw_dev = NULL;
+}
+
+/**
+ * comedi_alloc_devpriv() - Allocate memory for the device private data.
+ * @dev: comedi_device struct
+ * @size: size of the memory to allocate
+ */
+void *comedi_alloc_devpriv(struct comedi_device *dev, size_t size)
+{
+ dev->private = kzalloc(size, GFP_KERNEL);
+ return dev->private;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_devpriv);
+
+int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ if (num_subdevices < 1)
+ return -EINVAL;
+
+ s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ dev->subdevices = s;
+ dev->n_subdevices = num_subdevices;
+
+ for (i = 0; i < num_subdevices; ++i) {
+ s = &dev->subdevices[i];
+ s->device = dev;
+ s->index = i;
+ s->async_dma_dir = DMA_NONE;
+ spin_lock_init(&s->spin_lock);
+ s->minor = -1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_alloc_subdevices);
+
+static void comedi_device_detach_cleanup(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];
+ if (s->runflags & SRF_FREE_SPRIV)
+ kfree(s->private);
+ 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->iolen = 0;
+ dev->ioenabled = false;
+ dev->irq = 0;
+ dev->read_subdev = NULL;
+ dev->write_subdev = NULL;
+ dev->open = NULL;
+ dev->close = NULL;
+ comedi_clear_hw_dev(dev);
+}
+
+void comedi_device_detach(struct comedi_device *dev)
+{
+ comedi_device_cancel_all(dev);
+ down_write(&dev->attach_lock);
+ dev->attached = false;
+ dev->detach_count++;
+ if (dev->driver)
+ dev->driver->detach(dev);
+ comedi_device_detach_cleanup(dev);
+ up_write(&dev->attach_lock);
+}
+
+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;
+}
+
+/**
+ * comedi_timeout() - busy-wait for a driver condition to occur.
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ * @insn: comedi_insn struct
+ * @cb: callback to check for the condition
+ * @context: private context from the driver
+ */
+int comedi_timeout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ int (*cb)(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context),
+ unsigned long context)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(COMEDI_TIMEOUT_MS);
+ int ret;
+
+ while (time_before(jiffies, timeout)) {
+ ret = cb(dev, s, insn, context);
+ if (ret != -EBUSY)
+ return ret; /* success (0) or non EBUSY errno */
+ cpu_relax();
+ }
+ return -ETIMEDOUT;
+}
+EXPORT_SYMBOL_GPL(comedi_timeout);
+
+/**
+ * comedi_dio_insn_config() - boilerplate (*insn_config) for DIO subdevices.
+ * @dev: comedi_device struct
+ * @s: comedi_subdevice struct
+ * @insn: comedi_insn struct
+ * @data: parameters for the @insn
+ * @mask: io_bits mask for grouped channels
+ */
+int comedi_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data,
+ unsigned int mask)
+{
+ unsigned int chan_mask = 1 << CR_CHAN(insn->chanspec);
+
+ if (!mask)
+ mask = chan_mask;
+
+ 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 insn->n;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_insn_config);
+
+/**
+ * comedi_dio_update_state() - update the internal state of DIO subdevices.
+ * @s: comedi_subdevice struct
+ * @data: the channel mask and bits to update
+ */
+unsigned int comedi_dio_update_state(struct comedi_subdevice *s,
+ unsigned int *data)
+{
+ unsigned int chanmask = (s->n_chan < 32) ? ((1 << s->n_chan) - 1)
+ : 0xffffffff;
+ unsigned int mask = data[0] & chanmask;
+ unsigned int bits = data[1];
+
+ if (mask) {
+ s->state &= ~mask;
+ s->state |= (bits & mask);
+ }
+
+ return mask;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_update_state);
+
+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.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 int __comedi_device_postconfig_async(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async;
+ unsigned int buf_size;
+ int ret;
+
+ if ((s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) == 0) {
+ dev_warn(dev->class_dev,
+ "async subdevices must support SDF_CMD_READ or SDF_CMD_WRITE\n");
+ return -EINVAL;
+ }
+ if (!s->do_cmdtest) {
+ dev_warn(dev->class_dev,
+ "async subdevices must have a do_cmdtest() function\n");
+ return -EINVAL;
+ }
+
+ async = kzalloc(sizeof(*async), GFP_KERNEL);
+ if (!async)
+ return -ENOMEM;
+
+ init_waitqueue_head(&async->wait_head);
+ s->async = async;
+
+ async->max_bufsize = comedi_default_buf_maxsize_kb * 1024;
+ buf_size = comedi_default_buf_size_kb * 1024;
+ if (buf_size > async->max_bufsize)
+ buf_size = async->max_bufsize;
+
+ if (comedi_buf_alloc(dev, s, buf_size) < 0) {
+ dev_warn(dev->class_dev, "Buffer allocation failed\n");
+ return -ENOMEM;
+ }
+ if (s->buf_change) {
+ ret = s->buf_change(dev, s, buf_size);
+ if (ret < 0)
+ return ret;
+ }
+
+ comedi_alloc_subdevice_minor(s);
+
+ return 0;
+}
+
+static int __comedi_device_postconfig(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+
+ if (s->type == COMEDI_SUBD_UNUSED)
+ continue;
+
+ if (s->type == COMEDI_SUBD_DO) {
+ if (s->n_chan < 32)
+ s->io_bits = (1 << s->n_chan) - 1;
+ else
+ s->io_bits = 0xffffffff;
+ }
+
+ if (s->len_chanlist == 0)
+ s->len_chanlist = 1;
+
+ if (s->do_cmd) {
+ ret = __comedi_device_postconfig_async(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ 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;
+}
+
+/* do a little post-config cleanup */
+static int comedi_device_postconfig(struct comedi_device *dev)
+{
+ int ret;
+
+ ret = __comedi_device_postconfig(dev);
+ if (ret < 0)
+ return ret;
+ down_write(&dev->attach_lock);
+ dev->attached = true;
+ up_write(&dev->attach_lock);
+ return 0;
+}
+
+/*
+ * Generic recognize function for drivers that register their supported
+ * board names.
+ *
+ * 'driv->board_name' points to a 'const char *' member within the
+ * zeroth element of an array of some private board information
+ * structure, say 'struct foo_board' containing a member 'const char
+ * *board_name' that is initialized to point to a board name string that
+ * is one of the candidates matched against this function's 'name'
+ * parameter.
+ *
+ * 'driv->offset' is the size of the private board information
+ * structure, say 'sizeof(struct foo_board)', and 'driv->num_names' is
+ * the length of the array of private board information structures.
+ *
+ * If one of the board names in the array of private board information
+ * structures matches the name supplied to this function, the function
+ * returns a pointer to the pointer to the board name, otherwise it
+ * returns NULL. The return value ends up in the 'board_ptr' member of
+ * a 'struct comedi_device' that the low-level comedi driver's
+ * 'attach()' hook can convert to a point to a particular element of its
+ * array of private board information structures by subtracting the
+ * offset of the member that points to the board name. (No subtraction
+ * is required if the board name pointer is the first member of the
+ * private board information structure, which is generally the case.)
+ */
+static void *comedi_recognize(struct comedi_driver *driv, const char *name)
+{
+ char **name_ptr = (char **)driv->board_name;
+ int i;
+
+ for (i = 0; i < driv->num_names; i++) {
+ if (strcmp(*name_ptr, name) == 0)
+ return name_ptr;
+ name_ptr = (void *)name_ptr + driv->offset;
+ }
+
+ return NULL;
+}
+
+static void comedi_report_boards(struct comedi_driver *driv)
+{
+ unsigned int i;
+ const char *const *name_ptr;
+
+ pr_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++) {
+ pr_info(" %s\n", *name_ptr);
+ name_ptr = (const char **)((char *)name_ptr + driv->offset);
+ }
+
+ if (driv->num_names == 0)
+ pr_info(" %s\n", driv->driver_name);
+}
+
+/**
+ * comedi_load_firmware() - Request and load firmware for a device.
+ * @dev: comedi_device struct
+ * @hw_device: device struct for the comedi_device
+ * @name: the name of the firmware image
+ * @cb: callback to the upload the firmware image
+ * @context: private context from the driver
+ */
+int comedi_load_firmware(struct comedi_device *dev,
+ struct device *device,
+ const char *name,
+ int (*cb)(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context),
+ unsigned long context)
+{
+ const struct firmware *fw;
+ int ret;
+
+ if (!cb)
+ return -EINVAL;
+
+ ret = request_firmware(&fw, name, device);
+ if (ret == 0) {
+ ret = cb(dev, fw->data, fw->size, context);
+ release_firmware(fw);
+ }
+
+ return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(comedi_load_firmware);
+
+/**
+ * __comedi_request_region() - Request an I/O reqion for a legacy driver.
+ * @dev: comedi_device struct
+ * @start: base address of the I/O reqion
+ * @len: length of the I/O region
+ */
+int __comedi_request_region(struct comedi_device *dev,
+ unsigned long start, unsigned long len)
+{
+ if (!start) {
+ dev_warn(dev->class_dev,
+ "%s: a I/O base address must be specified\n",
+ dev->board_name);
+ return -EINVAL;
+ }
+
+ if (!request_region(start, len, dev->board_name)) {
+ dev_warn(dev->class_dev, "%s: I/O port conflict (%#lx,%lu)\n",
+ dev->board_name, start, len);
+ return -EIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__comedi_request_region);
+
+/**
+ * comedi_request_region() - Request an I/O reqion for a legacy driver.
+ * @dev: comedi_device struct
+ * @start: base address of the I/O reqion
+ * @len: length of the I/O region
+ */
+int comedi_request_region(struct comedi_device *dev,
+ unsigned long start, unsigned long len)
+{
+ int ret;
+
+ ret = __comedi_request_region(dev, start, len);
+ if (ret == 0) {
+ dev->iobase = start;
+ dev->iolen = len;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_request_region);
+
+/**
+ * comedi_legacy_detach() - A generic (*detach) function for legacy drivers.
+ * @dev: comedi_device struct
+ */
+void comedi_legacy_detach(struct comedi_device *dev)
+{
+ if (dev->irq) {
+ free_irq(dev->irq, dev);
+ dev->irq = 0;
+ }
+ if (dev->iobase && dev->iolen) {
+ release_region(dev->iobase, dev->iolen);
+ dev->iobase = 0;
+ dev->iolen = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_legacy_detach);
+
+int comedi_device_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_driver *driv;
+ int ret;
+
+ if (dev->attached)
+ return -EBUSY;
+
+ mutex_lock(&comedi_drivers_list_lock);
+ for (driv = comedi_drivers; driv; driv = driv->next) {
+ if (!try_module_get(driv->module))
+ continue;
+ if (driv->num_names) {
+ dev->board_ptr = comedi_recognize(driv, it->board_name);
+ if (dev->board_ptr)
+ break;
+ } else if (strcmp(driv->driver_name, it->board_name) == 0)
+ break;
+ module_put(driv->module);
+ }
+ if (driv == NULL) {
+ /* 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))
+ continue;
+ comedi_report_boards(driv);
+ module_put(driv->module);
+ }
+ ret = -EIO;
+ goto out;
+ }
+ if (driv->attach == NULL) {
+ /* driver does not support manual configuration */
+ dev_warn(dev->class_dev,
+ "driver '%s' does not support attach using comedi_config\n",
+ driv->driver_name);
+ module_put(driv->module);
+ ret = -ENOSYS;
+ goto out;
+ }
+ /* initialize dev->driver here so
+ * comedi_error() can be called from attach */
+ dev->driver = driv;
+ dev->board_name = dev->board_ptr ? *(const char **)dev->board_ptr
+ : dev->driver->driver_name;
+ ret = driv->attach(dev, it);
+ if (ret >= 0)
+ ret = comedi_device_postconfig(dev);
+ if (ret < 0) {
+ comedi_device_detach(dev);
+ module_put(driv->module);
+ }
+ /* On success, the driver module count has been incremented. */
+out:
+ mutex_unlock(&comedi_drivers_list_lock);
+ return ret;
+}
+
+int comedi_auto_config(struct device *hardware_device,
+ struct comedi_driver *driver, unsigned long context)
+{
+ struct comedi_device *dev;
+ int ret;
+
+ if (!hardware_device) {
+ pr_warn("BUG! comedi_auto_config called with NULL hardware_device\n");
+ return -EINVAL;
+ }
+ if (!driver) {
+ dev_warn(hardware_device,
+ "BUG! comedi_auto_config called with NULL comedi driver\n");
+ return -EINVAL;
+ }
+
+ if (!driver->auto_attach) {
+ dev_warn(hardware_device,
+ "BUG! comedi driver '%s' has no auto_attach handler\n",
+ driver->driver_name);
+ return -EINVAL;
+ }
+
+ dev = comedi_alloc_board_minor(hardware_device);
+ if (IS_ERR(dev)) {
+ dev_warn(hardware_device,
+ "driver '%s' could not create device.\n",
+ driver->driver_name);
+ return PTR_ERR(dev);
+ }
+ /* Note: comedi_alloc_board_minor() locked dev->mutex. */
+
+ dev->driver = driver;
+ dev->board_name = dev->driver->driver_name;
+ ret = driver->auto_attach(dev, context);
+ if (ret >= 0)
+ ret = comedi_device_postconfig(dev);
+ mutex_unlock(&dev->mutex);
+
+ if (ret < 0) {
+ dev_warn(hardware_device,
+ "driver '%s' failed to auto-configure device.\n",
+ driver->driver_name);
+ comedi_release_hardware_device(hardware_device);
+ } else {
+ /*
+ * class_dev should be set properly here
+ * after a successful auto config
+ */
+ dev_info(dev->class_dev,
+ "driver '%s' has successfully auto-configured '%s'.\n",
+ driver->driver_name, dev->board_name);
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_auto_config);
+
+void comedi_auto_unconfig(struct device *hardware_device)
+{
+ if (hardware_device == NULL)
+ return;
+ comedi_release_hardware_device(hardware_device);
+}
+EXPORT_SYMBOL_GPL(comedi_auto_unconfig);
+
+int comedi_driver_register(struct comedi_driver *driver)
+{
+ mutex_lock(&comedi_drivers_list_lock);
+ driver->next = comedi_drivers;
+ comedi_drivers = driver;
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_driver_register);
+
+void comedi_driver_unregister(struct comedi_driver *driver)
+{
+ struct comedi_driver *prev;
+ int i;
+
+ /* unlink the driver */
+ mutex_lock(&comedi_drivers_list_lock);
+ if (comedi_drivers == driver) {
+ comedi_drivers = driver->next;
+ } else {
+ for (prev = comedi_drivers; prev->next; prev = prev->next) {
+ if (prev->next == driver) {
+ prev->next = driver->next;
+ break;
+ }
+ }
+ }
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ /* check for devices using this driver */
+ for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) {
+ struct comedi_device *dev = comedi_dev_get_from_minor(i);
+
+ if (!dev)
+ continue;
+
+ mutex_lock(&dev->mutex);
+ if (dev->attached && dev->driver == driver) {
+ if (dev->use_count)
+ dev_warn(dev->class_dev,
+ "BUG! detaching device with use_count=%d\n",
+ dev->use_count);
+ comedi_device_detach(dev);
+ }
+ mutex_unlock(&dev->mutex);
+ comedi_dev_put(dev);
+ }
+}
+EXPORT_SYMBOL_GPL(comedi_driver_unregister);
diff --git a/drivers/staging/comedi/drivers/8253.h b/drivers/staging/comedi/drivers/8253.h
new file mode 100644
index 00000000000..5829b46b757
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8253.h
@@ -0,0 +1,345 @@
+/*
+ comedi/drivers/8253.h
+ Header file for 8253
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _8253_H
+#define _8253_H
+
+#include "../comedi.h"
+
+/*
+ * Common oscillator base values in nanoseconds
+ */
+#define I8254_OSC_BASE_10MHZ 100
+#define I8254_OSC_BASE_5MHZ 200
+#define I8254_OSC_BASE_4MHZ 250
+#define I8254_OSC_BASE_2MHZ 500
+#define I8254_OSC_BASE_1MHZ 1000
+
+static inline void i8253_cascade_ns_to_timer(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 __iomem *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 __iomem *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 __iomem *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_BCD))
+ 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 __iomem *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_BCD))
+ 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 __iomem *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 00000000000..46113a37413
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255.c
@@ -0,0 +1,380 @@
+/*
+ comedi/drivers/8255.c
+ Driver for 8255
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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, io_function, iobase)
+
+ device and subdevice are pointers to the device and subdevice
+ structures. io_function will be called to provide the
+ low-level input/output to the device, i.e., actual register
+ access. io_function will be called with the value of iobase
+ as the last parameter. If the 8255 device is mapped as 4
+ consecutive I/O ports, you can use NULL for io_function
+ and the I/O port base for iobase, 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 <linux/module.h>
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#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_private {
+ unsigned long iobase;
+ int (*io)(int, int, int, unsigned long);
+};
+
+static int subdev_8255_io(int dir, int port, int data, unsigned long iobase)
+{
+ if (dir) {
+ outb(data, iobase + port);
+ return 0;
+ } else {
+ return inb(iobase + port);
+ }
+}
+
+void subdev_8255_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct subdev_8255_private *spriv = s->private;
+ unsigned long iobase = spriv->iobase;
+ unsigned short d;
+
+ d = spriv->io(0, _8255_DATA, 0, iobase);
+ d |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8);
+
+ comedi_buf_put(s, d);
+ s->async->events |= COMEDI_CB_EOS;
+
+ comedi_event(dev, s);
+}
+EXPORT_SYMBOL_GPL(subdev_8255_interrupt);
+
+static int subdev_8255_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct subdev_8255_private *spriv = s->private;
+ unsigned long iobase = spriv->iobase;
+ unsigned int mask;
+ unsigned int v;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ spriv->io(1, _8255_DATA, s->state & 0xff, iobase);
+ if (mask & 0xff00)
+ spriv->io(1, _8255_DATA + 1, (s->state >> 8) & 0xff,
+ iobase);
+ if (mask & 0xff0000)
+ spriv->io(1, _8255_DATA + 2, (s->state >> 16) & 0xff,
+ iobase);
+ }
+
+ v = spriv->io(0, _8255_DATA, 0, iobase);
+ v |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8);
+ v |= (spriv->io(0, _8255_DATA + 2, 0, iobase) << 16);
+
+ data[1] = v;
+
+ return insn->n;
+}
+
+static void subdev_8255_do_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct subdev_8255_private *spriv = s->private;
+ unsigned long iobase = spriv->iobase;
+ 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;
+
+ spriv->io(1, _8255_CR, config, iobase);
+}
+
+static int subdev_8255_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ subdev_8255_do_config(dev, s);
+
+ return insn->n;
+}
+
+static int subdev_8255_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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 (*io)(int, int, int, unsigned long),
+ unsigned long iobase)
+{
+ struct subdev_8255_private *spriv;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+
+ spriv->iobase = iobase;
+ spriv->io = io ? io : subdev_8255_io;
+
+ 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->insn_bits = subdev_8255_insn;
+ s->insn_config = subdev_8255_insn_config;
+
+ subdev_8255_do_config(dev, s);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(subdev_8255_init);
+
+int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s,
+ int (*io)(int, int, int, unsigned long),
+ unsigned long iobase)
+{
+ int ret;
+
+ ret = subdev_8255_init(dev, s, io, iobase);
+ if (ret)
+ return ret;
+
+ s->len_chanlist = 1;
+ s->do_cmdtest = subdev_8255_cmdtest;
+ s->do_cmd = subdev_8255_cmd;
+ s->cancel = subdev_8255_cancel;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(subdev_8255_init_irq);
+
+/*
+
+ Start of the 8255 standalone device
+
+ */
+
+static int dev_8255_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+ unsigned long iobase;
+ int i;
+
+ for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
+ iobase = it->options[i];
+ if (!iobase)
+ break;
+ }
+ if (i == 0) {
+ dev_warn(dev->class_dev, "no devices specified\n");
+ return -EINVAL;
+ }
+
+ ret = comedi_alloc_subdevices(dev, i);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ iobase = it->options[i];
+
+ ret = __comedi_request_region(dev, iobase, _8255_SIZE);
+ if (ret) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = subdev_8255_init(dev, s, NULL, iobase);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void dev_8255_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ struct subdev_8255_private *spriv;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (s->type != COMEDI_SUBD_UNUSED) {
+ spriv = s->private;
+ release_region(spriv->iobase, _8255_SIZE);
+ }
+ }
+}
+
+static struct comedi_driver dev_8255_driver = {
+ .driver_name = "8255",
+ .module = THIS_MODULE,
+ .attach = dev_8255_attach,
+ .detach = dev_8255_detach,
+};
+module_comedi_driver(dev_8255_driver);
+
+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 00000000000..795d232a6c0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255.h
@@ -0,0 +1,33 @@
+/*
+ module/8255.h
+ Header file for 8255
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _8255_H
+#define _8255_H
+
+#include "../comedidev.h"
+
+int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s,
+ int (*io)(int, int, int, unsigned long),
+ unsigned long iobase);
+int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s,
+ int (*io)(int, int, int, unsigned long),
+ unsigned long iobase);
+void subdev_8255_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/8255_pci.c b/drivers/staging/comedi/drivers/8255_pci.c
new file mode 100644
index 00000000000..46a385c29ba
--- /dev/null
+++ b/drivers/staging/comedi/drivers/8255_pci.c
@@ -0,0 +1,329 @@
+/*
+ * COMEDI driver for generic PCI based 8255 digital i/o boards
+ * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the tested adl_pci7296 driver written by:
+ * Jon Grierson <jd@renko.co.uk>
+ * and the experimental cb_pcidio driver written by:
+ * Yoshiya Matsuzaka
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+Driver: 8255_pci
+Description: Generic PCI based 8255 Digital I/O boards
+Devices: (ADLink) PCI-7224 [adl_pci-7224] - 24 channels
+ (ADLink) PCI-7248 [adl_pci-7248] - 48 channels
+ (ADLink) PCI-7296 [adl_pci-7296] - 96 channels
+ (Measurement Computing) PCI-DIO24 [cb_pci-dio24] - 24 channels
+ (Measurement Computing) PCI-DIO24H [cb_pci-dio24h] - 24 channels
+ (Measurement Computing) PCI-DIO48H [cb_pci-dio48h] - 48 channels
+ (Measurement Computing) PCI-DIO96H [cb_pci-dio96h] - 96 channels
+ (National Instruments) PCI-DIO-96 [ni_pci-dio-96] - 96 channels
+ (National Instruments) PCI-DIO-96B [ni_pci-dio-96b] - 96 channels
+ (National Instruments) PXI-6508 [ni_pxi-6508] - 96 channels
+ (National Instruments) PCI-6503 [ni_pci-6503] - 24 channels
+ (National Instruments) PCI-6503B [ni_pci-6503b] - 24 channels
+ (National Instruments) PCI-6503X [ni_pci-6503x] - 24 channels
+ (National Instruments) PXI-6503 [ni_pxi-6503] - 24 channels
+Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+Updated: Wed, 12 Sep 2012 11:52:01 -0700
+Status: untested
+
+Some of these boards also have an 8254 programmable timer/counter
+chip. This chip is not currently supported by this driver.
+
+Interrupt support for these boards is also not currently supported.
+
+Configuration Options: not applicable, uses PCI auto config
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "mite.h"
+
+enum pci_8255_boardid {
+ BOARD_ADLINK_PCI7224,
+ BOARD_ADLINK_PCI7248,
+ BOARD_ADLINK_PCI7296,
+ BOARD_CB_PCIDIO24,
+ BOARD_CB_PCIDIO24H,
+ BOARD_CB_PCIDIO48H_OLD,
+ BOARD_CB_PCIDIO48H_NEW,
+ BOARD_CB_PCIDIO96H,
+ BOARD_NI_PCIDIO96,
+ BOARD_NI_PCIDIO96B,
+ BOARD_NI_PXI6508,
+ BOARD_NI_PCI6503,
+ BOARD_NI_PCI6503B,
+ BOARD_NI_PCI6503X,
+ BOARD_NI_PXI_6503,
+};
+
+struct pci_8255_boardinfo {
+ const char *name;
+ int dio_badr;
+ int n_8255;
+ unsigned int has_mite:1;
+};
+
+static const struct pci_8255_boardinfo pci_8255_boards[] = {
+ [BOARD_ADLINK_PCI7224] = {
+ .name = "adl_pci-7224",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_ADLINK_PCI7248] = {
+ .name = "adl_pci-7248",
+ .dio_badr = 2,
+ .n_8255 = 2,
+ },
+ [BOARD_ADLINK_PCI7296] = {
+ .name = "adl_pci-7296",
+ .dio_badr = 2,
+ .n_8255 = 4,
+ },
+ [BOARD_CB_PCIDIO24] = {
+ .name = "cb_pci-dio24",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_CB_PCIDIO24H] = {
+ .name = "cb_pci-dio24h",
+ .dio_badr = 2,
+ .n_8255 = 1,
+ },
+ [BOARD_CB_PCIDIO48H_OLD] = {
+ .name = "cb_pci-dio48h",
+ .dio_badr = 1,
+ .n_8255 = 2,
+ },
+ [BOARD_CB_PCIDIO48H_NEW] = {
+ .name = "cb_pci-dio48h",
+ .dio_badr = 2,
+ .n_8255 = 2,
+ },
+ [BOARD_CB_PCIDIO96H] = {
+ .name = "cb_pci-dio96h",
+ .dio_badr = 2,
+ .n_8255 = 4,
+ },
+ [BOARD_NI_PCIDIO96] = {
+ .name = "ni_pci-dio-96",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCIDIO96B] = {
+ .name = "ni_pci-dio-96b",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PXI6508] = {
+ .name = "ni_pxi-6508",
+ .dio_badr = 1,
+ .n_8255 = 4,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503] = {
+ .name = "ni_pci-6503",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503B] = {
+ .name = "ni_pci-6503b",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PCI6503X] = {
+ .name = "ni_pci-6503x",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+ [BOARD_NI_PXI_6503] = {
+ .name = "ni_pxi-6503",
+ .dio_badr = 1,
+ .n_8255 = 1,
+ .has_mite = 1,
+ },
+};
+
+struct pci_8255_private {
+ void __iomem *mmio_base;
+};
+
+static int pci_8255_mite_init(struct pci_dev *pcidev)
+{
+ void __iomem *mite_base;
+ u32 main_phys_addr;
+
+ /* ioremap the MITE registers (BAR 0) temporarily */
+ mite_base = pci_ioremap_bar(pcidev, 0);
+ if (!mite_base)
+ return -ENOMEM;
+
+ /* set data window to main registers (BAR 1) */
+ main_phys_addr = pci_resource_start(pcidev, 1);
+ writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR);
+
+ /* finished with MITE registers */
+ iounmap(mite_base);
+ return 0;
+}
+
+static int pci_8255_mmio(int dir, int port, int data, unsigned long iobase)
+{
+ void __iomem *mmio_base = (void __iomem *)iobase;
+
+ if (dir) {
+ writeb(data, mmio_base + port);
+ return 0;
+ } else {
+ return readb(mmio_base + port);
+ }
+}
+
+static int pci_8255_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pci_8255_boardinfo *board = NULL;
+ struct pci_8255_private *devpriv;
+ struct comedi_subdevice *s;
+ bool is_mmio;
+ int ret;
+ int i;
+
+ if (context < ARRAY_SIZE(pci_8255_boards))
+ board = &pci_8255_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ if (board->has_mite) {
+ ret = pci_8255_mite_init(pcidev);
+ if (ret)
+ return ret;
+ }
+
+ is_mmio = (pci_resource_flags(pcidev, board->dio_badr) &
+ IORESOURCE_MEM) != 0;
+ if (is_mmio) {
+ devpriv->mmio_base = pci_ioremap_bar(pcidev, board->dio_badr);
+ if (!devpriv->mmio_base)
+ return -ENOMEM;
+ } else {
+ dev->iobase = pci_resource_start(pcidev, board->dio_badr);
+ }
+
+ /*
+ * One, two, or four subdevices are setup by this driver depending
+ * on the number of channels provided by the board. Each subdevice
+ * has 24 channels supported by the 8255 module.
+ */
+ ret = comedi_alloc_subdevices(dev, board->n_8255);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < board->n_8255; i++) {
+ unsigned long iobase;
+
+ s = &dev->subdevices[i];
+ if (is_mmio) {
+ iobase = (unsigned long)(devpriv->mmio_base + (i * 4));
+ ret = subdev_8255_init(dev, s, pci_8255_mmio, iobase);
+ } else {
+ iobase = dev->iobase + (i * 4);
+ ret = subdev_8255_init(dev, s, NULL, iobase);
+ }
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void pci_8255_detach(struct comedi_device *dev)
+{
+ struct pci_8255_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->mmio_base)
+ iounmap(devpriv->mmio_base);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver pci_8255_driver = {
+ .driver_name = "8255_pci",
+ .module = THIS_MODULE,
+ .auto_attach = pci_8255_auto_attach,
+ .detach = pci_8255_detach,
+};
+
+static int pci_8255_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &pci_8255_driver, id->driver_data);
+}
+
+static const struct pci_device_id pci_8255_pci_table[] = {
+ { PCI_VDEVICE(ADLINK, 0x7224), BOARD_ADLINK_PCI7224 },
+ { PCI_VDEVICE(ADLINK, 0x7248), BOARD_ADLINK_PCI7248 },
+ { PCI_VDEVICE(ADLINK, 0x7296), BOARD_ADLINK_PCI7296 },
+ { PCI_VDEVICE(CB, 0x0028), BOARD_CB_PCIDIO24 },
+ { PCI_VDEVICE(CB, 0x0014), BOARD_CB_PCIDIO24H },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, 0x0000, 0x0000),
+ .driver_data = BOARD_CB_PCIDIO48H_OLD },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_CB, 0x000b, PCI_VENDOR_ID_CB, 0x000b),
+ .driver_data = BOARD_CB_PCIDIO48H_NEW },
+ { PCI_VDEVICE(CB, 0x0017), BOARD_CB_PCIDIO96H },
+ { PCI_VDEVICE(NI, 0x0160), BOARD_NI_PCIDIO96 },
+ { PCI_VDEVICE(NI, 0x1630), BOARD_NI_PCIDIO96B },
+ { PCI_VDEVICE(NI, 0x13c0), BOARD_NI_PXI6508 },
+ { PCI_VDEVICE(NI, 0x0400), BOARD_NI_PCI6503 },
+ { PCI_VDEVICE(NI, 0x1250), BOARD_NI_PCI6503B },
+ { PCI_VDEVICE(NI, 0x17d0), BOARD_NI_PCI6503X },
+ { PCI_VDEVICE(NI, 0x1800), BOARD_NI_PXI_6503 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, pci_8255_pci_table);
+
+static struct pci_driver pci_8255_pci_driver = {
+ .name = "8255_pci",
+ .id_table = pci_8255_pci_table,
+ .probe = pci_8255_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(pci_8255_driver, pci_8255_pci_driver);
+
+MODULE_DESCRIPTION("COMEDI - Generic PCI based 8255 Digital I/O boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile
new file mode 100644
index 00000000000..0757a82ddcf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/Makefile
@@ -0,0 +1,142 @@
+# Makefile for individual comedi drivers
+#
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+# Comedi "helper" modules
+
+# 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_AMPLC_DIO200_ISA) += amplc_dio200.o
+obj-$(CONFIG_COMEDI_AMPLC_PC263_ISA) += amplc_pc263.o
+obj-$(CONFIG_COMEDI_PCL711) += pcl711.o
+obj-$(CONFIG_COMEDI_PCL724) += pcl724.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_RTI800) += rti800.o
+obj-$(CONFIG_COMEDI_RTI802) += rti802.o
+obj-$(CONFIG_COMEDI_DAC02) += dac02.o
+obj-$(CONFIG_COMEDI_DAS16M1) += das16m1.o
+obj-$(CONFIG_COMEDI_DAS08_ISA) += das08_isa.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_II_PCI20KC) += ii_pci20kc.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_S526) += s526.o
+
+# Comedi PCI drivers
+obj-$(CONFIG_COMEDI_8255_PCI) += 8255_pci.o
+obj-$(CONFIG_COMEDI_ADDI_WATCHDOG) += addi_watchdog.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_2032) += addi_apci_2032.o
+obj-$(CONFIG_COMEDI_ADDI_APCI_2200) += addi_apci_2200.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_PCI7X3X) += adl_pci7x3x.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_PCI1724) += adv_pci1724.o
+obj-$(CONFIG_COMEDI_ADV_PCI_DIO) += adv_pci_dio.o
+obj-$(CONFIG_COMEDI_AMPLC_DIO200_PCI) += amplc_dio200_pci.o
+obj-$(CONFIG_COMEDI_AMPLC_PC236) += amplc_pc236.o
+obj-$(CONFIG_COMEDI_AMPLC_PC263_PCI) += amplc_pci263.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_DAS08_PCI) += das08_pci.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_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_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_LABPC_PCI) += ni_labpc_pci.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_S626) += s626.o
+obj-$(CONFIG_COMEDI_SSV_DNP) += ssv_dnp.o
+obj-$(CONFIG_COMEDI_MF6X4) += mf6x4.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_TIOCMD) += ni_tiocmd.o
+obj-$(CONFIG_COMEDI_NI_LABPC) += ni_labpc.o
+obj-$(CONFIG_COMEDI_NI_LABPC_ISADMA) += ni_labpc_isadma.o
+
+obj-$(CONFIG_COMEDI_8255) += 8255.o
+obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200_common.o
+obj-$(CONFIG_COMEDI_DAS08) += das08.o
+obj-$(CONFIG_COMEDI_FC) += comedi_fc.o
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 00000000000..dc87df03220
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/addi_common.c
@@ -0,0 +1,284 @@
+/**
+@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.
+
+@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 |
+ +-----------------------------------------------------------------------+
+*/
+
+static int i_ADDIDATA_InsnReadEeprom(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ unsigned short w_Address = CR_CHAN(insn->chanspec);
+ unsigned short w_Data;
+
+ w_Data = addi_eeprom_readw(devpriv->i_IobaseAmcc,
+ this_board->pc_EepromChip, 2 * w_Address);
+ data[0] = w_Data;
+
+ return insn->n;
+}
+
+static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ const struct addi_board *this_board = comedi_board(dev);
+
+ this_board->interrupt(irq, d);
+ return IRQ_RETVAL(1);
+}
+
+static int i_ADDI_Reset(struct comedi_device *dev)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+
+ this_board->reset(dev);
+ return 0;
+}
+
+static int addi_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, n_subdevices;
+ unsigned int dw_Dummy;
+
+ dev->board_name = this_board->pc_DriverName;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ if (this_board->i_IorangeBase1)
+ dev->iobase = pci_resource_start(pcidev, 1);
+ else
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ devpriv->iobase = dev->iobase;
+ devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
+ devpriv->i_IobaseAddon = pci_resource_start(pcidev, 2);
+ devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3);
+
+ /* 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_Timer = this_board->i_Timer;
+ devpriv->s_EeParameters.ui_MinAcquisitiontimeNs =
+ this_board->ui_MinAcquisitiontimeNs;
+ devpriv->s_EeParameters.ui_MinDelaytimeNs =
+ this_board->ui_MinDelaytimeNs;
+
+ /* ## */
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ /* Read eepeom and fill addi_board Structure */
+
+ if (this_board->i_PCIEeprom) {
+ if (!(strcmp(this_board->pc_EepromChip, "S5920"))) {
+ /* Set 3 wait stait */
+ if (!(strcmp(dev->board_name, "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);
+ }
+ addi_eeprom_read_info(dev, pci_resource_start(pcidev, 0));
+ }
+
+ n_subdevices = 7;
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ 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;
+
+ s->insn_config = this_board->ai_config;
+ s->insn_read = this_board->ai_read;
+ s->insn_write = this_board->ai_write;
+ s->insn_bits = this_board->ai_bits;
+ s->do_cmdtest = this_board->ai_cmdtest;
+ s->do_cmd = this_board->ai_cmd;
+ s->cancel = this_board->ai_cancel;
+
+ } 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->insn_write = this_board->ao_write;
+ } 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->insn_config = this_board->di_config;
+ s->insn_read = this_board->di_read;
+ s->insn_write = this_board->di_write;
+ s->insn_bits = this_board->di_bits;
+ } 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;
+
+ /* insn_config - for digital output memory */
+ s->insn_config = this_board->do_config;
+ s->insn_write = this_board->do_write;
+ s->insn_bits = this_board->do_bits;
+ s->insn_read = this_board->do_read;
+ } 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->timer_write;
+ s->insn_read = this_board->timer_read;
+ s->insn_config = this_board->timer_config;
+ s->insn_bits = this_board->timer_bits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Allocate and Initialise TTL */
+ s = &dev->subdevices[5];
+ 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;
+ }
+
+ i_ADDI_Reset(dev);
+ return 0;
+}
+
+static void i_ADDI_Detach(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ i_ADDI_Reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ }
+ comedi_pci_disable(dev);
+}
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 00000000000..5c6a11c35de
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/addi_common.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.
+ */
+
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+
+#define LOWORD(W) (unsigned short)((W) & 0xFFFF)
+#define HIWORD(W) (unsigned short)(((W) >> 16) & 0xFFFF)
+
+#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"
+
+/* 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_IorangeBase1;
+ 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 */
+
+ 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_Timer; /* timer subdevice present or not */
+ unsigned int ui_MinAcquisitiontimeNs; /* Minimum Acquisition in Nano secs */
+ unsigned int ui_MinDelaytimeNs; /* Minimum Delay in Nano secs */
+
+ /* interrupt and reset */
+ void (*interrupt)(int irq, void *d);
+ int (*reset)(struct comedi_device *);
+
+ /* Subdevice functions */
+
+ /* ANALOG INPUT */
+ int (*ai_config)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*ai_read)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*ai_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*ai_bits)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*ai_cmdtest)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_cmd *);
+ int (*ai_cmd)(struct comedi_device *, struct comedi_subdevice *);
+ int (*ai_cancel)(struct comedi_device *, struct comedi_subdevice *);
+
+ /* Analog Output */
+ int (*ao_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+ /* Digital Input */
+ int (*di_config)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*di_read)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*di_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*di_bits)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+ /* Digital Output */
+ int (*do_config)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*do_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*do_bits)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*do_read)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+
+ /* TIMER */
+ int (*timer_config)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*timer_write)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*timer_read)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+ int (*timer_bits)(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *);
+};
+
+struct addi_private {
+ int iobase;
+ int i_IobaseAmcc; /* base+size for AMCC chip */
+ int i_IobaseAddon; /* addon base address */
+ int i_IobaseReserved;
+ unsigned int ui_AiActualScan; /* how many scans we finished */
+ unsigned int ui_AiNbrofChannels; /* how many channels is measured */
+ unsigned int ui_AiChannelList[32]; /* actual chanlist */
+ unsigned int ui_AiReadData[32];
+ 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 */
+ unsigned short *ul_DmaBufferVirtual[2]; /* pointers to 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_DmaBufferPages[2]; /* number of pages in buffer */
+ unsigned char b_DigitalOutputRegister; /* Digital Output Register */
+ unsigned char b_OutputMemoryStatus;
+ 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_Timer2Mode; /* Specify the timer 2 mode */
+ unsigned char b_Timer2Interrupt; /* Timer2 interrupt enable or disable */
+ unsigned int ai_running:1;
+ unsigned char b_InterruptMode; /* eoc eos or dma */
+ unsigned char b_EocEosInterrupt; /* Enable disable eoc eos interrupt */
+ unsigned int ui_EocEosConversionTime;
+ unsigned char b_SingelDiff;
+ unsigned char b_ExttrigEnable; /* To enable or disable external trigger */
+
+ /* Pointer to the current process */
+ struct task_struct *tsk_Current;
+
+ /* 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_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;
+};
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 00000000000..aafc172f3a9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c
@@ -0,0 +1,360 @@
+/*
+ * addi_eeprom.c - ADDI EEPROM Module
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+
+#define NVRAM_USER_DATA_START 0x100
+
+#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 EE93C76_CLK_BIT (1 << 0)
+#define EE93C76_CS_BIT (1 << 1)
+#define EE93C76_DOUT_BIT (1 << 2)
+#define EE93C76_DIN_BIT (1 << 3)
+#define EE93C76_READ_CMD (0x0180 << 4)
+#define EE93C76_CMD_LEN 13
+
+#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
+
+static void addi_eeprom_clk_93c76(unsigned long iobase, unsigned int val)
+{
+ outl(val & ~EE93C76_CLK_BIT, iobase);
+ udelay(100);
+
+ outl(val | EE93C76_CLK_BIT, iobase);
+ udelay(100);
+}
+
+static unsigned int addi_eeprom_cmd_93c76(unsigned long iobase,
+ unsigned int cmd,
+ unsigned char len)
+{
+ unsigned int val = EE93C76_CS_BIT;
+ int i;
+
+ /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */
+ outl(val, iobase);
+ udelay(100);
+
+ /* Send EEPROM command - one bit at a time */
+ for (i = (len - 1); i >= 0; i--) {
+ if (cmd & (1 << i))
+ val |= EE93C76_DOUT_BIT;
+ else
+ val &= ~EE93C76_DOUT_BIT;
+
+ /* Write the command */
+ outl(val, iobase);
+ udelay(100);
+
+ addi_eeprom_clk_93c76(iobase, val);
+ }
+ return val;
+}
+
+static unsigned short addi_eeprom_readw_93c76(unsigned long iobase,
+ unsigned short addr)
+{
+ unsigned short val = 0;
+ unsigned int cmd;
+ unsigned int tmp;
+ int i;
+
+ /* Send EEPROM read command and offset to EEPROM */
+ cmd = EE93C76_READ_CMD | (addr / 2);
+ cmd = addi_eeprom_cmd_93c76(iobase, cmd, EE93C76_CMD_LEN);
+
+ /* Get the 16-bit value */
+ for (i = 0; i < 16; i++) {
+ addi_eeprom_clk_93c76(iobase, cmd);
+
+ tmp = inl(iobase);
+ udelay(100);
+
+ val <<= 1;
+ if (tmp & EE93C76_DIN_BIT)
+ val |= 0x1;
+ }
+
+ /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */
+ outl(0, iobase);
+ udelay(100);
+
+ return val;
+}
+
+static void addi_eeprom_nvram_wait(unsigned long iobase)
+{
+ unsigned char val;
+
+ do {
+ val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD);
+ } while (val & 0x80);
+}
+
+static unsigned short addi_eeprom_readw_nvram(unsigned long iobase,
+ unsigned short addr)
+{
+ unsigned short val = 0;
+ unsigned char tmp;
+ unsigned char i;
+
+ for (i = 0; i < 2; i++) {
+ /* Load the low 8 bit address */
+ outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ addi_eeprom_nvram_wait(iobase);
+ outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+ addi_eeprom_nvram_wait(iobase);
+
+ /* Load the high 8 bit address */
+ outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ addi_eeprom_nvram_wait(iobase);
+ outb(((addr + i) >> 8) & 0xff,
+ iobase + AMCC_OP_REG_MCSR_NVDATA);
+ addi_eeprom_nvram_wait(iobase);
+
+ /* Read the eeprom data byte */
+ outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ addi_eeprom_nvram_wait(iobase);
+ tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
+ addi_eeprom_nvram_wait(iobase);
+
+ if (i == 0)
+ val |= tmp;
+ else
+ val |= (tmp << 8);
+ }
+
+ return val;
+}
+
+static unsigned short addi_eeprom_readw(unsigned long iobase,
+ char *type,
+ unsigned short addr)
+{
+ unsigned short val = 0;
+
+ /* Add the offset to the start of the user data */
+ addr += NVRAM_USER_DATA_START;
+
+ if (!strcmp(type, "S5920") || !strcmp(type, "S5933"))
+ val = addi_eeprom_readw_nvram(iobase, addr);
+
+ if (!strcmp(type, "93C76"))
+ val = addi_eeprom_readw_93c76(iobase, addr);
+
+ return val;
+}
+
+static void addi_eeprom_read_di_info(struct comedi_device *dev,
+ unsigned long iobase,
+ unsigned short addr)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ char *type = this_board->pc_EepromChip;
+ unsigned short tmp;
+
+ /* Number of channels */
+ tmp = addi_eeprom_readw(iobase, type, addr + 6);
+ devpriv->s_EeParameters.i_NbrDiChannel = tmp;
+
+ /* Interruptible or not */
+ tmp = addi_eeprom_readw(iobase, type, addr + 8);
+ tmp = (tmp >> 7) & 0x01;
+
+ /* How many interruptible logic */
+ tmp = addi_eeprom_readw(iobase, type, addr + 10);
+}
+
+static void addi_eeprom_read_do_info(struct comedi_device *dev,
+ unsigned long iobase,
+ unsigned short addr)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ char *type = this_board->pc_EepromChip;
+ unsigned short tmp;
+
+ /* Number of channels */
+ tmp = addi_eeprom_readw(iobase, type, addr + 6);
+ devpriv->s_EeParameters.i_NbrDoChannel = tmp;
+
+ devpriv->s_EeParameters.i_DoMaxdata = 0xffffffff >> (32 - tmp);
+}
+
+static void addi_eeprom_read_timer_info(struct comedi_device *dev,
+ unsigned long iobase,
+ unsigned short addr)
+{
+ struct addi_private *devpriv = dev->private;
+#if 0
+ const struct addi_board *this_board = comedi_board(dev);
+ char *type = this_board->pc_EepromChip;
+ unsigned short offset = 0;
+ unsigned short ntimers;
+ unsigned short tmp;
+ int i;
+
+ /* Number of Timers */
+ ntimers = addi_eeprom_readw(iobase, type, addr + 6);
+
+ /* Read header size */
+ for (i = 0; i < ntimers; i++) {
+ unsigned short size;
+ unsigned short res;
+ unsigned short mode;
+ unsigned short min_timing;
+ unsigned short timebase;
+
+ size = addi_eeprom_readw(iobase, type, addr + 8 + offset + 0);
+
+ /* Resolution / Mode */
+ tmp = addi_eeprom_readw(iobase, type, addr + 8 + offset + 2);
+ res = (tmp >> 10) & 0x3f;
+ mode = (tmp >> 4) & 0x3f;
+
+ /* MinTiming / Timebase */
+ tmp = addi_eeprom_readw(iobase, type, addr + 8 + offset + 4);
+ min_timing = (tmp >> 6) & 0x3ff;
+ Timebase = tmp & 0x3f;
+
+ offset += size;
+ }
+#endif
+ /* Timer subdevice present */
+ devpriv->s_EeParameters.i_Timer = 1;
+}
+
+static void addi_eeprom_read_ao_info(struct comedi_device *dev,
+ unsigned long iobase,
+ unsigned short addr)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ char *type = this_board->pc_EepromChip;
+ unsigned short tmp;
+
+ /* No of channels for 1st hard component */
+ tmp = addi_eeprom_readw(iobase, type, addr + 10);
+ devpriv->s_EeParameters.i_NbrAoChannel = (tmp >> 4) & 0x3ff;
+
+ /* Resolution for 1st hard component */
+ tmp = addi_eeprom_readw(iobase, type, addr + 16);
+ tmp = (tmp >> 8) & 0xff;
+ devpriv->s_EeParameters.i_AoMaxdata = 0xfff >> (16 - tmp);
+}
+
+static void addi_eeprom_read_ai_info(struct comedi_device *dev,
+ unsigned long iobase,
+ unsigned short addr)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ char *type = this_board->pc_EepromChip;
+ unsigned short offset;
+ unsigned short tmp;
+
+ /* No of channels for 1st hard component */
+ tmp = addi_eeprom_readw(iobase, type, addr + 10);
+ devpriv->s_EeParameters.i_NbrAiChannel = (tmp >> 4) & 0x3ff;
+ if (!strcmp(this_board->pc_DriverName, "apci3200"))
+ devpriv->s_EeParameters.i_NbrAiChannel *= 4;
+
+ tmp = addi_eeprom_readw(iobase, type, addr + 16);
+ devpriv->s_EeParameters.ui_MinAcquisitiontimeNs = tmp * 1000;
+
+ tmp = addi_eeprom_readw(iobase, type, addr + 30);
+ devpriv->s_EeParameters.ui_MinDelaytimeNs = tmp * 1000;
+
+ tmp = addi_eeprom_readw(iobase, type, addr + 20);
+ /* dma = (tmp >> 13) & 0x01; */
+
+ tmp = addi_eeprom_readw(iobase, type, addr + 72) & 0xff;
+ if (tmp) { /* > 0 */
+ /* offset of first analog input single header */
+ offset = 74 + (2 * tmp) + (10 * (1 + (tmp / 16)));
+ } else { /* = 0 */
+ offset = 74;
+ }
+
+ /* Resolution */
+ tmp = addi_eeprom_readw(iobase, type, addr + offset + 2) & 0x1f;
+ devpriv->s_EeParameters.i_AiMaxdata = 0xffff >> (16 - tmp);
+}
+
+static void addi_eeprom_read_info(struct comedi_device *dev,
+ unsigned long iobase)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ char *type = this_board->pc_EepromChip;
+ unsigned short size;
+ unsigned char nfuncs;
+ int i;
+
+ size = addi_eeprom_readw(iobase, type, 8);
+ nfuncs = addi_eeprom_readw(iobase, type, 10) & 0xff;
+
+ /* Read functionality details */
+ for (i = 0; i < nfuncs; i++) {
+ unsigned short offset = i * 4;
+ unsigned short addr;
+ unsigned char func;
+
+ func = addi_eeprom_readw(iobase, type, 12 + offset) & 0x3f;
+ addr = addi_eeprom_readw(iobase, type, 14 + offset);
+
+ switch (func) {
+ case EEPROM_DIGITALINPUT:
+ addi_eeprom_read_di_info(dev, iobase, addr);
+ break;
+
+ case EEPROM_DIGITALOUTPUT:
+ addi_eeprom_read_do_info(dev, iobase, addr);
+ break;
+
+ case EEPROM_ANALOGINPUT:
+ addi_eeprom_read_ai_info(dev, iobase, addr);
+ break;
+
+ case EEPROM_ANALOGOUTPUT:
+ addi_eeprom_read_ao_info(dev, iobase, addr);
+ break;
+
+ case EEPROM_TIMER:
+ case EEPROM_WATCHDOG:
+ case EEPROM_TIMER_WATCHDOG_COUNTER:
+ addi_eeprom_read_timer_info(dev, iobase, addr);
+ break;
+ }
+ }
+}
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 00000000000..28450f65a13
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c
@@ -0,0 +1,485 @@
+/*
+ * 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.
+ */
+
+/* Card Specific information */
+#define APCI035_ADDRESS_RANGE 255
+
+/* 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
+
+/* 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)
+ }
+};
+
+static int i_WatchdogNbr;
+static int i_Temp;
+static int i_Flag = 1;
+
+/*
+ * Configures The Timer , Counter or Watchdog
+ *
+ * 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
+ */
+static int apci035_timer_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_Status;
+ unsigned int ui_Command;
+ unsigned int ui_Mode;
+
+ 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 = 0;
+ outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12);
+
+ 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;
+
+ } 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 {
+ dev_err(dev->class_dev, "The parameter for Timer/watchdog selection is in error\n");
+ return -EINVAL;
+ }
+
+ outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12);
+
+ 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 = 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 = 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 = 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 = 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;
+}
+
+/*
+ * Start / Stop The Selected Timer , or Watchdog
+ *
+ * data[0]
+ * 0 - Stop Selected Timer/Watchdog
+ * 1 - Start Selected Timer/Watch*dog
+ * 2 - Trigger Selected Timer/Watchdog
+ * 3 - Stop All Timer/Watchdog
+ * 4 - Start All Timer/Watchdog
+ * 5 - Trigger All Timer/Watchdog
+ */
+static int apci035_timer_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_Command;
+ int i_Count;
+
+ 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] == 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 */
+ 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[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;
+}
+
+/*
+ * Read The Selected Timer , Counter or Watchdog
+ *
+ * data[0] software trigger status
+ * data[1] hardware trigger status
+ * data[2] Software clear status
+ * data[3] Overflow status
+ * data[4] Timer actual value
+ */
+static int apci035_timer_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_Status; /* 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);
+
+ return insn->n;
+}
+
+/*
+ * Configures The Analog Input Subdevice
+ *
+ * data[0] Warning delay value
+ */
+static int apci035_ai_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ 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;
+}
+
+/*
+ * Read value of the selected channel
+ *
+ * data[0] Digital Value Of Input
+ */
+static int apci035_ai_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_CommandRegister;
+
+ /* 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;
+}
+
+static int apci035_reset(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ int i_Count;
+
+ for (i_Count = 1; i_Count <= 4; i_Count++) {
+ i_WatchdogNbr = i_Count;
+
+ /* stop all timers */
+ outl(0x0, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 0);
+ }
+ outl(0x0, devpriv->iobase + 128 + 12); /* Disable the warning delay */
+
+ return 0;
+}
+
+static void apci035_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_StatusRegister1;
+ unsigned int ui_StatusRegister2;
+ unsigned int ui_ReadCommand;
+ unsigned int ui_ChannelNumber;
+ unsigned int ui_DigitalTemperature;
+
+ 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);
+
+ /* Test if warning relay interrupt */
+ if ((((ui_StatusRegister1) & 0x8) == 0x8)) {
+
+ /* 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 signal to the sample */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+
+ } else if ((ui_StatusRegister2 & 0x1) == 0x1) {
+ /* send signal to the sample */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+ }
+
+ return;
+}
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 00000000000..a633957890d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c
@@ -0,0 +1,2413 @@
+/*
+ * 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.
+ *
+ */
+
+/* Card Specific information */
+#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
+};
+
+static int i_TimerCounter1Init;
+static int i_TimerCounter2Init;
+static int i_WatchdogCounter3Init;
+static int i_Event1Status, i_Event2Status;
+static int i_TimerCounterWatchdogInterrupt;
+static int i_Logic, i_CounterLogic;
+static int i_InterruptMask;
+static int i_InputChannel;
+static int i_TimerCounter1Enabled, i_TimerCounter2Enabled,
+ i_WatchdogCounter3Enabled;
+
+/*
+ * 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.
+ *
+ * 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:
+ * APCI1500_AND This logic links the inputs with an AND logic.
+ * APCI1500_OR This logic links the inputs with a OR logic.
+ * 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
+ */
+static int apci1500_di_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "The 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:
+ dev_warn(dev->hw_dev,
+ "The 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:
+ dev_warn(dev->hw_dev,
+ "The 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) {
+ dev_warn(dev->hw_dev,
+ "Transition 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) {
+ dev_warn(dev->hw_dev,
+ "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 {
+ dev_warn(dev->hw_dev,
+ "The 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 {
+ dev_warn(dev->hw_dev,
+ "The choice for interrupt logic does not exist\n");
+ return -EINVAL;
+ } /* elseif (data[1] == APCI1500_OR) */
+ } /* if(data[0]==2) */
+
+ return insn->n;
+}
+
+/*
+ * Allows or disallows a port event
+ *
+ * data[0] 0 = Start input event, 1 = Stop input event
+ * data[1] Number of port (1 or 2)
+ */
+static int apci1500_di_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "Event 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 {
+ dev_warn(dev->hw_dev,
+ "Event 2 not initialised\n");
+ return -EINVAL;
+ } /* else if(i_Event2Status==1) */
+ } /* if(data[1]==2) */
+ } /* if (data[1] == 1 || data[0] == 2) */
+ else {
+ dev_warn(dev->hw_dev,
+ "The 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 {
+ dev_warn(dev->hw_dev,
+ "Event 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 {
+ dev_warn(dev->hw_dev,
+ "Event 2 not initialised\n");
+ return -EINVAL;
+ } /* else if(i_Event2Status==1) */
+ } /* if(data[1]==2) */
+
+ } /* if (data[1] == 1 || data[1] == 2) */
+ else {
+ dev_warn(dev->hw_dev,
+ "The port parameter is in error\n");
+ return -EINVAL;
+ } /* else if (data[1] == 1 || data[1] == 2) */
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "The option of START/STOP logic does not exist\n");
+ return -EINVAL;
+ } /* switch(data[0]) */
+
+ return insn->n;
+}
+
+/*
+ * Return the status of the digital input
+ */
+static int apci1500_di_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int apci1500_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ data[1] = inw(devpriv->i_IobaseAddon + APCI1500_DIGITAL_IP);
+
+ return insn->n;
+}
+
+/*
+ * Configures the digital output memory and the digital output error interrupt
+ *
+ * data[1] 1 = Enable the voltage error interrupt
+ * 2 = Disable the voltage error interrupt
+ */
+static int apci1500_do_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ devpriv->b_OutputMemoryStatus = data[0];
+ return insn->n;
+}
+
+/*
+ * Writes port value to the selected port
+ */
+static int apci1500_do_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ static unsigned int ui_Temp;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "Specified channel not supported\n");
+ return -EINVAL;
+ } /* 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 {
+ dev_warn(dev->hw_dev,
+ "Specified channel not supported\n");
+ return -EINVAL;
+ } /* else if(data[1]==1) */
+ } /* elseif(data[1]==0) */
+ } /* if(data[3]==1); */
+ else {
+ dev_warn(dev->hw_dev,
+ "Specified functionality does not exist\n");
+ return -EINVAL;
+ } /* if else data[3]==1) */
+ } /* if else data[3]==0) */
+ ui_Temp = data[0];
+ return insn->n;
+}
+
+/*
+ * Configures The Watchdog
+ *
+ * data[0] 0 = APCI1500_115_KHZ, 1 = APCI1500_3_6_KHZ, 2 = APCI1500_1_8_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
+ */
+static int apci1500_timer_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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) {
+ dev_warn(dev->hw_dev,
+ "The 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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:
+ dev_warn(dev->hw_dev,
+ "This 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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 {
+ dev_warn(dev->hw_dev,
+ "Error 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:
+ dev_warn(dev->hw_dev,
+ "The specified counter/timer option does not exist\n");
+ return -EINVAL;
+ } /* switch(data[1]) */
+ i_CounterLogic = data[2];
+ return insn->n;
+}
+
+/*
+ * Start / Stop or trigger the timer counter or Watchdog
+ *
+ * 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
+ */
+static int apci1500_timer_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "Counter/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 {
+ dev_warn(dev->hw_dev,
+ "Counter/Timer1 not configured\n");
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ dev_warn(dev->hw_dev,
+ "The 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 {
+ dev_warn(dev->hw_dev,
+ "Counter/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 {
+ dev_warn(dev->hw_dev,
+ "Counter/Timer2 not configured\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "The 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 {
+ dev_warn(dev->hw_dev,
+ "Watchdog/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 {
+ dev_warn(dev->hw_dev,
+ "Counter3 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 {
+ dev_warn(dev->hw_dev,
+ "Watchdog 3 not configured\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "Wrong choice of watchdog/counter3\n");
+ return -EINVAL;
+ } /* switch(data[2]) */
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "The specified option for start/stop/trigger does not exist\n");
+ return -EINVAL;
+ } /* switch(data[1]) */
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "The specified choice for counter/watchdog/timer does not exist\n");
+ return -EINVAL;
+ } /* switch(data[0]) */
+ return insn->n;
+}
+
+/*
+ * Read The Watchdog
+ *
+ * data[0] 0 = Counter1/Timer1, 1 = Counter2/Timer2, 2 = Counter3/Watchdog
+ */
+static int apci1500_timer_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "Timer/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 {
+ dev_warn(dev->hw_dev,
+ "Timer/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 {
+ dev_warn(dev->hw_dev,
+ "WatchdogCounter3 not configured\n");
+ return -EINVAL;
+ } /* elseif( i_WatchdogCounter3Init==1) */
+ break;
+ default:
+ dev_warn(dev->hw_dev,
+ "The choice of timer/counter/watchdog does not exist\n");
+ return -EINVAL;
+ } /* switch(data[0]) */
+
+ return insn->n;
+}
+
+/*
+ * Read the interrupt mask
+ *
+ * data[0] The interrupt mask value
+ * data[1] Channel Number
+ */
+static int apci1500_timer_read(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;
+}
+
+/*
+ * Configures the interrupt registers
+ */
+static int apci1500_do_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 {
+ dev_warn(dev->hw_dev,
+ "The 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;
+}
+
+static void apci1500_interrupt(int irq, void *d)
+{
+
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ 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);
+ /* 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 {
+ dev_warn(dev->hw_dev,
+ "Interrupt from unknown source\n");
+
+ } /* else if ((ui_InterruptStatus & 0x800000) == 0x800000) */
+ return;
+}
+
+static int apci1500_reset(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ 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_apci1564.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c
new file mode 100644
index 00000000000..0ba5385226a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c
@@ -0,0 +1,551 @@
+/*
+ * 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.
+ *
+ */
+
+#include "../addi_watchdog.h"
+
+#define APCI1564_ADDRESS_RANGE 128
+
+/* Digital Input IRQ Function Selection */
+#define ADDIDATA_OR 0
+#define ADDIDATA_AND 1
+
+/* 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
+
+/* TIMER COUNTER WATCHDOG DEFINES */
+#define ADDIDATA_TIMER 0
+#define ADDIDATA_COUNTER 1
+#define ADDIDATA_WATCHDOG 2
+#define APCI1564_COUNTER1 0
+#define APCI1564_COUNTER2 1
+#define APCI1564_COUNTER3 2
+#define APCI1564_COUNTER4 3
+
+/*
+ * devpriv->i_IobaseAmcc Register Map
+ */
+#define APCI1564_DI_REG 0x04
+#define APCI1564_DI_INT_MODE1_REG 0x08
+#define APCI1564_DI_INT_MODE2_REG 0x0c
+#define APCI1564_DI_INT_STATUS_REG 0x10
+#define APCI1564_DI_IRQ_REG 0x14
+#define APCI1564_DO_REG 0x18
+#define APCI1564_DO_INT_CTRL_REG 0x1c
+#define APCI1564_DO_INT_STATUS_REG 0x20
+#define APCI1564_DO_IRQ_REG 0x24
+#define APCI1564_WDOG_REG 0x28
+#define APCI1564_WDOG_RELOAD_REG 0x2c
+#define APCI1564_WDOG_TIMEBASE_REG 0x30
+#define APCI1564_WDOG_CTRL_REG 0x34
+#define APCI1564_WDOG_STATUS_REG 0x38
+#define APCI1564_WDOG_IRQ_REG 0x3c
+#define APCI1564_WDOG_WARN_TIMEVAL_REG 0x40
+#define APCI1564_WDOG_WARN_TIMEBASE_REG 0x44
+#define APCI1564_TIMER_REG 0x48
+#define APCI1564_TIMER_RELOAD_REG 0x4c
+#define APCI1564_TIMER_TIMEBASE_REG 0x50
+#define APCI1564_TIMER_CTRL_REG 0x54
+#define APCI1564_TIMER_STATUS_REG 0x58
+#define APCI1564_TIMER_IRQ_REG 0x5c
+#define APCI1564_TIMER_WARN_TIMEVAL_REG 0x60
+#define APCI1564_TIMER_WARN_TIMEBASE_REG 0x64
+
+/*
+ * dev>iobase Register Map
+ */
+#define APCI1564_TCW_REG(x) (0x00 + ((x) * 0x20))
+#define APCI1564_TCW_RELOAD_REG(x) (0x04 + ((x) * 0x20))
+#define APCI1564_TCW_TIMEBASE_REG(x) (0x08 + ((x) * 0x20))
+#define APCI1564_TCW_CTRL_REG(x) (0x0c + ((x) * 0x20))
+#define APCI1564_TCW_STATUS_REG(x) (0x10 + ((x) * 0x20))
+#define APCI1564_TCW_IRQ_REG(x) (0x14 + ((x) * 0x20))
+#define APCI1564_TCW_WARN_TIMEVAL_REG(x) (0x18 + ((x) * 0x20))
+#define APCI1564_TCW_WARN_TIMEBASE_REG(x) (0x1c + ((x) * 0x20))
+
+/* Global variables */
+static unsigned int ui_InterruptStatus_1564;
+static unsigned int ui_InterruptData, ui_Type;
+
+/*
+ * Configures the digital input Subdevice
+ *
+ * data[0] 1 = Enable interrupt, 0 = Disable 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
+ */
+static int apci1564_di_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ 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_DI_INT_MODE1_REG);
+ outl(data[3], devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG);
+ if (data[1] == ADDIDATA_OR)
+ outl(0x4, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ else
+ outl(0x6, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ } else {
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE1_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ }
+
+ return insn->n;
+}
+
+/*
+ * Configures The Digital Output Subdevice.
+ *
+ * data[1] 0 = Disable VCC Interrupt, 1 = Enable VCC Interrupt
+ * data[2] 0 = Disable CC Interrupt, 1 = Enable CC Interrupt
+ */
+static int apci1564_do_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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])
+ devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE;
+ else
+ devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE;
+
+ if (data[1] == ADDIDATA_ENABLE)
+ ul_Command = ul_Command | 0x1;
+ else
+ ul_Command = ul_Command & 0xFFFFFFFE;
+
+ if (data[2] == ADDIDATA_ENABLE)
+ ul_Command = ul_Command | 0x2;
+ else
+ ul_Command = ul_Command & 0xFFFFFFFD;
+
+ outl(ul_Command, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG);
+ ui_InterruptData = inl(devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG);
+ devpriv->tsk_Current = current;
+ return insn->n;
+}
+
+/*
+ * Configures The Timer, Counter or Watchdog
+ *
+ * data[0] Configure as: 0 = Timer, 1 = Counter, 2 = 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
+ */
+static int apci1564_timer_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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_WDOG_CTRL_REG);
+ /* Loading the Reload value */
+ outl(data[3], devpriv->i_IobaseAmcc + APCI1564_WDOG_RELOAD_REG);
+ } else if (data[0] == ADDIDATA_TIMER) {
+ /* First Stop The Timer */
+ ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ /* Stop The Timer */
+ outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+
+ devpriv->b_TimerSelectMode = ADDIDATA_TIMER;
+ if (data[1] == 1) {
+ /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */
+ outl(0x02, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_IRQ_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_WDOG_IRQ_REG);
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER1));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER2));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER3));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER4));
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ }
+
+ /* Loading Timebase */
+ outl(data[2], devpriv->i_IobaseAmcc + APCI1564_TIMER_TIMEBASE_REG);
+
+ /* Loading the Reload value */
+ outl(data[3], devpriv->i_IobaseAmcc + APCI1564_TIMER_RELOAD_REG);
+
+ ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL;
+ /* mode 2 */
+ outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ } else if (data[0] == ADDIDATA_COUNTER) {
+ devpriv->b_TimerSelectMode = ADDIDATA_COUNTER;
+ devpriv->b_ModeSelectRegister = data[5];
+
+ /* First Stop The Counter */
+ ul_Command1 = inl(dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1));
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ /* Stop The Timer */
+ outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1));
+
+ /* Set the reload value */
+ outl(data[3], dev->iobase + APCI1564_TCW_RELOAD_REG(data[5] - 1));
+
+ /* 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, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1));
+
+ /* Enable or Disable Interrupt */
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FD) | (data[1] << 1);
+ outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1));
+
+ /* Set the Up/Down selection */
+ ul_Command1 = (ul_Command1 & 0xFFFBF9FFUL) | (data[6] << 18);
+ outl(ul_Command1, dev->iobase + APCI1564_TCW_CTRL_REG(data[5] - 1));
+ } else {
+ dev_err(dev->class_dev, "Invalid subdevice.\n");
+ }
+
+ return insn->n;
+}
+
+/*
+ * Start / Stop The Selected Timer, Counter or Watchdog
+ *
+ * data[0] Configure as: 0 = Timer, 1 = Counter, 2 = Watchdog
+ * data[1] 0 = Stop, 1 = Start, 2 = Trigger Clear (Only Counter)
+ */
+static int apci1564_timer_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+ switch (data[1]) {
+ case 0: /* stop the watchdog */
+ /* disable the watchdog */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG);
+ break;
+ case 1: /* start the watchdog */
+ outl(0x0001, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG);
+ break;
+ case 2: /* Software trigger */
+ outl(0x0201, devpriv->i_IobaseAmcc + APCI1564_WDOG_CTRL_REG);
+ break;
+ default:
+ dev_err(dev->class_dev, "Specified functionality does not exist.\n");
+ return -EINVAL;
+ }
+ }
+ if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ if (data[1] == 1) {
+ ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+
+ /* Enable the Timer */
+ outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ } else if (data[1] == 0) {
+ /* Stop The Timer */
+
+ ul_Command1 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ }
+ }
+ if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) {
+ ul_Command1 =
+ inl(dev->iobase +
+ APCI1564_TCW_CTRL_REG(devpriv->b_ModeSelectRegister - 1));
+ if (data[1] == 1) {
+ /* Start the Counter subdevice */
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+ } else if (data[1] == 0) {
+ /* Stops the Counter subdevice */
+ ul_Command1 = 0;
+
+ } else if (data[1] == 2) {
+ /* Clears the Counter subdevice */
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x400;
+ }
+ outl(ul_Command1, dev->iobase +
+ APCI1564_TCW_CTRL_REG(devpriv->b_ModeSelectRegister - 1));
+ }
+ return insn->n;
+}
+
+/*
+ * Read The Selected Timer, Counter or Watchdog
+ */
+static int apci1564_timer_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+ /* Stores the status of the Watchdog */
+ data[0] = inl(devpriv->i_IobaseAmcc + APCI1564_WDOG_STATUS_REG) & 0x1;
+ data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_WDOG_REG);
+ } else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ /* Stores the status of the Timer */
+ data[0] = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_STATUS_REG) & 0x1;
+
+ /* Stores the Actual value of the Timer */
+ data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_REG);
+ } else if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) {
+ /* Read the Counter Actual Value. */
+ data[0] =
+ inl(dev->iobase +
+ APCI1564_TCW_REG(devpriv->b_ModeSelectRegister - 1));
+ ul_Command1 =
+ inl(dev->iobase +
+ APCI1564_TCW_STATUS_REG(devpriv->b_ModeSelectRegister - 1));
+
+ /* 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_TIMER)
+ && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG)
+ && (devpriv->b_TimerSelectMode != ADDIDATA_COUNTER)) {
+ dev_err(dev->class_dev, "Invalid Subdevice!\n");
+ }
+ return insn->n;
+}
+
+/*
+ * Reads the interrupt status register
+ */
+static int apci1564_do_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ *data = ui_Type;
+ return insn->n;
+}
+
+/*
+ * Interrupt handler for the interruptible digital inputs
+ */
+static void apci1564_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ 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_DI_IRQ_REG) & 0x01;
+ ui_DO = inl(devpriv->i_IobaseAmcc + APCI1564_DO_IRQ_REG) & 0x01;
+ ui_Timer = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_IRQ_REG) & 0x01;
+ ui_C1 =
+ inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER1)) & 0x1;
+ ui_C2 =
+ inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER2)) & 0x1;
+ ui_C3 =
+ inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER3)) & 0x1;
+ ui_C4 =
+ inl(dev->iobase + APCI1564_TCW_IRQ_REG(APCI1564_COUNTER4)) & 0x1;
+ if (ui_DI == 0 && ui_DO == 0 && ui_Timer == 0 && ui_C1 == 0
+ && ui_C2 == 0 && ui_C3 == 0 && ui_C4 == 0) {
+ dev_err(dev->class_dev, "Interrupt from unknown source.\n");
+ }
+
+ if (ui_DI == 1) {
+ ui_DI = inl(devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ ui_InterruptStatus_1564 =
+ inl(devpriv->i_IobaseAmcc + APCI1564_DI_INT_STATUS_REG);
+ ui_InterruptStatus_1564 = ui_InterruptStatus_1564 & 0X000FFFF0;
+ /* send signal to the sample */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+ /* enable the interrupt */
+ outl(ui_DI, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ return;
+ }
+
+ if (ui_DO == 1) {
+ /* Check for Digital Output interrupt Type */
+ /* 1: VCC interrupt */
+ /* 2: CC interrupt */
+ ui_Type = inl(devpriv->i_IobaseAmcc + APCI1564_DO_INT_STATUS_REG) & 0x3;
+ /* Disable the Interrupt */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG);
+
+ /* Sends signal to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+ }
+
+ if (ui_Timer == 1) {
+ devpriv->b_TimerSelectMode = ADDIDATA_TIMER;
+ if (devpriv->b_TimerSelectMode) {
+
+ /* Disable Timer Interrupt */
+ ul_Command2 = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+
+ /* 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_CTRL_REG);
+ }
+ }
+
+ if (ui_C1 == 1) {
+ devpriv->b_TimerSelectMode = ADDIDATA_COUNTER;
+ if (devpriv->b_TimerSelectMode) {
+
+ /* Disable Counter Interrupt */
+ ul_Command2 =
+ inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+
+ /* Enable Counter Interrupt */
+ outl(ul_Command2,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
+ }
+ }
+
+ if (ui_C2 == 1) {
+ devpriv->b_TimerSelectMode = ADDIDATA_COUNTER;
+ if (devpriv->b_TimerSelectMode) {
+
+ /* Disable Counter Interrupt */
+ ul_Command2 =
+ inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+
+ /* Enable Counter Interrupt */
+ outl(ul_Command2,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
+ }
+ }
+
+ if (ui_C3 == 1) {
+ devpriv->b_TimerSelectMode = ADDIDATA_COUNTER;
+ if (devpriv->b_TimerSelectMode) {
+
+ /* Disable Counter Interrupt */
+ ul_Command2 =
+ inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+
+ /* Enable Counter Interrupt */
+ outl(ul_Command2,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
+ }
+ }
+
+ if (ui_C4 == 1) {
+ devpriv->b_TimerSelectMode = ADDIDATA_COUNTER;
+ if (devpriv->b_TimerSelectMode) {
+
+ /* Disable Counter Interrupt */
+ ul_Command2 =
+ inl(dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
+ outl(0x0,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
+
+ /* Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+
+ /* Enable Counter Interrupt */
+ outl(ul_Command2,
+ dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
+ }
+ }
+ return;
+}
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 00000000000..764c8f17f8f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c
@@ -0,0 +1,2190 @@
+/**
+@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.
+
+@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 <linux/delay.h>
+
+/*
+ * 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
+
+/* for transfer count enable bit */
+#define AGCSTS_TC_ENABLE 0x10000000
+
+/* used for test on mixture of BIP/UNI ranges */
+#define APCI3120_BIPOLAR_RANGES 4
+
+#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
+#define APCI3120_BIPOLAR 0x00
+#define APCI3120_ANALOG_OUTPUT_1 0x08
+#define APCI3120_ANALOG_OUTPUT_2 0x0a
+#define APCI3120_1_GAIN 0x00
+#define APCI3120_2_GAIN 0x10
+#define APCI3120_5_GAIN 0x20
+#define APCI3120_10_GAIN 0x30
+#define APCI3120_SEQ_RAM_ADDRESS 0x06
+#define APCI3120_RESET_FIFO 0x0c
+#define APCI3120_TIMER_0_MODE_2 0x01
+#define APCI3120_TIMER_0_MODE_4 0x2
+#define APCI3120_SELECT_TIMER_0_WORD 0x00
+#define APCI3120_ENABLE_TIMER0 0x1000
+#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
+#define APCI3120_DISABLE_SCAN (~APCI3120_ENABLE_SCAN)
+#define APCI3120_ENABLE_EOS_INT 0x2
+
+#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
+
+/* 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
+#define APCI3120_DISABLE_TIMER2 (~APCI3120_ENABLE_TIMER2)
+#define APCI3120_ENABLE_TIMER_INT 0x04
+#define APCI3120_DISABLE_TIMER_INT (~APCI3120_ENABLE_TIMER_INT)
+#define APCI3120_WRITE_MODE_SELECT 0x0e
+#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
+#define APCI3120_TIMER_CRT1 0x0c
+
+#define APCI3120_TIMER_VALUE 0x04
+#define APCI3120_TIMER_STATUS_REGISTER 0x0d
+#define APCI3120_RD_STATUS 0x02
+#define APCI3120_WR_ADDRESS 0x00
+#define APCI3120_ENABLE_WATCHDOG 0x20
+#define APCI3120_DISABLE_WATCHDOG (~APCI3120_ENABLE_WATCHDOG)
+#define APCI3120_ENABLE_TIMER_COUNTER 0x10
+#define APCI3120_DISABLE_TIMER_COUNTER (~APCI3120_ENABLE_TIMER_COUNTER)
+#define APCI3120_FC_TIMER 0x1000
+#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
+#define APCI3120_COUNTER 3
+#define APCI3120_DISABLE_ALL_TIMER (APCI3120_DISABLE_TIMER0 & \
+ APCI3120_DISABLE_TIMER1 & \
+ APCI3120_DISABLE_TIMER2)
+
+#define MAX_ANALOGINPUT_CHANNELS 32
+
+struct str_AnalogReadInformation {
+ /* EOC or EOS */
+ unsigned char b_Type;
+ /* Interrupt use or not */
+ unsigned char b_InterruptFlag;
+ /* Selection of the conversion time */
+ unsigned int ui_ConvertTiming;
+ /* Number of channel to read */
+ unsigned char b_NbrOfChannel;
+ /* Number of the channel to be read */
+ unsigned int ui_ChannelList[MAX_ANALOGINPUT_CHANNELS];
+ /* Gain of each channel */
+ unsigned int ui_RangeList[MAX_ANALOGINPUT_CHANNELS];
+};
+
+/* 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)
+ }
+};
+
+
+/* FUNCTION DEFINITIONS */
+
+/*
++----------------------------------------------------------------------------+
+| ANALOG INPUT SUBDEVICE |
++----------------------------------------------------------------------------+
+*/
+
+static int apci3120_ai_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ 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]) >=
+ this_board->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;
+}
+
+/*
+ * 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.
+ */
+static int apci3120_setup_chan_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int n_chan,
+ unsigned int *chanlist,
+ char check)
+{
+ struct addi_private *devpriv = dev->private;
+ 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 */
+}
+
+/*
+ * 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.
+ */
+static int apci3120_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ 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->ai_read(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 (!apci3120_setup_chan_list(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 (!apci3120_setup_chan_list(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;
+
+}
+
+static int apci3120_reset(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int i;
+ unsigned short us_TmpValue;
+
+ devpriv->ai_running = 0;
+ devpriv->b_EocEosInterrupt = APCI3120_DISABLE;
+ devpriv->b_InterruptMode = APCI3120_EOC_MODE;
+ devpriv->ui_EocEosConversionTime = 0; /* set eoc eos conv time to 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;
+}
+
+static int apci3120_exttrig_enable(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ devpriv->us_OutputRegister |= APCI3120_ENABLE_EXT_TRIGGER;
+ outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS);
+ return 0;
+}
+
+static int apci3120_exttrig_disable(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ devpriv->us_OutputRegister &= ~APCI3120_ENABLE_EXT_TRIGGER;
+ outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS);
+ return 0;
+}
+
+static int apci3120_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct addi_private *devpriv = dev->private;
+
+ /* 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 */
+ apci3120_exttrig_disable(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;
+ s->async->cur_chan = 0;
+ devpriv->ui_DmaActualBuffer = 0;
+
+ devpriv->ai_running = 0;
+ devpriv->b_InterruptMode = APCI3120_EOC_MODE;
+ devpriv->b_EocEosInterrupt = APCI3120_DISABLE;
+ apci3120_reset(dev);
+ return 0;
+}
+
+static int apci3120_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) /* Test Delay timing */
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 100000);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ if (cmd->convert_arg)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ 10000);
+ } else {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000);
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ 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 |= -EINVAL;
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+/*
+ * 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.
+ */
+static int apci3120_cyclic_ai(int mode,
+ struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ 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;
+
+ /*******************/
+ /* 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 */
+
+ devpriv->ai_running = 1;
+
+ /* 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;
+ s->async->cur_chan = 0;
+ devpriv->ui_DmaActualBuffer = 0;
+
+ /* value for timer2 minus -2 has to be done .....dunno y?? */
+ ui_TimerValue2 = cmd->stop_arg - 2;
+ ui_ConvertTiming = cmd->convert_arg;
+
+ if (mode == 2)
+ ui_DelayTiming = cmd->scan_begin_arg;
+
+ /**********************************/
+ /* Initializes the sequence array */
+ /**********************************/
+ if (!apci3120_setup_chan_list(dev, s, devpriv->ui_AiNbrofChannels,
+ cmd->chanlist, 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)
+ apci3120_exttrig_enable(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 (cmd->stop_src == TRIG_COUNT) {
+/*
+ * 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 */
+ unsigned int scan_bytes = cmd->scan_end_arg * sizeof(short);
+
+ /* 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 (cmd->stop_src == TRIG_COUNT) {
+ /*
+ * Must we fill full first buffer? And must we fill
+ * full second buffer when first is once filled?
+ */
+ if (dmalen0 > (cmd->stop_arg * scan_bytes)) {
+ dmalen0 = cmd->stop_arg * scan_bytes;
+ } else if (dmalen1 > (cmd->stop_arg * scan_bytes -
+ dmalen0))
+ dmalen1 = cmd->stop_arg * scan_bytes -
+ dmalen0;
+ }
+
+ if (cmd->flags & TRIG_WAKE_EOS) {
+ /* don't we want wake up every scan? */
+ if (dmalen0 > scan_bytes) {
+ dmalen0 = scan_bytes;
+ if (cmd->scan_end_arg & 1)
+ dmalen0 += 2;
+ }
+ if (dmalen1 > scan_bytes) {
+ dmalen1 = scan_bytes;
+ if (cmd->scan_end_arg & 1)
+ dmalen1 -= 2;
+ if (dmalen1 < 4)
+ dmalen1 = 4;
+ }
+ } else { /* isn't output buff smaller that our DMA buff? */
+ if (dmalen0 > s->async->prealloc_bufsz)
+ dmalen0 = s->async->prealloc_bufsz;
+ if (dmalen1 > s->async->prealloc_bufsz)
+ dmalen1 = s->async->prealloc_bufsz;
+ }
+ 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 &&
+ cmd->stop_src == TRIG_COUNT) {
+ /* 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;
+
+}
+
+/*
+ * Does asynchronous acquisition.
+ * Determines the mode 1 or 2.
+ */
+static int apci3120_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct addi_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ /* loading private structure with cmd structure inputs */
+ devpriv->ui_AiNbrofChannels = cmd->chanlist_len;
+
+ if (cmd->start_src == TRIG_EXT)
+ devpriv->b_ExttrigEnable = APCI3120_ENABLE;
+ else
+ devpriv->b_ExttrigEnable = APCI3120_DISABLE;
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW)
+ return apci3120_cyclic_ai(1, dev, s);
+ else /* TRIG_TIMER */
+ return apci3120_cyclic_ai(2, dev, s);
+}
+
+/*
+ * This function copies the data from DMA buffer to the Comedi buffer.
+ */
+static void v_APCI3120_InterruptDmaMoveBlock16bit(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *dma_buffer,
+ unsigned int num_samples)
+{
+ struct addi_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ devpriv->ui_AiActualScan +=
+ (s->async->cur_chan + num_samples) / cmd->scan_end_arg;
+ s->async->cur_chan += num_samples;
+ s->async->cur_chan %= cmd->scan_end_arg;
+
+ cfc_write_array_to_buffer(s, dma_buffer, num_samples * sizeof(short));
+}
+
+/*
+ * 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.
+ */
+static void apci3120_interrupt_dma(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ 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!");
+ apci3120_cancel(dev, s);
+ 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 (!(cmd->flags & TRIG_WAKE_EOS)) {
+ s->async->events |= COMEDI_CB_EOS;
+ comedi_event(dev, s);
+ }
+ }
+ if (cmd->stop_src == TRIG_COUNT)
+ if (devpriv->ui_AiActualScan >= cmd->stop_arg) {
+ /* all data sampled */
+ apci3120_cancel(dev, s);
+ 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);
+ }
+}
+
+/*
+ * This function handles EOS interrupt.
+ * This function copies the acquired data(from FIFO) to Comedi buffer.
+ */
+static int apci3120_interrupt_handle_eos(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int n_chan, i;
+ int err = 1;
+
+ n_chan = devpriv->ui_AiNbrofChannels;
+
+ for (i = 0; i < n_chan; i++)
+ err &= comedi_buf_put(s, 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;
+}
+
+static void apci3120_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned short int_daq;
+ unsigned int int_amcc, ui_Check, i;
+ unsigned short us_TmpValue;
+ unsigned char b_DummyRead;
+
+ 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 */
+ apci3120_exttrig_disable(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->ai_running) {
+ ui_Check = 0;
+ apci3120_interrupt_handle_eos(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_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 */
+ apci3120_cancel(dev, s);
+
+ /* 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->ai_running) {
+
+ /****************************/
+ /* 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);
+ /* do some data transfer */
+ apci3120_interrupt_dma(irq, d);
+ } else {
+ /* Stops the Timer */
+ outw(devpriv->
+ us_OutputRegister & APCI3120_DISABLE_TIMER0 &
+ APCI3120_DISABLE_TIMER1,
+ dev->iobase + APCI3120_WR_ADDRESS);
+ }
+
+ }
+
+ return;
+}
+
+/*
+ * Configure Timer 2
+ *
+ * data[0] = TIMER configure as timer
+ * = WATCHDOG configure as watchdog
+ * data[1] = Timer constant
+ * data[2] = Timer2 interrupt (1)enable or(0) disable
+ */
+static int apci3120_config_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ 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->timer_config(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;
+
+}
+
+/*
+ * To start and stop the timer
+ *
+ * data[0] = 1 (start)
+ * = 0 (stop)
+ * = 2 (write new value)
+ * data[1] = new value
+ *
+ * devpriv->b_Timer2Mode = 0 DISABLE
+ * = 1 Timer
+ * = 2 Watch dog
+ */
+static int apci3120_write_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct addi_board *this_board = comedi_board(dev);
+ struct addi_private *devpriv = dev->private;
+ 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->timer_write(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;
+}
+
+/*
+ * Read the Timer value
+ *
+ * for Timer: data[0]= Timer constant
+ *
+ * for watchdog: data[0] = 0 (still running)
+ * = 1 (run down)
+ */
+static int apci3120_read_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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->timer_read(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;
+}
+
+static int apci3120_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int val;
+
+ /* the input channels are bits 11:8 of the status reg */
+ val = inw(devpriv->iobase + APCI3120_RD_STATUS);
+ data[1] = (val >> 8) & 0xf;
+
+ return insn->n;
+}
+
+static int apci3120_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data)) {
+ /* The do channels are bits 7:4 of the do register */
+ devpriv->b_DigitalOutputRegister = s->state << 4;
+
+ outb(devpriv->b_DigitalOutputRegister,
+ devpriv->iobase + APCI3120_DIGITAL_OUTPUT);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci3120_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_Range, ui_Channel;
+ unsigned short us_TmpValue;
+
+ ui_Range = CR_RANGE(insn->chanspec);
+ ui_Channel = CR_CHAN(insn->chanspec);
+
+ /* this_board->ao_write(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_apci3200.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c
new file mode 100644
index 00000000000..f540394d17b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c
@@ -0,0 +1,3165 @@
+/**
+@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.
+
+@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 |
+ +----------+-----------+------------------------------------------------+
+*/
+
+/* #define PRINT_INFO */
+
+/* Card Specific information */
+/* #define APCI3200_ADDRESS_RANGE 264 */
+
+/* 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
+
+#define MAX_MODULE 4
+
+/* 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)
+ }
+};
+
+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;
+
+
+struct str_ADDIDATA_RTDStruct {
+ unsigned int ul_NumberOfValue;
+ unsigned int *pul_ResistanceValue;
+ unsigned int *pul_TemperatureValue;
+};
+
+struct str_Module {
+ unsigned long ul_CurrentSourceCJC;
+ unsigned long ul_CurrentSource[5];
+ unsigned long ul_GainFactor[8]; /* Gain Factor */
+ unsigned int w_GainValue[10];
+};
+
+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;
+ unsigned int ui_InterruptChannelValue[144]; /* Buffer */
+ unsigned char b_StructInitialized;
+ /* 7 is the maximal number of channels */
+ unsigned int ui_ScanValueArray[7 + 12];
+
+ int i_ConnectionType;
+ int i_NbrOfModule;
+ struct str_Module s_Module[MAX_MODULE];
+};
+
+/* 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 */
+
+#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 */
+
+static 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;
+}
+
+static 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
+ }
+}
+
+static 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;
+}
+
+static int apci3200_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ data[1] = inl(devpriv->i_IobaseReserved) & 0xf;
+
+ return insn->n;
+}
+
+static int apci3200_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ s->state = inl(devpriv->i_IobaseAddon) & 0xf;
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, devpriv->i_IobaseAddon);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int i_APCI3200_Read1AnalogInputChannel(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int i_APCI3200_ReadCalibrationOffsetValue(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int i_APCI3200_ReadCalibrationGainValue(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int i_APCI3200_ReadCJCValue(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int i_APCI3200_ReadCJCCalOffset(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int i_APCI3200_ReadCJCCalGain(struct comedi_device *dev,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+static int apci3200_reset(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+/*
+ * Read value of the selected channel
+ *
+ * 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
+ * data[6] : CJC current source from eeprom
+ * data[7] : Channel current source from eeprom
+ * data[8] : Channle gain factor from eeprom
+ */
+static int apci3200_ai_read(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 */
+ {
+ 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");
+ apci3200_reset(dev);
+ return -EINVAL;
+ } /* switch(insn->unused[0]) */
+
+ return insn->n;
+}
+
+/*
+ * Configures The Analog Input Subdevice
+ *
+ * data[0] = 0 Normal AI
+ * = 1 RTD
+ * = 2 THERMOCOUPLE
+ * data[1] = Gain To Use
+ * data[2] = 0 Bipolar
+ * = 1 Unipolar
+ * data[3] = Offset Range
+ * data[4] = 0 DC Coupling
+ * = 1 AC Coupling
+ * data[5] = 0 Single
+ * = 1 Differential
+ * data[6] = TimerReloadValue
+ * data[7] = ConvertingTimeUnit
+ * data[8] = 0 Analog voltage measurement
+ * = 1 Resistance measurement
+ * = 2 Temperature measurement
+ * data[9] = 0 Interrupt Disable
+ * = 1 INterrupt Enable
+ * data[10] = Type of Thermocouple
+ * data[11] = 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] = 0 RTD not used
+ * = 1 RTD 2 wire connection
+ * = 2 RTD 3 wire connection
+ * = 3 RTD 4 wire connection
+ */
+static int apci3200_ai_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ 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) {
+ 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;
+ apci3200_ai_read(dev, s, insn, &ui_Dummy);
+ }
+
+ return insn->n;
+}
+
+/*
+ * Tests the Selected Anlog Input Channel
+ *
+ * data[0] = 0 TestAnalogInputShortCircuit
+ * = 1 TestAnalogInputConnection
+ *
+ * data[0] : Digital value obtained
+ * data[1] : calibration offset
+ * data[2] : calibration gain
+ */
+static int apci3200_ai_bits_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+ unsigned int ui_Configuration = 0;
+ int i_Temp; /* ,i_TimeUnit; */
+
+ /* if(i_Initialised==0) */
+
+ if (s_BoardInfos[dev->minor].i_Initialised == 0) {
+ apci3200_reset(dev);
+ return -EINVAL;
+ } /* if(i_Initialised==0); */
+ if (data[0] != 0 && data[0] != 1) {
+ printk("\nError in selection of functionality\n");
+ 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;
+}
+
+static int apci3200_ai_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ apci3200_reset(dev);
+ return insn->n;
+}
+
+static int apci3200_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+
+ int err = 0;
+ unsigned int ui_ConvertTime = 0;
+ unsigned int ui_ConvertTimeBase = 0;
+ unsigned int ui_DelayTime = 0;
+ unsigned int ui_DelayTimeBase = 0;
+ int i_NbrOfChannel = 0;
+ int i_Cpt = 0;
+ double d_ConversionTimeForAllChannels = 0.0;
+ double d_SCANTimeNewUnit = 0.0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (s_BoardInfos[dev->minor].i_InterruptFlag == 0)
+ err |= -EINVAL;
+
+ if (err) {
+ apci3200_reset(dev);
+ return 1;
+ }
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(&cmd->start_src);
+ err |= cfc_check_trigger_is_unique(&cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(&cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err) {
+ apci3200_reset(dev);
+ return 2;
+ }
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* validate the trigger edge selection */
+ arg = cmd->start_arg & 0xffff;
+ if (arg < 1 || arg > 3) {
+ cmd->start_arg &= ~0xffff;
+ cmd->start_arg |= 1;
+ err |= -EINVAL;
+ }
+ /* validate the trigger mode selection */
+ arg = cmd->start_arg >> 16;
+ if (arg != 2) {
+ cmd->start_arg &= ~(0xffff << 16);
+ cmd->start_arg |= (2 << 16);
+ err |= -EINVAL;
+ }
+ break;
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ /* 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) {
+ 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) {
+ apci3200_reset(dev);
+ return 4;
+ }
+
+ return 0;
+}
+
+static int apci3200_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+/*
+ * Does asynchronous acquisition
+ * Determines the mode 1 or 2.
+ */
+static int apci3200_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
+
+/*
+ * This function copies the acquired data(from FIFO) to Comedi buffer.
+ */
+static int i_APCI3200_InterruptHandleEos(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int ui_StatusRegister = 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); */
+ /* 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,
+ (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, 0,
+ (unsigned int *) s_BoardInfos[dev->minor].
+ ui_ScanValueArray, (7 + 12) * sizeof(unsigned int));
+
+ /* Update comedi buffer pinters indexes */
+ comedi_buf_write_free(s,
+ (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;
+}
+
+static void apci3200_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct addi_private *devpriv = dev->private;
+ 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;
+}
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 00000000000..e82c3fcd048
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c
@@ -0,0 +1,174 @@
+/* Watchdog Related Defines */
+
+#define ADDIDATA_TIMER 0
+#define ADDIDATA_WATCHDOG 2
+
+/*
+ * (*insn_config) for the timer subdevice
+ *
+ * Configures The Timer, Counter or Watchdog
+ * 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
+ */
+static int apci3501_config_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+
+ devpriv->tsk_Current = current;
+ if (data[0] == ADDIDATA_WATCHDOG) {
+
+ devpriv->b_TimerSelectMode = ADDIDATA_WATCHDOG;
+ /* Disable the watchdog */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+
+ if (data[1] == 1) {
+ /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */
+ outl(0x02, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ outl(data[2], dev->iobase + APCI3501_TIMER_TIMEBASE_REG);
+ outl(data[3], dev->iobase + APCI3501_TIMER_RELOAD_REG);
+
+ /* Set the mode (e2->e0) */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG) | 0xFFF819E0UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ else if (data[0] == ADDIDATA_TIMER) {
+ /* First Stop The Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ devpriv->b_TimerSelectMode = ADDIDATA_TIMER;
+ if (data[1] == 1) {
+ /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */
+ outl(0x02, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else {
+ /* disable Timer interrupt */
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ outl(data[2], dev->iobase + APCI3501_TIMER_TIMEBASE_REG);
+ outl(data[3], dev->iobase + APCI3501_TIMER_RELOAD_REG);
+
+ /* mode 2 */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 =
+ (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ return insn->n;
+}
+
+/*
+ * (*insn_write) for the timer subdevice
+ *
+ * Start / Stop The Selected Timer , Counter or Watchdog
+ * Data Pointer contains configuration parameters as below
+ * data[0] : 0 Timer
+ * 1 Counter
+ * 2 Watchdog
+ * data[1] : 1 Start
+ * 0 Stop
+ * 2 Trigger
+ */
+static int apci3501_write_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ul_Command1 = 0;
+ int i_Temp;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+
+ if (data[1] == 1) {
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+ /* Enable the Watchdog */
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 0) { /* Stop The Watchdog */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(0x0, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 2) {
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+ }
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ if (data[1] == 1) {
+
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL;
+ /* Enable the Timer */
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ } else if (data[1] == 0) {
+ /* Stop The Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ul_Command1 & 0xFFFFF9FEUL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+
+ else if (data[1] == 2) {
+ /* Trigger the Timer */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL;
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ }
+ }
+
+ i_Temp = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+ return insn->n;
+}
+
+/*
+ * (*insn_read) for the timer subdevice
+ *
+ * Read The Selected Timer, Counter or Watchdog
+ * Data Pointer contains configuration parameters as below
+ * data[0] : 0 Timer
+ * 1 Counter
+ * 2 Watchdog
+ * data[1] : Timer Counter Watchdog Number
+ */
+static int apci3501_read_insn_timer(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+
+ if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) {
+ data[0] = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+ data[1] = inl(dev->iobase + APCI3501_TIMER_SYNC_REG);
+ }
+
+ else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) {
+ data[0] = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+ data[1] = inl(dev->iobase + APCI3501_TIMER_SYNC_REG);
+ }
+
+ else if ((devpriv->b_TimerSelectMode != ADDIDATA_TIMER)
+ && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG)) {
+ printk("\nIn ReadTimerCounterWatchdog :: Invalid Subdevice \n");
+ }
+ return insn->n;
+}
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 00000000000..4da9db35b8e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_035.c
@@ -0,0 +1,77 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+#include "addi-data/addi_common.h"
+
+#define ADDIDATA_WATCHDOG 2 /* Or shold it be something else */
+
+#include "addi-data/addi_eeprom.c"
+#include "addi-data/hwdrv_apci035.c"
+#include "addi-data/addi_common.c"
+
+static const struct addi_board apci035_boardtypes[] = {
+ {
+ .pc_DriverName = "apci035",
+ .i_IorangeBase1 = APCI035_ADDRESS_RANGE,
+ .i_PCIEeprom = 1,
+ .pc_EepromChip = ADDIDATA_S5920,
+ .i_NbrAiChannel = 16,
+ .i_NbrAiChannelDiff = 8,
+ .i_AiChannelList = 16,
+ .i_AiMaxdata = 0xff,
+ .pr_AiRangelist = &range_apci035_ai,
+ .i_Timer = 1,
+ .ui_MinAcquisitiontimeNs = 10000,
+ .ui_MinDelaytimeNs = 100000,
+ .interrupt = apci035_interrupt,
+ .reset = apci035_reset,
+ .ai_config = apci035_ai_config,
+ .ai_read = apci035_ai_read,
+ .timer_config = apci035_timer_config,
+ .timer_write = apci035_timer_write,
+ .timer_read = apci035_timer_read,
+ },
+};
+
+static int apci035_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ dev->board_ptr = &apci035_boardtypes[0];
+
+ return addi_auto_attach(dev, context);
+}
+
+static struct comedi_driver apci035_driver = {
+ .driver_name = "addi_apci_035",
+ .module = THIS_MODULE,
+ .auto_attach = apci035_auto_attach,
+ .detach = i_ADDI_Detach,
+};
+
+static int apci035_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci035_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci035_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x0300) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci035_pci_table);
+
+static struct pci_driver apci035_pci_driver = {
+ .name = "addi_apci_035",
+ .id_table = apci035_pci_table,
+ .probe = apci035_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci035_driver, apci035_pci_driver);
+
+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 00000000000..1b2e7c040c9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1032.c
@@ -0,0 +1,384 @@
+/*
+ * addi_apci_1032.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+/*
+ * I/O Register Map
+ */
+#define APCI1032_DI_REG 0x00
+#define APCI1032_MODE1_REG 0x04
+#define APCI1032_MODE2_REG 0x08
+#define APCI1032_STATUS_REG 0x0c
+#define APCI1032_CTRL_REG 0x10
+#define APCI1032_CTRL_INT_OR (0 << 1)
+#define APCI1032_CTRL_INT_AND (1 << 1)
+#define APCI1032_CTRL_INT_ENA (1 << 2)
+
+struct apci1032_private {
+ unsigned long amcc_iobase; /* base of AMCC I/O registers */
+ unsigned int mode1; /* rising-edge/high level channels */
+ unsigned int mode2; /* falling-edge/low level channels */
+ unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
+};
+
+static int apci1032_reset(struct comedi_device *dev)
+{
+ /* disable the interrupts */
+ outl(0x0, dev->iobase + APCI1032_CTRL_REG);
+ /* Reset the interrupt status register */
+ inl(dev->iobase + APCI1032_STATUS_REG);
+ /* Disable the and/or interrupt */
+ outl(0x0, dev->iobase + APCI1032_MODE1_REG);
+ outl(0x0, dev->iobase + APCI1032_MODE2_REG);
+
+ return 0;
+}
+
+/*
+ * Change-Of-State (COS) interrupt configuration
+ *
+ * Channels 0 to 15 are interruptible. These channels can be configured
+ * to generate interrupts based on AND/OR logic for the desired channels.
+ *
+ * OR logic
+ * - reacts to rising or falling edges
+ * - interrupt is generated when any enabled channel
+ * meet the desired interrupt condition
+ *
+ * AND logic
+ * - reacts to changes in level of the selected inputs
+ * - interrupt is generated when all enabled channels
+ * meet the desired interrupt condition
+ * - after an interrupt, a change in level must occur on
+ * the selected inputs to release the IRQ logic
+ *
+ * The COS interrupt must be configured before it can be enabled.
+ *
+ * data[0] : INSN_CONFIG_DIGITAL_TRIG
+ * data[1] : trigger number (= 0)
+ * data[2] : configuration operation:
+ * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
+ * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
+ * data[3] : left-shift for data[4] and data[5]
+ * data[4] : rising-edge/high level channels
+ * data[5] : falling-edge/low level channels
+ */
+static int apci1032_cos_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci1032_private *devpriv = dev->private;
+ unsigned int shift, oldmask;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIGITAL_TRIG:
+ if (data[1] != 0)
+ return -EINVAL;
+ shift = data[3];
+ oldmask = (1U << shift) - 1;
+ switch (data[2]) {
+ case COMEDI_DIGITAL_TRIG_DISABLE:
+ devpriv->ctrl = 0;
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ apci1032_reset(dev);
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
+ if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_OR)) {
+ /* switching to 'OR' mode */
+ devpriv->ctrl = APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_OR;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
+ if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_AND)) {
+ /* switching to 'AND' mode */
+ devpriv->ctrl = APCI1032_CTRL_INT_ENA |
+ APCI1032_CTRL_INT_AND;
+ /* wipe old channels */
+ devpriv->mode1 = 0;
+ devpriv->mode2 = 0;
+ } else {
+ /* preserve unspecified channels */
+ devpriv->mode1 &= oldmask;
+ devpriv->mode2 &= oldmask;
+ }
+ /* configure specified channels */
+ devpriv->mode1 |= data[4] << shift;
+ devpriv->mode2 |= data[5] << shift;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int apci1032_cos_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = s->state;
+
+ return 0;
+}
+
+static int apci1032_cos_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: ignored */
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+/*
+ * Change-Of-State (COS) 'do_cmd' operation
+ *
+ * Enable the COS interrupt as configured by apci1032_cos_insn_config().
+ */
+static int apci1032_cos_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci1032_private *devpriv = dev->private;
+
+ if (!devpriv->ctrl) {
+ dev_warn(dev->class_dev,
+ "Interrupts disabled due to mode configuration!\n");
+ return -EINVAL;
+ }
+
+ outl(devpriv->mode1, dev->iobase + APCI1032_MODE1_REG);
+ outl(devpriv->mode2, dev->iobase + APCI1032_MODE2_REG);
+ outl(devpriv->ctrl, dev->iobase + APCI1032_CTRL_REG);
+
+ return 0;
+}
+
+static int apci1032_cos_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return apci1032_reset(dev);
+}
+
+static irqreturn_t apci1032_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci1032_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int ctrl;
+
+ /* check interrupt is from this device */
+ if ((inl(devpriv->amcc_iobase + AMCC_OP_REG_INTCSR) &
+ INTCSR_INTR_ASSERTED) == 0)
+ return IRQ_NONE;
+
+ /* check interrupt is enabled */
+ ctrl = inl(dev->iobase + APCI1032_CTRL_REG);
+ if ((ctrl & APCI1032_CTRL_INT_ENA) == 0)
+ return IRQ_HANDLED;
+
+ /* disable the interrupt */
+ outl(ctrl & ~APCI1032_CTRL_INT_ENA, dev->iobase + APCI1032_CTRL_REG);
+
+ s->state = inl(dev->iobase + APCI1032_STATUS_REG) & 0xffff;
+ comedi_buf_put(s, s->state);
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ comedi_event(dev, s);
+
+ /* enable the interrupt */
+ outl(ctrl, dev->iobase + APCI1032_CTRL_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int apci1032_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI1032_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1032_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci1032_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->amcc_iobase = pci_resource_start(pcidev, 0);
+ dev->iobase = pci_resource_start(pcidev, 1);
+ apci1032_reset(dev);
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci1032_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Allocate and Initialise DI Subdevice Structures */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1032_di_insn_bits;
+
+ /* Change-Of-State (COS) interrupt subdevice */
+ s = &dev->subdevices[1];
+ if (dev->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_config = apci1032_cos_insn_config;
+ s->insn_bits = apci1032_cos_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = apci1032_cos_cmdtest;
+ s->do_cmd = apci1032_cos_cmd;
+ s->cancel = apci1032_cos_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void apci1032_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci1032_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci1032_driver = {
+ .driver_name = "addi_apci_1032",
+ .module = THIS_MODULE,
+ .auto_attach = apci1032_auto_attach,
+ .detach = apci1032_detach,
+};
+
+static int apci1032_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1032_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1032_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1003) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1032_pci_table);
+
+static struct pci_driver apci1032_pci_driver = {
+ .name = "addi_apci_1032",
+ .id_table = apci1032_pci_table,
+ .probe = apci1032_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1032_driver, apci1032_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1032, 32 channel DI boards");
+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 00000000000..eab75eb2647
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1500.c
@@ -0,0 +1,76 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+#include "addi-data/addi_common.h"
+
+#include "addi-data/addi_eeprom.c"
+#include "addi-data/hwdrv_apci1500.c"
+#include "addi-data/addi_common.c"
+
+static const struct addi_board apci1500_boardtypes[] = {
+ {
+ .pc_DriverName = "apci1500",
+ .i_IorangeBase1 = APCI1500_ADDRESS_RANGE,
+ .i_PCIEeprom = ADDIDATA_NO_EEPROM,
+ .i_NbrDiChannel = 16,
+ .i_NbrDoChannel = 16,
+ .i_DoMaxdata = 0xffff,
+ .i_Timer = 1,
+ .interrupt = apci1500_interrupt,
+ .reset = apci1500_reset,
+ .di_config = apci1500_di_config,
+ .di_read = apci1500_di_read,
+ .di_write = apci1500_di_write,
+ .di_bits = apci1500_di_insn_bits,
+ .do_config = apci1500_do_config,
+ .do_write = apci1500_do_write,
+ .do_bits = apci1500_do_bits,
+ .timer_config = apci1500_timer_config,
+ .timer_write = apci1500_timer_write,
+ .timer_read = apci1500_timer_read,
+ .timer_bits = apci1500_timer_bits,
+ },
+};
+
+static int apci1500_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ dev->board_ptr = &apci1500_boardtypes[0];
+
+ return addi_auto_attach(dev, context);
+}
+
+static struct comedi_driver apci1500_driver = {
+ .driver_name = "addi_apci_1500",
+ .module = THIS_MODULE,
+ .auto_attach = apci1500_auto_attach,
+ .detach = i_ADDI_Detach,
+};
+
+static int apci1500_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1500_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1500_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80fc) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1500_pci_table);
+
+static struct pci_driver apci1500_pci_driver = {
+ .name = "addi_apci_1500",
+ .id_table = apci1500_pci_table,
+ .probe = apci1500_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1500_driver, apci1500_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1500, 16 channel DI / 16 channel DO boards");
+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 00000000000..e9c5291c77c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1516.c
@@ -0,0 +1,227 @@
+/*
+ * addi_apci_1516.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "addi_watchdog.h"
+#include "comedi_fc.h"
+
+/*
+ * PCI bar 1 I/O Register map - Digital input/output
+ */
+#define APCI1516_DI_REG 0x00
+#define APCI1516_DO_REG 0x04
+
+/*
+ * PCI bar 2 I/O Register map - Watchdog (APCI-1516 and APCI-2016)
+ */
+#define APCI1516_WDOG_REG 0x00
+
+enum apci1516_boardid {
+ BOARD_APCI1016,
+ BOARD_APCI1516,
+ BOARD_APCI2016,
+};
+
+struct apci1516_boardinfo {
+ const char *name;
+ int di_nchan;
+ int do_nchan;
+ int has_wdog;
+};
+
+static const struct apci1516_boardinfo apci1516_boardtypes[] = {
+ [BOARD_APCI1016] = {
+ .name = "apci1016",
+ .di_nchan = 16,
+ },
+ [BOARD_APCI1516] = {
+ .name = "apci1516",
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .has_wdog = 1,
+ },
+ [BOARD_APCI2016] = {
+ .name = "apci2016",
+ .do_nchan = 16,
+ .has_wdog = 1,
+ },
+};
+
+struct apci1516_private {
+ unsigned long wdog_iobase;
+};
+
+static int apci1516_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + APCI1516_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1516_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inw(dev->iobase + APCI1516_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + APCI1516_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci1516_reset(struct comedi_device *dev)
+{
+ const struct apci1516_boardinfo *this_board = comedi_board(dev);
+ struct apci1516_private *devpriv = dev->private;
+
+ if (!this_board->has_wdog)
+ return 0;
+
+ outw(0x0, dev->iobase + APCI1516_DO_REG);
+
+ addi_watchdog_reset(devpriv->wdog_iobase);
+
+ return 0;
+}
+
+static int apci1516_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci1516_boardinfo *this_board = NULL;
+ struct apci1516_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci1516_boardtypes))
+ this_board = &apci1516_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->wdog_iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[0];
+ if (this_board->di_nchan) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = this_board->di_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1516_di_insn_bits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[1];
+ if (this_board->do_nchan) {
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = this_board->do_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci1516_do_insn_bits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[2];
+ if (this_board->has_wdog) {
+ ret = addi_watchdog_init(s, devpriv->wdog_iobase);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ apci1516_reset(dev);
+ return 0;
+}
+
+static void apci1516_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci1516_reset(dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci1516_driver = {
+ .driver_name = "addi_apci_1516",
+ .module = THIS_MODULE,
+ .auto_attach = apci1516_auto_attach,
+ .detach = apci1516_detach,
+};
+
+static int apci1516_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1516_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1516_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x1000), BOARD_APCI1016 },
+ { PCI_VDEVICE(ADDIDATA, 0x1001), BOARD_APCI1516 },
+ { PCI_VDEVICE(ADDIDATA, 0x1002), BOARD_APCI2016 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1516_pci_table);
+
+static struct pci_driver apci1516_pci_driver = {
+ .name = "addi_apci_1516",
+ .id_table = apci1516_pci_table,
+ .probe = apci1516_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1516_driver, apci1516_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-1016/1516/2016, 16 channel DIO boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+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 00000000000..13d9962b47e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_1564.c
@@ -0,0 +1,192 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+
+#include "addi-data/addi_common.h"
+
+#include "addi-data/hwdrv_apci1564.c"
+
+static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
+{
+ apci1564_interrupt(irq, d);
+ return IRQ_RETVAL(1);
+}
+
+static int apci1564_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_DI_REG);
+
+ return insn->n;
+}
+
+static int apci1564_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_private *devpriv = dev->private;
+
+ s->state = inl(devpriv->i_IobaseAmcc + APCI1564_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, devpriv->i_IobaseAmcc + APCI1564_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci1564_reset(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ ui_Type = 0;
+
+ /* Disable the input interrupts and reset status register */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_IRQ_REG);
+ inl(devpriv->i_IobaseAmcc + APCI1564_DI_INT_STATUS_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE1_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DI_INT_MODE2_REG);
+
+ /* Reset the output channels and disable interrupts */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DO_INT_CTRL_REG);
+
+ /* Reset the watchdog registers */
+ addi_watchdog_reset(devpriv->i_IobaseAmcc + APCI1564_WDOG_REG);
+
+ /* Reset the timer registers */
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_CTRL_REG);
+ outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER_RELOAD_REG);
+
+ /* Reset the counter registers */
+ outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
+ outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
+ outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
+ outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
+
+ return 0;
+}
+
+static int apci1564_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct addi_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev->board_name = dev->driver->driver_name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
+
+ apci1564_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Allocate and Initialise DI Subdevice Structures */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->len_chanlist = 32;
+ s->range_table = &range_digital;
+ s->insn_config = apci1564_di_config;
+ s->insn_bits = apci1564_di_insn_bits;
+
+ /* Allocate and Initialise DO Subdevice Structures */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 32;
+ s->maxdata = 0xffffffff;
+ s->len_chanlist = 32;
+ s->range_table = &range_digital;
+ s->insn_config = apci1564_do_config;
+ s->insn_bits = apci1564_do_insn_bits;
+ s->insn_read = apci1564_do_read;
+
+ /* Allocate and Initialise Timer Subdevice Structures */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 1;
+ s->maxdata = 0;
+ s->len_chanlist = 1;
+ s->range_table = &range_digital;
+ s->insn_write = apci1564_timer_write;
+ s->insn_read = apci1564_timer_read;
+ s->insn_config = apci1564_timer_config;
+
+ return 0;
+}
+
+static void apci1564_detach(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ apci1564_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci1564_driver = {
+ .driver_name = "addi_apci_1564",
+ .module = THIS_MODULE,
+ .auto_attach = apci1564_auto_attach,
+ .detach = apci1564_detach,
+};
+
+static int apci1564_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci1564_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci1564_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1006) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci1564_pci_table);
+
+static struct pci_driver apci1564_pci_driver = {
+ .name = "addi_apci_1564",
+ .id_table = apci1564_pci_table,
+ .probe = apci1564_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci1564_driver, apci1564_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-1564, 32 channel DI / 32 channel DO boards");
+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 00000000000..28df4b50b87
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_16xx.c
@@ -0,0 +1,188 @@
+/*
+ * addi_apci_16xx.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: S. Weber
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define APCI16XX_IN_REG(x) (((x) * 4) + 0x08)
+#define APCI16XX_OUT_REG(x) (((x) * 4) + 0x14)
+#define APCI16XX_DIR_REG(x) (((x) * 4) + 0x20)
+
+enum apci16xx_boardid {
+ BOARD_APCI1648,
+ BOARD_APCI1696,
+};
+
+struct apci16xx_boardinfo {
+ const char *name;
+ int n_chan;
+};
+
+static const struct apci16xx_boardinfo apci16xx_boardtypes[] = {
+ [BOARD_APCI1648] = {
+ .name = "apci1648",
+ .n_chan = 48, /* 2 subdevices */
+ },
+ [BOARD_APCI1696] = {
+ .name = "apci1696",
+ .n_chan = 96, /* 3 subdevices */
+ },
+};
+
+static int apci16xx_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(s->index));
+
+ return insn->n;
+}
+
+static int apci16xx_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI16XX_OUT_REG(s->index));
+
+ data[1] = inl(dev->iobase + APCI16XX_IN_REG(s->index));
+
+ return insn->n;
+}
+
+static int apci16xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci16xx_boardinfo *board = NULL;
+ struct comedi_subdevice *s;
+ unsigned int n_subdevs;
+ unsigned int last;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci16xx_boardtypes))
+ board = &apci16xx_boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ /*
+ * Work out the nubmer of subdevices needed to support all the
+ * digital i/o channels on the board. Each subdevice supports
+ * up to 32 channels.
+ */
+ n_subdevs = board->n_chan / 32;
+ if ((n_subdevs * 32) < board->n_chan) {
+ last = board->n_chan - (n_subdevs * 32);
+ n_subdevs++;
+ } else {
+ last = 0;
+ }
+
+ ret = comedi_alloc_subdevices(dev, n_subdevs);
+ if (ret)
+ return ret;
+
+ /* Initialize the TTL digital i/o subdevices */
+ for (i = 0; i < n_subdevs; i++) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_READABLE;
+ s->n_chan = ((i * 32) < board->n_chan) ? 32 : last;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = apci16xx_insn_config;
+ s->insn_bits = apci16xx_dio_insn_bits;
+
+ /* Default all channels to inputs */
+ s->io_bits = 0;
+ outl(s->io_bits, dev->iobase + APCI16XX_DIR_REG(i));
+ }
+
+ return 0;
+}
+
+static struct comedi_driver apci16xx_driver = {
+ .driver_name = "addi_apci_16xx",
+ .module = THIS_MODULE,
+ .auto_attach = apci16xx_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int apci16xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci16xx_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci16xx_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x1009), BOARD_APCI1648 },
+ { PCI_VDEVICE(ADDIDATA, 0x100a), BOARD_APCI1696 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci16xx_pci_table);
+
+static struct pci_driver apci16xx_pci_driver = {
+ .name = "addi_apci_16xx",
+ .id_table = apci16xx_pci_table,
+ .probe = apci16xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci16xx_driver, apci16xx_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-1648/1696, TTL I/O boards");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+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 00000000000..be0a8a7bd3b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_2032.c
@@ -0,0 +1,378 @@
+/*
+ * addi_apci_2032.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+#include "addi_watchdog.h"
+#include "comedi_fc.h"
+
+/*
+ * PCI bar 1 I/O Register map
+ */
+#define APCI2032_DO_REG 0x00
+#define APCI2032_INT_CTRL_REG 0x04
+#define APCI2032_INT_CTRL_VCC_ENA (1 << 0)
+#define APCI2032_INT_CTRL_CC_ENA (1 << 1)
+#define APCI2032_INT_STATUS_REG 0x08
+#define APCI2032_INT_STATUS_VCC (1 << 0)
+#define APCI2032_INT_STATUS_CC (1 << 1)
+#define APCI2032_STATUS_REG 0x0c
+#define APCI2032_STATUS_IRQ (1 << 0)
+#define APCI2032_WDOG_REG 0x10
+
+struct apci2032_int_private {
+ spinlock_t spinlock;
+ unsigned int stop_count;
+ bool active;
+ unsigned char enabled_isns;
+};
+
+static int apci2032_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + APCI2032_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI2032_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci2032_int_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
+ return insn->n;
+}
+
+static void apci2032_int_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci2032_int_private *subpriv = s->private;
+
+ subpriv->active = false;
+ subpriv->enabled_isns = 0;
+ outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+}
+
+static bool apci2032_int_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned char enabled_isns)
+{
+ struct apci2032_int_private *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ bool do_event;
+
+ subpriv->enabled_isns = enabled_isns;
+ subpriv->stop_count = cmd->stop_arg;
+ if (cmd->stop_src == TRIG_COUNT && subpriv->stop_count == 0) {
+ /* An empty acquisition! */
+ s->async->events |= COMEDI_CB_EOA;
+ subpriv->active = false;
+ do_event = true;
+ } else {
+ subpriv->active = true;
+ outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG);
+ do_event = false;
+ }
+
+ return do_event;
+}
+
+static int apci2032_int_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: ignored */
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int apci2032_int_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct apci2032_int_private *subpriv = s->private;
+ unsigned char enabled_isns;
+ unsigned int n;
+ unsigned long flags;
+ bool do_event;
+
+ enabled_isns = 0;
+ for (n = 0; n < cmd->chanlist_len; n++)
+ enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]);
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ do_event = apci2032_int_start(dev, s, enabled_isns);
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ if (do_event)
+ comedi_event(dev, s);
+
+ return 0;
+}
+
+static int apci2032_int_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci2032_int_private *subpriv = s->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ if (subpriv->active)
+ apci2032_int_stop(dev, s);
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+
+ return 0;
+}
+
+static irqreturn_t apci2032_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct apci2032_int_private *subpriv;
+ unsigned int val;
+ bool do_event = false;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ /* Check if VCC OR CC interrupt has occurred */
+ val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ;
+ if (!val)
+ return IRQ_NONE;
+
+ subpriv = s->private;
+ spin_lock(&subpriv->spinlock);
+
+ val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
+ /* Disable triggered interrupt sources. */
+ outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG);
+ /*
+ * Note: We don't reenable the triggered interrupt sources because they
+ * are level-sensitive, hardware error status interrupt sources and
+ * they'd keep triggering interrupts repeatedly.
+ */
+
+ if (subpriv->active && (val & subpriv->enabled_isns) != 0) {
+ unsigned short bits = 0;
+ int i;
+
+ /* Bits in scan data correspond to indices in channel list. */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (val & (1 << chan))
+ bits |= (1 << i);
+ }
+
+ if (comedi_buf_put(s, bits)) {
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ if (cmd->stop_src == TRIG_COUNT &&
+ subpriv->stop_count > 0) {
+ subpriv->stop_count--;
+ if (subpriv->stop_count == 0) {
+ /* end of acquisition */
+ s->async->events |= COMEDI_CB_EOA;
+ apci2032_int_stop(dev, s);
+ }
+ }
+ } else {
+ apci2032_int_stop(dev, s);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ do_event = true;
+ }
+
+ spin_unlock(&subpriv->spinlock);
+ if (do_event)
+ comedi_event(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int apci2032_reset(struct comedi_device *dev)
+{
+ outl(0x0, dev->iobase + APCI2032_DO_REG);
+ outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
+
+ addi_watchdog_reset(dev->iobase + APCI2032_WDOG_REG);
+
+ return 0;
+}
+
+static int apci2032_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 1);
+ apci2032_reset(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci2032_interrupt,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2032_do_insn_bits;
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[1];
+ ret = addi_watchdog_init(s, dev->iobase + APCI2032_WDOG_REG);
+ if (ret)
+ return ret;
+
+ /* Initialize the interrupt subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2032_int_insn_bits;
+ if (dev->irq) {
+ struct apci2032_int_private *subpriv;
+
+ dev->read_subdev = s;
+ subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
+ if (!subpriv)
+ return -ENOMEM;
+ spin_lock_init(&subpriv->spinlock);
+ s->private = subpriv;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ s->len_chanlist = 2;
+ s->do_cmdtest = apci2032_int_cmdtest;
+ s->do_cmd = apci2032_int_cmd;
+ s->cancel = apci2032_int_cancel;
+ }
+
+ return 0;
+}
+
+static void apci2032_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci2032_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (dev->read_subdev)
+ kfree(dev->read_subdev->private);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci2032_driver = {
+ .driver_name = "addi_apci_2032",
+ .module = THIS_MODULE,
+ .auto_attach = apci2032_auto_attach,
+ .detach = apci2032_detach,
+};
+
+static int apci2032_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci2032_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci2032_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci2032_pci_table);
+
+static struct pci_driver apci2032_pci_driver = {
+ .name = "addi_apci_2032",
+ .id_table = apci2032_pci_table,
+ .probe = apci2032_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards");
+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 00000000000..e1a916546d1
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_2200.c
@@ -0,0 +1,153 @@
+/*
+ * addi_apci_2200.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "addi_watchdog.h"
+
+/*
+ * I/O Register Map
+ */
+#define APCI2200_DI_REG 0x00
+#define APCI2200_DO_REG 0x04
+#define APCI2200_WDOG_REG 0x08
+
+static int apci2200_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + APCI2200_DI_REG);
+
+ return insn->n;
+}
+
+static int apci2200_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inw(dev->iobase + APCI2200_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + APCI2200_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci2200_reset(struct comedi_device *dev)
+{
+ outw(0x0, dev->iobase + APCI2200_DO_REG);
+
+ addi_watchdog_reset(dev->iobase + APCI2200_WDOG_REG);
+
+ return 0;
+}
+
+static int apci2200_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2200_di_insn_bits;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci2200_do_insn_bits;
+
+ /* Initialize the watchdog subdevice */
+ s = &dev->subdevices[2];
+ ret = addi_watchdog_init(s, dev->iobase + APCI2200_WDOG_REG);
+ if (ret)
+ return ret;
+
+ apci2200_reset(dev);
+ return 0;
+}
+
+static void apci2200_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci2200_reset(dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci2200_driver = {
+ .driver_name = "addi_apci_2200",
+ .module = THIS_MODULE,
+ .auto_attach = apci2200_auto_attach,
+ .detach = apci2200_detach,
+};
+
+static int apci2200_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci2200_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci2200_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1005) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci2200_pci_table);
+
+static struct pci_driver apci2200_pci_driver = {
+ .name = "addi_apci_2200",
+ .id_table = apci2200_pci_table,
+ .probe = apci2200_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci2200_driver, apci2200_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-2200 Relay board, optically isolated");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+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 00000000000..0cfb12fa1cb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3120.c
@@ -0,0 +1,250 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+#include "addi-data/addi_common.h"
+
+#include "addi-data/hwdrv_apci3120.c"
+
+enum apci3120_boardid {
+ BOARD_APCI3120,
+ BOARD_APCI3001,
+};
+
+static const struct addi_board apci3120_boardtypes[] = {
+ [BOARD_APCI3120] = {
+ .pc_DriverName = "apci3120",
+ .i_NbrAiChannel = 16,
+ .i_NbrAiChannelDiff = 8,
+ .i_AiChannelList = 16,
+ .i_NbrAoChannel = 8,
+ .i_AiMaxdata = 0xffff,
+ .i_AoMaxdata = 0x3fff,
+ .i_NbrDiChannel = 4,
+ .i_NbrDoChannel = 4,
+ .i_DoMaxdata = 0x0f,
+ .interrupt = apci3120_interrupt,
+ },
+ [BOARD_APCI3001] = {
+ .pc_DriverName = "apci3001",
+ .i_NbrAiChannel = 16,
+ .i_NbrAiChannelDiff = 8,
+ .i_AiChannelList = 16,
+ .i_AiMaxdata = 0xfff,
+ .i_NbrDiChannel = 4,
+ .i_NbrDoChannel = 4,
+ .i_DoMaxdata = 0x0f,
+ .interrupt = apci3120_interrupt,
+ },
+};
+
+static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ const struct addi_board *this_board = comedi_board(dev);
+
+ this_board->interrupt(irq, d);
+ return IRQ_RETVAL(1);
+}
+
+static int apci3120_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct addi_board *this_board = NULL;
+ struct addi_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, pages, i;
+
+ if (context < ARRAY_SIZE(apci3120_boardtypes))
+ this_board = &apci3120_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->pc_DriverName;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ pci_set_master(pcidev);
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->iobase = dev->iobase;
+ devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
+ devpriv->i_IobaseAddon = pci_resource_start(pcidev, 2);
+ devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ devpriv->us_UseDma = ADDI_ENABLE;
+
+ /* Allocate 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->ul_DmaBufferHw[i] =
+ virt_to_bus((void *)devpriv->
+ ul_DmaBufferVirtual[i]);
+ }
+ }
+ if (!devpriv->ul_DmaBufferVirtual[0])
+ devpriv->us_UseDma = ADDI_DISABLE;
+
+ if (devpriv->ul_DmaBufferVirtual[1])
+ devpriv->b_DmaDoubleBuffer = 1;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* Allocate and Initialise AI Subdevice Structures */
+ 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 (this_board->i_NbrAiChannel) {
+ s->n_chan = this_board->i_NbrAiChannel;
+ devpriv->b_SingelDiff = 0;
+ } else {
+ s->n_chan = this_board->i_NbrAiChannelDiff;
+ devpriv->b_SingelDiff = 1;
+ }
+ s->maxdata = this_board->i_AiMaxdata;
+ s->len_chanlist = this_board->i_AiChannelList;
+ s->range_table = &range_apci3120_ai;
+
+ s->insn_config = apci3120_ai_insn_config;
+ s->insn_read = apci3120_ai_insn_read;
+ s->do_cmdtest = apci3120_ai_cmdtest;
+ s->do_cmd = apci3120_ai_cmd;
+ s->cancel = apci3120_cancel;
+
+ /* Allocate and Initialise AO Subdevice Structures */
+ s = &dev->subdevices[1];
+ if (this_board->i_NbrAoChannel) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = this_board->i_NbrAoChannel;
+ s->maxdata = this_board->i_AoMaxdata;
+ s->len_chanlist = this_board->i_NbrAoChannel;
+ s->range_table = &range_apci3120_ao;
+ s->insn_write = apci3120_ao_insn_write;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Allocate and Initialise DI Subdevice Structures */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = this_board->i_NbrDiChannel;
+ s->maxdata = 1;
+ s->len_chanlist = this_board->i_NbrDiChannel;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3120_di_insn_bits;
+
+ /* Allocate and Initialise DO Subdevice Structures */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags =
+ SDF_READABLE | SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = this_board->i_NbrDoChannel;
+ s->maxdata = this_board->i_DoMaxdata;
+ s->len_chanlist = this_board->i_NbrDoChannel;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3120_do_insn_bits;
+
+ /* Allocate and Initialise Timer Subdevice Structures */
+ s = &dev->subdevices[4];
+ 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 = apci3120_write_insn_timer;
+ s->insn_read = apci3120_read_insn_timer;
+ s->insn_config = apci3120_config_insn_timer;
+
+ apci3120_reset(dev);
+ return 0;
+}
+
+static void apci3120_detach(struct comedi_device *dev)
+{
+ struct addi_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ apci3120_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ 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]);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci3120_driver = {
+ .driver_name = "addi_apci_3120",
+ .module = THIS_MODULE,
+ .auto_attach = apci3120_auto_attach,
+ .detach = apci3120_detach,
+};
+
+static int apci3120_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3120_pci_table[] = {
+ { PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 },
+ { PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3120_pci_table);
+
+static struct pci_driver apci3120_pci_driver = {
+ .name = "addi_apci_3120",
+ .id_table = apci3120_pci_table,
+ .probe = apci3120_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board");
+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 00000000000..f0f891a482a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3200.c
@@ -0,0 +1,125 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include <asm/i387.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+#include "addi-data/addi_common.h"
+
+static void fpu_begin(void)
+{
+ kernel_fpu_begin();
+}
+
+static void fpu_end(void)
+{
+ kernel_fpu_end();
+}
+
+#include "addi-data/addi_eeprom.c"
+#include "addi-data/hwdrv_apci3200.c"
+#include "addi-data/addi_common.c"
+
+enum apci3200_boardid {
+ BOARD_APCI3200,
+ BOARD_APCI3300,
+};
+
+static const struct addi_board apci3200_boardtypes[] = {
+ [BOARD_APCI3200] = {
+ .pc_DriverName = "apci3200",
+ .i_IorangeBase1 = 256,
+ .i_PCIEeprom = ADDIDATA_EEPROM,
+ .pc_EepromChip = ADDIDATA_S5920,
+ .i_NbrAiChannel = 16,
+ .i_NbrAiChannelDiff = 8,
+ .i_AiChannelList = 16,
+ .i_AiMaxdata = 0x3ffff,
+ .pr_AiRangelist = &range_apci3200_ai,
+ .i_NbrDiChannel = 4,
+ .i_NbrDoChannel = 4,
+ .ui_MinAcquisitiontimeNs = 10000,
+ .ui_MinDelaytimeNs = 100000,
+ .interrupt = apci3200_interrupt,
+ .reset = apci3200_reset,
+ .ai_config = apci3200_ai_config,
+ .ai_read = apci3200_ai_read,
+ .ai_write = apci3200_ai_write,
+ .ai_bits = apci3200_ai_bits_test,
+ .ai_cmdtest = apci3200_ai_cmdtest,
+ .ai_cmd = apci3200_ai_cmd,
+ .ai_cancel = apci3200_cancel,
+ .di_bits = apci3200_di_insn_bits,
+ .do_bits = apci3200_do_insn_bits,
+ },
+ [BOARD_APCI3300] = {
+ .pc_DriverName = "apci3300",
+ .i_IorangeBase1 = 256,
+ .i_PCIEeprom = ADDIDATA_EEPROM,
+ .pc_EepromChip = ADDIDATA_S5920,
+ .i_NbrAiChannelDiff = 8,
+ .i_AiChannelList = 8,
+ .i_AiMaxdata = 0x3ffff,
+ .pr_AiRangelist = &range_apci3300_ai,
+ .i_NbrDiChannel = 4,
+ .i_NbrDoChannel = 4,
+ .ui_MinAcquisitiontimeNs = 10000,
+ .ui_MinDelaytimeNs = 100000,
+ .interrupt = apci3200_interrupt,
+ .reset = apci3200_reset,
+ .ai_config = apci3200_ai_config,
+ .ai_read = apci3200_ai_read,
+ .ai_write = apci3200_ai_write,
+ .ai_bits = apci3200_ai_bits_test,
+ .ai_cmdtest = apci3200_ai_cmdtest,
+ .ai_cmd = apci3200_ai_cmd,
+ .ai_cancel = apci3200_cancel,
+ .di_bits = apci3200_di_insn_bits,
+ .do_bits = apci3200_do_insn_bits,
+ },
+};
+
+static int apci3200_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ const struct addi_board *board = NULL;
+
+ if (context < ARRAY_SIZE(apci3200_boardtypes))
+ board = &apci3200_boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+
+ return addi_auto_attach(dev, context);
+}
+
+static struct comedi_driver apci3200_driver = {
+ .driver_name = "addi_apci_3200",
+ .module = THIS_MODULE,
+ .auto_attach = apci3200_auto_attach,
+ .detach = i_ADDI_Detach,
+};
+
+static int apci3200_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3200_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3200_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x3000), BOARD_APCI3200 },
+ { PCI_VDEVICE(ADDIDATA, 0x3007), BOARD_APCI3300 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3200_pci_table);
+
+static struct pci_driver apci3200_pci_driver = {
+ .name = "addi_apci_3200",
+ .id_table = apci3200_pci_table,
+ .probe = apci3200_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3200_driver, apci3200_pci_driver);
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 00000000000..49bf1fb840f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3501.c
@@ -0,0 +1,447 @@
+/*
+ * addi_apci_3501.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: Eric Stolz
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedidev.h"
+#include "comedi_fc.h"
+#include "amcc_s5933.h"
+
+/*
+ * PCI bar 1 register I/O map
+ */
+#define APCI3501_AO_CTRL_STATUS_REG 0x00
+#define APCI3501_AO_CTRL_BIPOLAR (1 << 0)
+#define APCI3501_AO_STATUS_READY (1 << 8)
+#define APCI3501_AO_DATA_REG 0x04
+#define APCI3501_AO_DATA_CHAN(x) ((x) << 0)
+#define APCI3501_AO_DATA_VAL(x) ((x) << 8)
+#define APCI3501_AO_DATA_BIPOLAR (1 << 31)
+#define APCI3501_AO_TRIG_SCS_REG 0x08
+#define APCI3501_TIMER_SYNC_REG 0x20
+#define APCI3501_TIMER_RELOAD_REG 0x24
+#define APCI3501_TIMER_TIMEBASE_REG 0x28
+#define APCI3501_TIMER_CTRL_REG 0x2c
+#define APCI3501_TIMER_STATUS_REG 0x30
+#define APCI3501_TIMER_IRQ_REG 0x34
+#define APCI3501_TIMER_WARN_RELOAD_REG 0x38
+#define APCI3501_TIMER_WARN_TIMEBASE_REG 0x3c
+#define APCI3501_DO_REG 0x40
+#define APCI3501_DI_REG 0x50
+
+/*
+ * AMCC S5933 NVRAM
+ */
+#define NVRAM_USER_DATA_START 0x100
+
+#define NVCMD_BEGIN_READ (0x7 << 5)
+#define NVCMD_LOAD_LOW (0x4 << 5)
+#define NVCMD_LOAD_HIGH (0x5 << 5)
+
+/*
+ * Function types stored in the eeprom
+ */
+#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 apci3501_private {
+ int i_IobaseAmcc;
+ struct task_struct *tsk_Current;
+ unsigned char b_TimerSelectMode;
+};
+
+static struct comedi_lrange apci3501_ao_range = {
+ 2, {
+ BIP_RANGE(10),
+ UNI_RANGE(10)
+ }
+};
+
+static int apci3501_wait_for_dac(struct comedi_device *dev)
+{
+ unsigned int status;
+
+ do {
+ status = inl(dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ } while (!(status & APCI3501_AO_STATUS_READY));
+
+ return 0;
+}
+
+static int apci3501_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 range = CR_RANGE(insn->chanspec);
+ unsigned int val = 0;
+ int i;
+ int ret;
+
+ /*
+ * All analog output channels have the same output range.
+ * 14-bit bipolar: 0-10V
+ * 13-bit unipolar: +/-10V
+ * Changing the range of one channel changes all of them!
+ */
+ if (range) {
+ outl(0, dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ } else {
+ val |= APCI3501_AO_DATA_BIPOLAR;
+ outl(APCI3501_AO_CTRL_BIPOLAR,
+ dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ }
+
+ val |= APCI3501_AO_DATA_CHAN(chan);
+
+ for (i = 0; i < insn->n; i++) {
+ if (range == 1) {
+ if (data[i] > 0x1fff) {
+ dev_err(dev->class_dev,
+ "Unipolar resolution is only 13-bits\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = apci3501_wait_for_dac(dev);
+ if (ret)
+ return ret;
+
+ outl(val | APCI3501_AO_DATA_VAL(data[i]),
+ dev->iobase + APCI3501_AO_DATA_REG);
+ }
+
+ return insn->n;
+}
+
+#include "addi-data/hwdrv_apci3501.c"
+
+static int apci3501_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + APCI3501_DI_REG) & 0x3;
+
+ return insn->n;
+}
+
+static int apci3501_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + APCI3501_DO_REG);
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + APCI3501_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void apci3501_eeprom_wait(unsigned long iobase)
+{
+ unsigned char val;
+
+ do {
+ val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD);
+ } while (val & 0x80);
+}
+
+static unsigned short apci3501_eeprom_readw(unsigned long iobase,
+ unsigned short addr)
+{
+ unsigned short val = 0;
+ unsigned char tmp;
+ unsigned char i;
+
+ /* Add the offset to the start of the user data */
+ addr += NVRAM_USER_DATA_START;
+
+ for (i = 0; i < 2; i++) {
+ /* Load the low 8 bit address */
+ outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ /* Load the high 8 bit address */
+ outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ outb(((addr + i) >> 8) & 0xff,
+ iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ /* Read the eeprom data byte */
+ outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD);
+ apci3501_eeprom_wait(iobase);
+ tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA);
+ apci3501_eeprom_wait(iobase);
+
+ if (i == 0)
+ val |= tmp;
+ else
+ val |= (tmp << 8);
+ }
+
+ return val;
+}
+
+static int apci3501_eeprom_get_ao_n_chan(struct comedi_device *dev)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned long iobase = devpriv->i_IobaseAmcc;
+ unsigned char nfuncs;
+ int i;
+
+ nfuncs = apci3501_eeprom_readw(iobase, 10) & 0xff;
+
+ /* Read functionality details */
+ for (i = 0; i < nfuncs; i++) {
+ unsigned short offset = i * 4;
+ unsigned short addr;
+ unsigned char func;
+ unsigned short val;
+
+ func = apci3501_eeprom_readw(iobase, 12 + offset) & 0x3f;
+ addr = apci3501_eeprom_readw(iobase, 14 + offset);
+
+ if (func == EEPROM_ANALOGOUTPUT) {
+ val = apci3501_eeprom_readw(iobase, addr + 10);
+ return (val >> 4) & 0x3ff;
+ }
+ }
+ return 0;
+}
+
+static int apci3501_eeprom_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3501_private *devpriv = dev->private;
+ unsigned short addr = CR_CHAN(insn->chanspec);
+
+ data[0] = apci3501_eeprom_readw(devpriv->i_IobaseAmcc, 2 * addr);
+
+ return insn->n;
+}
+
+static irqreturn_t apci3501_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci3501_private *devpriv = dev->private;
+ unsigned int ui_Timer_AOWatchdog;
+ unsigned long ul_Command1;
+ int i_temp;
+
+ /* Disable Interrupt */
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = (ul_Command1 & 0xFFFFF9FDul);
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+
+ ui_Timer_AOWatchdog = inl(dev->iobase + APCI3501_TIMER_IRQ_REG) & 0x1;
+ if ((!ui_Timer_AOWatchdog)) {
+ comedi_error(dev, "IRQ from unknown source");
+ return IRQ_NONE;
+ }
+
+ /* Enable Interrupt Send a signal to from kernel to user space */
+ send_sig(SIGIO, devpriv->tsk_Current, 0);
+ ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG);
+ ul_Command1 = ((ul_Command1 & 0xFFFFF9FDul) | 1 << 1);
+ outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG);
+ i_temp = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1;
+
+ return IRQ_HANDLED;
+}
+
+static int apci3501_reset(struct comedi_device *dev)
+{
+ unsigned int val;
+ int chan;
+ int ret;
+
+ /* Reset all digital outputs to "0" */
+ outl(0x0, dev->iobase + APCI3501_DO_REG);
+
+ /* Default all analog outputs to 0V (bipolar) */
+ outl(APCI3501_AO_CTRL_BIPOLAR,
+ dev->iobase + APCI3501_AO_CTRL_STATUS_REG);
+ val = APCI3501_AO_DATA_BIPOLAR | APCI3501_AO_DATA_VAL(0);
+
+ /* Set all analog output channels */
+ for (chan = 0; chan < 8; chan++) {
+ ret = apci3501_wait_for_dac(dev);
+ if (ret) {
+ dev_warn(dev->class_dev,
+ "%s: DAC not-ready for channel %i\n",
+ __func__, chan);
+ } else {
+ outl(val | APCI3501_AO_DATA_CHAN(chan),
+ dev->iobase + APCI3501_AO_DATA_REG);
+ }
+ }
+
+ return 0;
+}
+
+static int apci3501_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct apci3501_private *devpriv;
+ struct comedi_subdevice *s;
+ int ao_n_chan;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 1);
+ devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
+
+ ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci3501_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* Initialize the analog output subdevice */
+ s = &dev->subdevices[0];
+ if (ao_n_chan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = ao_n_chan;
+ s->maxdata = 0x3fff;
+ s->range_table = &apci3501_ao_range;
+ s->insn_write = apci3501_ao_insn_write;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Initialize the digital input subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3501_di_insn_bits;
+
+ /* Initialize the digital output subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 2;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3501_do_insn_bits;
+
+ /* Initialize the timer/watchdog subdevice */
+ s = &dev->subdevices[3];
+ 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 = apci3501_write_insn_timer;
+ s->insn_read = apci3501_read_insn_timer;
+ s->insn_config = apci3501_config_insn_timer;
+
+ /* Initialize the eeprom subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_MEMORY;
+ s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
+ s->n_chan = 256;
+ s->maxdata = 0xffff;
+ s->insn_read = apci3501_eeprom_insn_read;
+
+ apci3501_reset(dev);
+ return 0;
+}
+
+static void apci3501_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ apci3501_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci3501_driver = {
+ .driver_name = "addi_apci_3501",
+ .module = THIS_MODULE,
+ .auto_attach = apci3501_auto_attach,
+ .detach = apci3501_detach,
+};
+
+static int apci3501_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3501_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3501_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3501_pci_table);
+
+static struct pci_driver apci3501_pci_driver = {
+ .name = "addi_apci_3501",
+ .id_table = apci3501_pci_table,
+ .probe = apci3501_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver);
+
+MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+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 00000000000..0532b6cc40e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_apci_3xxx.c
@@ -0,0 +1,982 @@
+/*
+ * addi_apci_3xxx.c
+ * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
+ * Project manager: S. Weber
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+#define CONV_UNIT_NS (1 << 0)
+#define CONV_UNIT_US (1 << 1)
+#define CONV_UNIT_MS (1 << 2)
+
+static const struct comedi_lrange apci3xxx_ai_range = {
+ 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 apci3xxx_ao_range = {
+ 2, {
+ BIP_RANGE(10),
+ UNI_RANGE(10)
+ }
+};
+
+enum apci3xxx_boardid {
+ BOARD_APCI3000_16,
+ BOARD_APCI3000_8,
+ BOARD_APCI3000_4,
+ BOARD_APCI3006_16,
+ BOARD_APCI3006_8,
+ BOARD_APCI3006_4,
+ BOARD_APCI3010_16,
+ BOARD_APCI3010_8,
+ BOARD_APCI3010_4,
+ BOARD_APCI3016_16,
+ BOARD_APCI3016_8,
+ BOARD_APCI3016_4,
+ BOARD_APCI3100_16_4,
+ BOARD_APCI3100_8_4,
+ BOARD_APCI3106_16_4,
+ BOARD_APCI3106_8_4,
+ BOARD_APCI3110_16_4,
+ BOARD_APCI3110_8_4,
+ BOARD_APCI3116_16_4,
+ BOARD_APCI3116_8_4,
+ BOARD_APCI3003,
+ BOARD_APCI3002_16,
+ BOARD_APCI3002_8,
+ BOARD_APCI3002_4,
+ BOARD_APCI3500,
+};
+
+struct apci3xxx_boardinfo {
+ const char *name;
+ int ai_subdev_flags;
+ int ai_n_chan;
+ unsigned int ai_maxdata;
+ unsigned char ai_conv_units;
+ unsigned int ai_min_acq_ns;
+ unsigned int has_ao:1;
+ unsigned int has_dig_in:1;
+ unsigned int has_dig_out:1;
+ unsigned int has_ttl_io:1;
+};
+
+static const struct apci3xxx_boardinfo apci3xxx_boardtypes[] = {
+ [BOARD_APCI3000_16] = {
+ .name = "apci3000-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3000_8] = {
+ .name = "apci3000-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3000_4] = {
+ .name = "apci3000-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_16] = {
+ .name = "apci3006-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_8] = {
+ .name = "apci3006-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3006_4] = {
+ .name = "apci3006-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_16] = {
+ .name = "apci3010-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_8] = {
+ .name = "apci3010-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3010_4] = {
+ .name = "apci3010-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_16] = {
+ .name = "apci3016-16",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_8] = {
+ .name = "apci3016-8",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3016_4] = {
+ .name = "apci3016-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3100_16_4] = {
+ .name = "apci3100-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3100_8_4] = {
+ .name = "apci3100-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3106_16_4] = {
+ .name = "apci3106-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3106_8_4] = {
+ .name = "apci3106-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 10000,
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3110_16_4] = {
+ .name = "apci3110-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3110_8_4] = {
+ .name = "apci3110-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0x0fff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3116_16_4] = {
+ .name = "apci3116-16-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3116_8_4] = {
+ .name = "apci3116-8-4",
+ .ai_subdev_flags = SDF_COMMON | SDF_GROUND | SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_ao = 1,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ .has_ttl_io = 1,
+ },
+ [BOARD_APCI3003] = {
+ .name = "apci3003",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US |
+ CONV_UNIT_NS,
+ .ai_min_acq_ns = 2500,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_16] = {
+ .name = "apci3002-16",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 16,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_8] = {
+ .name = "apci3002-8",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 8,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3002_4] = {
+ .name = "apci3002-4",
+ .ai_subdev_flags = SDF_DIFF,
+ .ai_n_chan = 4,
+ .ai_maxdata = 0xffff,
+ .ai_conv_units = CONV_UNIT_MS | CONV_UNIT_US,
+ .ai_min_acq_ns = 5000,
+ .has_dig_in = 1,
+ .has_dig_out = 1,
+ },
+ [BOARD_APCI3500] = {
+ .name = "apci3500",
+ .has_ao = 1,
+ .has_ttl_io = 1,
+ },
+};
+
+struct apci3xxx_private {
+ void __iomem *mmio;
+ unsigned int ai_timer;
+ unsigned char ai_time_base;
+};
+
+static irqreturn_t apci3xxx_irq_handler(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct apci3xxx_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+ unsigned int val;
+
+ /* Test if interrupt occur */
+ status = readl(devpriv->mmio + 16);
+ if ((status & 0x2) == 0x2) {
+ /* Reset the interrupt */
+ writel(status, devpriv->mmio + 16);
+
+ val = readl(devpriv->mmio + 28);
+ comedi_buf_put(s, val);
+
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int apci3xxx_ai_started(struct comedi_device *dev)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+
+ if ((readl(devpriv->mmio + 8) & 0x80000) == 0x80000)
+ return 1;
+ else
+ return 0;
+
+}
+
+static int apci3xxx_ai_setup(struct comedi_device *dev, unsigned int chanspec)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int delay_mode;
+ unsigned int val;
+
+ if (apci3xxx_ai_started(dev))
+ return -EBUSY;
+
+ /* Clear the FIFO */
+ writel(0x10000, devpriv->mmio + 12);
+
+ /* Get and save the delay mode */
+ delay_mode = readl(devpriv->mmio + 4);
+ delay_mode &= 0xfffffef0;
+
+ /* Channel configuration selection */
+ writel(delay_mode, devpriv->mmio + 4);
+
+ /* Make the configuration */
+ val = (range & 3) | ((range >> 2) << 6) |
+ ((aref == AREF_DIFF) << 7);
+ writel(val, devpriv->mmio + 0);
+
+ /* Channel selection */
+ writel(delay_mode | 0x100, devpriv->mmio + 4);
+ writel(chan, devpriv->mmio + 0);
+
+ /* Restore delay mode */
+ writel(delay_mode, devpriv->mmio + 4);
+
+ /* Set the number of sequence to 1 */
+ writel(1, devpriv->mmio + 48);
+
+ return 0;
+}
+
+static int apci3xxx_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readl(devpriv->mmio + 20);
+ if (status & 0x1)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3xxx_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ int ret;
+ int i;
+
+ ret = apci3xxx_ai_setup(dev, insn->chanspec);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Start the conversion */
+ writel(0x80000, devpriv->mmio + 8);
+
+ /* Wait the EOS */
+ ret = comedi_timeout(dev, s, insn, apci3xxx_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Read the analog value */
+ data[i] = readl(devpriv->mmio + 28);
+ }
+
+ return insn->n;
+}
+
+static int apci3xxx_ai_ns_to_timer(struct comedi_device *dev,
+ unsigned int *ns, int round_mode)
+{
+ const struct apci3xxx_boardinfo *board = comedi_board(dev);
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int base;
+ unsigned int timer;
+ int time_base;
+
+ /* time_base: 0 = ns, 1 = us, 2 = ms */
+ for (time_base = 0; time_base < 3; time_base++) {
+ /* skip unsupported time bases */
+ if (!(board->ai_conv_units & (1 << time_base)))
+ continue;
+
+ switch (time_base) {
+ case 0:
+ base = 1;
+ break;
+ case 1:
+ base = 1000;
+ break;
+ case 2:
+ base = 1000000;
+ break;
+ }
+
+ switch (round_mode) {
+ case TRIG_ROUND_NEAREST:
+ default:
+ timer = (*ns + base / 2) / base;
+ break;
+ case TRIG_ROUND_DOWN:
+ timer = *ns / base;
+ break;
+ case TRIG_ROUND_UP:
+ timer = (*ns + base - 1) / base;
+ break;
+ }
+
+ if (timer < 0x10000) {
+ devpriv->ai_time_base = time_base;
+ devpriv->ai_timer = timer;
+ *ns = timer * time_base;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int apci3xxx_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct apci3xxx_boardinfo *board = comedi_board(dev);
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_min_acq_ns);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->convert_arg;
+ err |= apci3xxx_ai_ns_to_timer(dev, &arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int apci3xxx_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ ret = apci3xxx_ai_setup(dev, cmd->chanlist[0]);
+ if (ret)
+ return ret;
+
+ /* Set the convert timing unit */
+ writel(devpriv->ai_time_base, devpriv->mmio + 36);
+
+ /* Set the convert timing */
+ writel(devpriv->ai_timer, devpriv->mmio + 32);
+
+ /* Start the conversion */
+ writel(0x180000, devpriv->mmio + 8);
+
+ return 0;
+}
+
+static int apci3xxx_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return 0;
+}
+
+static int apci3xxx_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readl(devpriv->mmio + 96);
+ if (status & 0x100)
+ return 0;
+ return -EBUSY;
+}
+
+static int apci3xxx_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Set the range selection */
+ writel(range, devpriv->mmio + 96);
+
+ /* Write the analog value to the selected channel */
+ writel((data[i] << 8) | chan, devpriv->mmio + 100);
+
+ /* Wait the end of transfer */
+ ret = comedi_timeout(dev, s, insn, apci3xxx_ao_eoc, 0);
+ if (ret)
+ return ret;
+ }
+
+ return insn->n;
+}
+
+static int apci3xxx_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inl(dev->iobase + 32) & 0xf;
+
+ return insn->n;
+}
+
+static int apci3xxx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ s->state = inl(dev->iobase + 48) & 0xf;
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + 48);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int apci3xxx_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask = 0;
+ int ret;
+
+ /*
+ * Port 0 (channels 0-7) are always inputs
+ * Port 1 (channels 8-15) are always outputs
+ * Port 2 (channels 16-23) are programmable i/o
+ */
+ if (data[0] != INSN_CONFIG_DIO_QUERY) {
+ /* ignore all other instructions for ports 0 and 1 */
+ if (chan < 16)
+ return -EINVAL;
+ else
+ /* changing any channel in port 2 */
+ /* changes the entire port */
+ mask = 0xff0000;
+ }
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ /* update port 2 configuration */
+ outl((s->io_bits >> 24) & 0xff, dev->iobase + 224);
+
+ return insn->n;
+}
+
+static int apci3xxx_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ outl(s->state & 0xff, dev->iobase + 80);
+ if (mask & 0xff0000)
+ outl((s->state >> 16) & 0xff, dev->iobase + 112);
+ }
+
+ val = inl(dev->iobase + 80);
+ val |= (inl(dev->iobase + 64) << 8);
+ if (s->io_bits & 0xff0000)
+ val |= (inl(dev->iobase + 112) << 16);
+ else
+ val |= (inl(dev->iobase + 96) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int apci3xxx_reset(struct comedi_device *dev)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+ unsigned int val;
+ int i;
+
+ /* Disable the interrupt */
+ disable_irq(dev->irq);
+
+ /* Clear the start command */
+ writel(0, devpriv->mmio + 8);
+
+ /* Reset the interrupt flags */
+ val = readl(devpriv->mmio + 16);
+ writel(val, devpriv->mmio + 16);
+
+ /* clear the EOS */
+ readl(devpriv->mmio + 20);
+
+ /* Clear the FIFO */
+ for (i = 0; i < 16; i++)
+ val = readl(devpriv->mmio + 28);
+
+ /* Enable the interrupt */
+ enable_irq(dev->irq);
+
+ return 0;
+}
+
+static int apci3xxx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct apci3xxx_boardinfo *board = NULL;
+ struct apci3xxx_private *devpriv;
+ struct comedi_subdevice *s;
+ int n_subdevices;
+ int subdev;
+ int ret;
+
+ if (context < ARRAY_SIZE(apci3xxx_boardtypes))
+ board = &apci3xxx_boardtypes[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 2);
+ devpriv->mmio = pci_ioremap_bar(pcidev, 3);
+
+ if (pcidev->irq > 0) {
+ ret = request_irq(pcidev->irq, apci3xxx_irq_handler,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ n_subdevices = (board->ai_n_chan ? 0 : 1) + board->has_ao +
+ board->has_dig_in + board->has_dig_out +
+ board->has_ttl_io;
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Input subdevice */
+ if (board->ai_n_chan) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | board->ai_subdev_flags;
+ s->n_chan = board->ai_n_chan;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = &apci3xxx_ai_range;
+ s->insn_read = apci3xxx_ai_insn_read;
+ if (dev->irq) {
+ /*
+ * FIXME: The hardware supports multiple scan modes
+ * but the original addi-data driver only supported
+ * reading a single channel with interrupts. Need a
+ * proper datasheet to fix this.
+ *
+ * The following scan modes are supported by the
+ * hardware:
+ * 1) Single software scan
+ * 2) Single hardware triggered scan
+ * 3) Continuous software scan
+ * 4) Continuous software scan with timer delay
+ * 5) Continuous hardware triggered scan
+ * 6) Continuous hardware triggered scan with timer
+ * delay
+ *
+ * For now, limit the chanlist to a single channel.
+ */
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmdtest = apci3xxx_ai_cmdtest;
+ s->do_cmd = apci3xxx_ai_cmd;
+ s->cancel = apci3xxx_ai_cancel;
+ }
+
+ subdev++;
+ }
+
+ /* Analog Output subdevice */
+ if (board->has_ao) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &apci3xxx_ao_range;
+ s->insn_write = apci3xxx_ao_insn_write;
+
+ subdev++;
+ }
+
+ /* Digital Input subdevice */
+ if (board->has_dig_in) {
+ s = &dev->subdevices[subdev];
+ 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 = apci3xxx_di_insn_bits;
+
+ subdev++;
+ }
+
+ /* Digital Output subdevice */
+ if (board->has_dig_out) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 4;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = apci3xxx_do_insn_bits;
+
+ subdev++;
+ }
+
+ /* TTL Digital I/O subdevice */
+ if (board->has_ttl_io) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITEABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->io_bits = 0xff; /* channels 0-7 are always outputs */
+ s->range_table = &range_digital;
+ s->insn_config = apci3xxx_dio_insn_config;
+ s->insn_bits = apci3xxx_dio_insn_bits;
+
+ subdev++;
+ }
+
+ apci3xxx_reset(dev);
+ return 0;
+}
+
+static void apci3xxx_detach(struct comedi_device *dev)
+{
+ struct apci3xxx_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ apci3xxx_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv->mmio)
+ iounmap(devpriv->mmio);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver apci3xxx_driver = {
+ .driver_name = "addi_apci_3xxx",
+ .module = THIS_MODULE,
+ .auto_attach = apci3xxx_auto_attach,
+ .detach = apci3xxx_detach,
+};
+
+static int apci3xxx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &apci3xxx_driver, id->driver_data);
+}
+
+static const struct pci_device_id apci3xxx_pci_table[] = {
+ { PCI_VDEVICE(ADDIDATA, 0x3010), BOARD_APCI3000_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x300f), BOARD_APCI3000_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x300e), BOARD_APCI3000_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3013), BOARD_APCI3006_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3014), BOARD_APCI3006_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3015), BOARD_APCI3006_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3016), BOARD_APCI3010_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3017), BOARD_APCI3010_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3018), BOARD_APCI3010_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3019), BOARD_APCI3016_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x301a), BOARD_APCI3016_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x301b), BOARD_APCI3016_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301c), BOARD_APCI3100_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301d), BOARD_APCI3100_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301e), BOARD_APCI3106_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x301f), BOARD_APCI3106_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3020), BOARD_APCI3110_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3021), BOARD_APCI3110_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3022), BOARD_APCI3116_16_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3023), BOARD_APCI3116_8_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x300B), BOARD_APCI3003 },
+ { PCI_VDEVICE(ADDIDATA, 0x3002), BOARD_APCI3002_16 },
+ { PCI_VDEVICE(ADDIDATA, 0x3003), BOARD_APCI3002_8 },
+ { PCI_VDEVICE(ADDIDATA, 0x3004), BOARD_APCI3002_4 },
+ { PCI_VDEVICE(ADDIDATA, 0x3024), BOARD_APCI3500 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, apci3xxx_pci_table);
+
+static struct pci_driver apci3xxx_pci_driver = {
+ .name = "addi_apci_3xxx",
+ .id_table = apci3xxx_pci_table,
+ .probe = apci3xxx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(apci3xxx_driver, apci3xxx_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_watchdog.c b/drivers/staging/comedi/drivers/addi_watchdog.c
new file mode 100644
index 00000000000..23031feaa09
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_watchdog.c
@@ -0,0 +1,161 @@
+/*
+ * COMEDI driver for the watchdog subdevice found on some addi-data boards
+ * Copyright (c) 2013 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on implementations in various addi-data COMEDI drivers.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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 <linux/module.h>
+#include "../comedidev.h"
+#include "addi_watchdog.h"
+
+/*
+ * Register offsets/defines for the addi-data watchdog
+ */
+#define ADDI_WDOG_REG 0x00
+#define ADDI_WDOG_RELOAD_REG 0x04
+#define ADDI_WDOG_TIMEBASE 0x08
+#define ADDI_WDOG_CTRL_REG 0x0c
+#define ADDI_WDOG_CTRL_ENABLE (1 << 0)
+#define ADDI_WDOG_CTRL_SW_TRIG (1 << 9)
+#define ADDI_WDOG_STATUS_REG 0x10
+#define ADDI_WDOG_STATUS_ENABLED (1 << 0)
+#define ADDI_WDOG_STATUS_SW_TRIG (1 << 1)
+
+struct addi_watchdog_private {
+ unsigned long iobase;
+ unsigned int wdog_ctrl;
+};
+
+/*
+ * The watchdog subdevice is configured with two INSN_CONFIG instructions:
+ *
+ * Enable the watchdog and set the reload timeout:
+ * data[0] = INSN_CONFIG_ARM
+ * data[1] = timeout reload value
+ *
+ * Disable the watchdog:
+ * data[0] = INSN_CONFIG_DISARM
+ */
+static int addi_watchdog_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ unsigned int reload;
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ spriv->wdog_ctrl = ADDI_WDOG_CTRL_ENABLE;
+ reload = data[1] & s->maxdata;
+ outl(reload, spriv->iobase + ADDI_WDOG_RELOAD_REG);
+
+ /* Time base is 20ms, let the user know the timeout */
+ dev_info(dev->class_dev, "watchdog enabled, timeout:%dms\n",
+ 20 * reload + 20);
+ break;
+ case INSN_CONFIG_DISARM:
+ spriv->wdog_ctrl = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ outl(spriv->wdog_ctrl, spriv->iobase + ADDI_WDOG_CTRL_REG);
+
+ return insn->n;
+}
+
+static int addi_watchdog_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = inl(spriv->iobase + ADDI_WDOG_STATUS_REG);
+
+ return insn->n;
+}
+
+static int addi_watchdog_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct addi_watchdog_private *spriv = s->private;
+ int i;
+
+ if (spriv->wdog_ctrl == 0) {
+ dev_warn(dev->class_dev, "watchdog is disabled\n");
+ return -EINVAL;
+ }
+
+ /* "ping" the watchdog */
+ for (i = 0; i < insn->n; i++) {
+ outl(spriv->wdog_ctrl | ADDI_WDOG_CTRL_SW_TRIG,
+ spriv->iobase + ADDI_WDOG_CTRL_REG);
+ }
+
+ return insn->n;
+}
+
+void addi_watchdog_reset(unsigned long iobase)
+{
+ outl(0x0, iobase + ADDI_WDOG_CTRL_REG);
+ outl(0x0, iobase + ADDI_WDOG_RELOAD_REG);
+}
+EXPORT_SYMBOL_GPL(addi_watchdog_reset);
+
+int addi_watchdog_init(struct comedi_subdevice *s, unsigned long iobase)
+{
+ struct addi_watchdog_private *spriv;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+
+ spriv->iobase = iobase;
+
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 1;
+ s->maxdata = 0xff;
+ s->insn_config = addi_watchdog_insn_config;
+ s->insn_read = addi_watchdog_insn_read;
+ s->insn_write = addi_watchdog_insn_write;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(addi_watchdog_init);
+
+static int __init addi_watchdog_module_init(void)
+{
+ return 0;
+}
+module_init(addi_watchdog_module_init);
+
+static void __exit addi_watchdog_module_exit(void)
+{
+}
+module_exit(addi_watchdog_module_exit);
+
+MODULE_DESCRIPTION("ADDI-DATA Watchdog subdevice");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/addi_watchdog.h b/drivers/staging/comedi/drivers/addi_watchdog.h
new file mode 100644
index 00000000000..83b47befa4d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/addi_watchdog.h
@@ -0,0 +1,9 @@
+#ifndef _ADDI_WATCHDOG_H
+#define _ADDI_WATCHDOG_H
+
+#include "../comedidev.h"
+
+void addi_watchdog_reset(unsigned long iobase);
+int addi_watchdog_init(struct comedi_subdevice *, unsigned long iobase);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/adl_pci6208.c b/drivers/staging/comedi/drivers/adl_pci6208.c
new file mode 100644
index 00000000000..921f6942dfc
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci6208.c
@@ -0,0 +1,260 @@
+/*
+ * adl_pci6208.c
+ * Comedi driver for ADLink 6208 series cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: adl_pci6208
+ * Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards
+ * Devices: (ADLink) PCI-6208 [adl_pci6208]
+ * (ADLink) PCI-6216 [adl_pci6216]
+ * Author: nsyeow <nsyeow@pd.jaring.my>
+ * Updated: Fri, 30 Jan 2004 14:44:27 +0800
+ * Status: untested
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+/*
+ * PCI-6208/6216-GL register map
+ */
+#define PCI6208_AO_CONTROL(x) (0x00 + (2 * (x)))
+#define PCI6208_AO_STATUS 0x00
+#define PCI6208_AO_STATUS_DATA_SEND (1 << 0)
+#define PCI6208_DIO 0x40
+#define PCI6208_DIO_DO_MASK (0x0f)
+#define PCI6208_DIO_DO_SHIFT (0)
+#define PCI6208_DIO_DI_MASK (0xf0)
+#define PCI6208_DIO_DI_SHIFT (4)
+
+#define PCI6208_MAX_AO_CHANNELS 16
+
+enum pci6208_boardid {
+ BOARD_PCI6208,
+ BOARD_PCI6216,
+};
+
+struct pci6208_board {
+ const char *name;
+ int ao_chans;
+};
+
+static const struct pci6208_board pci6208_boards[] = {
+ [BOARD_PCI6208] = {
+ .name = "adl_pci6208",
+ .ao_chans = 8,
+ },
+ [BOARD_PCI6216] = {
+ .name = "adl_pci6216",
+ .ao_chans = 16,
+ },
+};
+
+struct pci6208_private {
+ unsigned int ao_readback[PCI6208_MAX_AO_CHANNELS];
+};
+
+static int pci6208_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + PCI6208_AO_STATUS);
+ if ((status & PCI6208_AO_STATUS_DATA_SEND) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci6208_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci6208_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* D/A transfer rate is 2.2us */
+ ret = comedi_timeout(dev, s, insn, pci6208_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* the hardware expects two's complement values */
+ outw(comedi_offset_munge(s, val),
+ dev->iobase + PCI6208_AO_CONTROL(chan));
+ }
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pci6208_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci6208_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pci6208_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int val;
+
+ val = inw(dev->iobase + PCI6208_DIO);
+ val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pci6208_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI6208_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci6208_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pci6208_board *boardinfo = NULL;
+ struct pci6208_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int val;
+ int ret;
+
+ if (context < ARRAY_SIZE(pci6208_boards))
+ boardinfo = &pci6208_boards[context];
+ if (!boardinfo)
+ return -ENODEV;
+ dev->board_ptr = boardinfo;
+ dev->board_name = boardinfo->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = boardinfo->ao_chans;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci6208_ao_insn_write;
+ s->insn_read = pci6208_ao_insn_read;
+
+ s = &dev->subdevices[1];
+ /* digital input subdevice */
+ 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 = pci6208_di_insn_bits;
+
+ s = &dev->subdevices[2];
+ /* digital output subdevice */
+ 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 = pci6208_do_insn_bits;
+
+ /*
+ * Get the read back signals from the digital outputs
+ * and save it as the initial state for the subdevice.
+ */
+ val = inw(dev->iobase + PCI6208_DIO);
+ val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT;
+ s->state = val;
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci6208_driver = {
+ .driver_name = "adl_pci6208",
+ .module = THIS_MODULE,
+ .auto_attach = pci6208_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int adl_pci6208_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci6208_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci6208_pci_table[] = {
+ { PCI_VDEVICE(ADLINK, 0x6208), BOARD_PCI6208 },
+ { PCI_VDEVICE(ADLINK, 0x6216), BOARD_PCI6216 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table);
+
+static struct pci_driver adl_pci6208_pci_driver = {
+ .name = "adl_pci6208",
+ .id_table = adl_pci6208_pci_table,
+ .probe = adl_pci6208_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for ADLink 6208 series cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adl_pci7x3x.c b/drivers/staging/comedi/drivers/adl_pci7x3x.c
new file mode 100644
index 00000000000..5e3cc77a8a0
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci7x3x.c
@@ -0,0 +1,280 @@
+/*
+ * COMEDI driver for the ADLINK PCI-723x/743x series boards.
+ * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the adl_pci7230 driver written by:
+ * David Fernandez <dfcastelao@gmail.com>
+ * and the adl_pci7432 driver written by:
+ * Michel Lachaine <mike@mikelachaine.ca>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+Driver: adl_pci7x3x
+Description: 32/64-Channel Isolated Digital I/O Boards
+Devices: (ADLink) PCI-7230 [adl_pci7230] - 16 input / 16 output
+ (ADLink) PCI-7233 [adl_pci7233] - 32 input
+ (ADLink) PCI-7234 [adl_pci7234] - 32 output
+ (ADLink) PCI-7432 [adl_pci7432] - 32 input / 32 output
+ (ADLink) PCI-7433 [adl_pci7433] - 64 input
+ (ADLink) PCI-7434 [adl_pci7434] - 64 output
+Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+Updated: Thu, 02 Aug 2012 14:27:46 -0700
+Status: untested
+
+The PCI-7230, PCI-7432 and PCI-7433 boards also support external
+interrupt signals on digital input channels 0 and 1. The PCI-7233
+has dual-interrupt sources for change-of-state (COS) on any 16
+digital input channels of LSB and for COS on any 16 digital input
+lines of MSB. Interrupts are not currently supported by this
+driver.
+
+Configuration Options: not applicable, uses comedi PCI auto config
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map (32-bit access only)
+ */
+#define PCI7X3X_DIO_REG 0x00
+#define PCI743X_DIO_REG 0x04
+
+enum apci1516_boardid {
+ BOARD_PCI7230,
+ BOARD_PCI7233,
+ BOARD_PCI7234,
+ BOARD_PCI7432,
+ BOARD_PCI7433,
+ BOARD_PCI7434,
+};
+
+struct adl_pci7x3x_boardinfo {
+ const char *name;
+ int nsubdevs;
+ int di_nchan;
+ int do_nchan;
+};
+
+static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
+ [BOARD_PCI7230] = {
+ .name = "adl_pci7230",
+ .nsubdevs = 2,
+ .di_nchan = 16,
+ .do_nchan = 16,
+ },
+ [BOARD_PCI7233] = {
+ .name = "adl_pci7233",
+ .nsubdevs = 1,
+ .di_nchan = 32,
+ },
+ [BOARD_PCI7234] = {
+ .name = "adl_pci7234",
+ .nsubdevs = 1,
+ .do_nchan = 32,
+ },
+ [BOARD_PCI7432] = {
+ .name = "adl_pci7432",
+ .nsubdevs = 2,
+ .di_nchan = 32,
+ .do_nchan = 32,
+ },
+ [BOARD_PCI7433] = {
+ .name = "adl_pci7433",
+ .nsubdevs = 2,
+ .di_nchan = 64,
+ },
+ [BOARD_PCI7434] = {
+ .name = "adl_pci7434",
+ .nsubdevs = 2,
+ .do_nchan = 64,
+ }
+};
+
+static int adl_pci7x3x_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+
+ if (comedi_dio_update_state(s, data))
+ outl(s->state, dev->iobase + reg);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int adl_pci7x3x_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+
+ data[1] = inl(dev->iobase + reg);
+
+ return insn->n;
+}
+
+static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct adl_pci7x3x_boardinfo *board = NULL;
+ struct comedi_subdevice *s;
+ int subdev;
+ int nchan;
+ int ret;
+
+ if (context < ARRAY_SIZE(adl_pci7x3x_boards))
+ board = &adl_pci7x3x_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ /*
+ * One or two subdevices are setup by this driver depending on
+ * the number of digital inputs and/or outputs provided by the
+ * board. Each subdevice has a maximum of 32 channels.
+ *
+ * PCI-7230 - 2 subdevices: 0 - 16 input, 1 - 16 output
+ * PCI-7233 - 1 subdevice: 0 - 32 input
+ * PCI-7234 - 1 subdevice: 0 - 32 output
+ * PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output
+ * PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input
+ * PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output
+ */
+ ret = comedi_alloc_subdevices(dev, board->nsubdevs);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ if (board->di_nchan) {
+ nchan = min(board->di_nchan, 32);
+
+ s = &dev->subdevices[subdev];
+ /* Isolated digital inputs 0 to 15/31 */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_di_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI7X3X_DIO_REG;
+
+ subdev++;
+
+ nchan = board->di_nchan - nchan;
+ if (nchan) {
+ s = &dev->subdevices[subdev];
+ /* Isolated digital inputs 32 to 63 */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_di_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI743X_DIO_REG;
+
+ subdev++;
+ }
+ }
+
+ if (board->do_nchan) {
+ nchan = min(board->do_nchan, 32);
+
+ s = &dev->subdevices[subdev];
+ /* Isolated digital outputs 0 to 15/31 */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_do_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI7X3X_DIO_REG;
+
+ subdev++;
+
+ nchan = board->do_nchan - nchan;
+ if (nchan) {
+ s = &dev->subdevices[subdev];
+ /* Isolated digital outputs 32 to 63 */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = nchan;
+ s->maxdata = 1;
+ s->insn_bits = adl_pci7x3x_do_insn_bits;
+ s->range_table = &range_digital;
+
+ s->private = (void *)PCI743X_DIO_REG;
+
+ subdev++;
+ }
+ }
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci7x3x_driver = {
+ .driver_name = "adl_pci7x3x",
+ .module = THIS_MODULE,
+ .auto_attach = adl_pci7x3x_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci7x3x_pci_table[] = {
+ { PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 },
+ { PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 },
+ { PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 },
+ { PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 },
+ { PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 },
+ { PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table);
+
+static struct pci_driver adl_pci7x3x_pci_driver = {
+ .name = "adl_pci7x3x",
+ .id_table = adl_pci7x3x_pci_table,
+ .probe = adl_pci7x3x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver);
+
+MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+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 00000000000..300df55a280
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci8164.c
@@ -0,0 +1,164 @@
+/*
+ * comedi/drivers/adl_pci8164.c
+ *
+ * Hardware comedi driver for PCI-8164 Adlink card
+ * Copyright (C) 2004 Michel Lachine <mike@mikelachaine.ca>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: adl_pci8164
+ * Description: Driver for the Adlink PCI-8164 4 Axes Motion Control board
+ * Devices: (ADLink) PCI-8164 [adl_pci8164]
+ * Author: Michel Lachaine <mike@mikelachaine.ca>
+ * Status: experimental
+ * Updated: Mon, 14 Apr 2008 15:10:32 +0100
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#define PCI8164_AXIS(x) ((x) * 0x08)
+#define PCI8164_CMD_MSTS_REG 0x00
+#define PCI8164_OTP_SSTS_REG 0x02
+#define PCI8164_BUF0_REG 0x04
+#define PCI8164_BUF1_REG 0x06
+
+static int adl_pci8164_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long offset = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = inw(dev->iobase + PCI8164_AXIS(chan) + offset);
+
+ return insn->n;
+}
+
+static int adl_pci8164_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long offset = (unsigned long)s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ outw(data[i], dev->iobase + PCI8164_AXIS(chan) + offset);
+
+ return insn->n;
+}
+
+static int adl_pci8164_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* read MSTS register / write CMD register for each axis (channel) */
+ 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->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_CMD_MSTS_REG;
+
+ /* read SSTS register / write OTP register for each axis (channel) */
+ 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->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_OTP_SSTS_REG;
+
+ /* read/write BUF0 register for each axis (channel) */
+ 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->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_BUF0_REG;
+
+ /* read/write BUF1 register for each axis (channel) */
+ 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->insn_read = adl_pci8164_insn_read;
+ s->insn_write = adl_pci8164_insn_write;
+ s->private = (void *)PCI8164_BUF1_REG;
+
+ return 0;
+}
+
+static struct comedi_driver adl_pci8164_driver = {
+ .driver_name = "adl_pci8164",
+ .module = THIS_MODULE,
+ .auto_attach = adl_pci8164_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int adl_pci8164_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci8164_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci8164_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x8164) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci8164_pci_table);
+
+static struct pci_driver adl_pci8164_pci_driver = {
+ .name = "adl_pci8164",
+ .id_table = adl_pci8164_pci_table,
+ .probe = adl_pci8164_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci8164_driver, adl_pci8164_pci_driver);
+
+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 00000000000..584fd57ecb7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci9111.c
@@ -0,0 +1,903 @@
+/*
+
+comedi/drivers/adl_pci9111.c
+
+Hardware driver for PCI9111 ADLink cards:
+
+PCI-9111HR
+
+Copyright (C) 2002-2005 Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr>
+
+This program is free software; you can redistribute 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.
+*/
+
+/*
+Driver: adl_pci9111
+Description: Adlink PCI-9111HR
+Author: Emmanuel Pacaud <emmanuel.pacaud@univ-poitiers.fr>
+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: not applicable, uses PCI auto config
+*/
+
+/*
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+#include "plx9052.h"
+#include "comedi_fc.h"
+
+#define PCI9111_DRIVER_NAME "adl_pci9111"
+#define PCI9111_HR_DEVICE_ID 0x9111
+
+#define PCI9111_FIFO_HALF_SIZE 512
+
+#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000
+
+#define PCI9111_RANGE_SETTING_DELAY 10
+#define PCI9111_AI_INSTANT_READ_UDELAY_US 2
+
+/*
+ * IO address map and bit defines
+ */
+#define PCI9111_AI_FIFO_REG 0x00
+#define PCI9111_AO_REG 0x00
+#define PCI9111_DIO_REG 0x02
+#define PCI9111_EDIO_REG 0x04
+#define PCI9111_AI_CHANNEL_REG 0x06
+#define PCI9111_AI_RANGE_STAT_REG 0x08
+#define PCI9111_AI_STAT_AD_BUSY (1 << 7)
+#define PCI9111_AI_STAT_FF_FF (1 << 6)
+#define PCI9111_AI_STAT_FF_HF (1 << 5)
+#define PCI9111_AI_STAT_FF_EF (1 << 4)
+#define PCI9111_AI_RANGE_MASK (7 << 0)
+#define PCI9111_AI_TRIG_CTRL_REG 0x0a
+#define PCI9111_AI_TRIG_CTRL_TRGEVENT (1 << 5)
+#define PCI9111_AI_TRIG_CTRL_POTRG (1 << 4)
+#define PCI9111_AI_TRIG_CTRL_PTRG (1 << 3)
+#define PCI9111_AI_TRIG_CTRL_ETIS (1 << 2)
+#define PCI9111_AI_TRIG_CTRL_TPST (1 << 1)
+#define PCI9111_AI_TRIG_CTRL_ASCAN (1 << 0)
+#define PCI9111_INT_CTRL_REG 0x0c
+#define PCI9111_INT_CTRL_ISC2 (1 << 3)
+#define PCI9111_INT_CTRL_FFEN (1 << 2)
+#define PCI9111_INT_CTRL_ISC1 (1 << 1)
+#define PCI9111_INT_CTRL_ISC0 (1 << 0)
+#define PCI9111_SOFT_TRIG_REG 0x0e
+#define PCI9111_8254_BASE_REG 0x40
+#define PCI9111_INT_CLR_REG 0x48
+
+/* PLX 9052 Local Interrupt 1 enabled and active */
+#define PCI9111_LI1_ACTIVE (PLX9052_INTCSR_LI1ENAB | \
+ PLX9052_INTCSR_LI1STAT)
+
+/* PLX 9052 Local Interrupt 2 enabled and active */
+#define PCI9111_LI2_ACTIVE (PLX9052_INTCSR_LI2ENAB | \
+ PLX9052_INTCSR_LI2STAT)
+
+static const struct comedi_lrange pci9111_ai_range = {
+ 5, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+struct pci9111_private_data {
+ unsigned long lcr_io_base;
+
+ int stop_counter;
+
+ unsigned int scan_delay;
+ unsigned int chunk_counter;
+ unsigned int chunk_num_samples;
+
+ int ao_readback;
+
+ unsigned int div1;
+ unsigned int div2;
+
+ unsigned short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE];
+};
+
+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 |= PLX9052_INTCSR_LI1ENAB;
+ if (LINTi1_active_high)
+ flags |= PLX9052_INTCSR_LI1POL;
+ if (LINTi2_enable)
+ flags |= PLX9052_INTCSR_LI2ENAB;
+ if (LINTi2_active_high)
+ flags |= PLX9052_INTCSR_LI2POL;
+
+ if (interrupt_enable)
+ flags |= PLX9052_INTCSR_PCIENAB;
+
+ outb(flags, io_base + PLX9052_INTCSR);
+}
+
+static void pci9111_timer_set(struct comedi_device *dev)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ unsigned long timer_base = dev->iobase + PCI9111_8254_BASE_REG;
+
+ i8254_set_mode(timer_base, 1, 0, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY);
+
+ udelay(1);
+
+ i8254_write(timer_base, 1, 2, dev_private->div2);
+ i8254_write(timer_base, 1, 1, dev_private->div1);
+}
+
+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;
+
+ /* Read the current trigger mode control bits */
+ flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+ /* Mask off the EITS and TPST bits */
+ flags &= 0x9;
+
+ switch (source) {
+ case software:
+ break;
+
+ case timer_pacer:
+ flags |= PCI9111_AI_TRIG_CTRL_TPST;
+ break;
+
+ case external:
+ flags |= PCI9111_AI_TRIG_CTRL_ETIS;
+ break;
+ }
+
+ outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+}
+
+static void pci9111_pretrigger_set(struct comedi_device *dev, bool pretrigger)
+{
+ int flags;
+
+ /* Read the current trigger mode control bits */
+ flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+ /* Mask off the PTRG bit */
+ flags &= 0x7;
+
+ if (pretrigger)
+ flags |= PCI9111_AI_TRIG_CTRL_PTRG;
+
+ outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+}
+
+static void pci9111_autoscan_set(struct comedi_device *dev, bool autoscan)
+{
+ int flags;
+
+ /* Read the current trigger mode control bits */
+ flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+ /* Mask off the ASCAN bit */
+ flags &= 0xe;
+
+ if (autoscan)
+ flags |= PCI9111_AI_TRIG_CTRL_ASCAN;
+
+ outb(flags, dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+}
+
+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;
+
+ /* Read the current interrupt control bits */
+ flags = inb(dev->iobase + PCI9111_AI_TRIG_CTRL_REG);
+ /* Shift the bits so they are compatible with the write register */
+ flags >>= 4;
+ /* Mask off the ISCx bits */
+ flags &= 0xc0;
+
+ /* Now set the new ISCx bits */
+ if (irq_0_source == irq_on_fifo_half_full)
+ flags |= PCI9111_INT_CTRL_ISC0;
+
+ if (irq_1_source == irq_on_external_trigger)
+ flags |= PCI9111_INT_CTRL_ISC1;
+
+ outb(flags, dev->iobase + PCI9111_INT_CTRL_REG);
+}
+
+static void pci9111_fifo_reset(struct comedi_device *dev)
+{
+ unsigned long int_ctrl_reg = dev->iobase + PCI9111_INT_CTRL_REG;
+
+ /* To reset the FIFO, set FFEN sequence as 0 -> 1 -> 0 */
+ outb(0, int_ctrl_reg);
+ outb(PCI9111_INT_CTRL_FFEN, int_ctrl_reg);
+ outb(0, int_ctrl_reg);
+}
+
+static int pci9111_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+
+ /* 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(dev);
+
+ return 0;
+}
+
+static int pci9111_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels,counting upwards from 0\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int pci9111_ai_do_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src != TRIG_FOLLOW) {
+ if (cmd->scan_begin_src != cmd->convert_src)
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ PCI9111_AI_ACQUISITION_PERIOD_MIN_NS);
+ else /* TRIG_EXT */
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ PCI9111_AI_ACQUISITION_PERIOD_MIN_NS);
+ else /* TRIG_FOLLOW || TRIG_EXT */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+ &dev_private->div1,
+ &dev_private->div2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ /*
+ * 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) {
+ arg = cmd->chanlist_len * cmd->convert_arg;
+
+ if (arg < cmd->scan_begin_arg)
+ arg *= (cmd->scan_begin_arg / arg);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci9111_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+
+}
+
+static int pci9111_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ /* Set channel scan limit */
+ /* PCI9111 allows only scanning from channel 0 to channel n */
+ /* TODO: handle the case of an external multiplexer */
+
+ if (cmd->chanlist_len > 1) {
+ outb(cmd->chanlist_len - 1,
+ dev->iobase + PCI9111_AI_CHANNEL_REG);
+ pci9111_autoscan_set(dev, true);
+ } else {
+ outb(CR_CHAN(cmd->chanlist[0]),
+ dev->iobase + PCI9111_AI_CHANNEL_REG);
+ pci9111_autoscan_set(dev, false);
+ }
+
+ /* Set gain */
+ /* This is the same gain on every channel */
+
+ outb(CR_RANGE(cmd->chanlist[0]) & PCI9111_AI_RANGE_MASK,
+ dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+
+ /* Set counter */
+ if (cmd->stop_src == TRIG_COUNT)
+ dev_private->stop_counter = cmd->stop_arg * cmd->chanlist_len;
+ else /* TRIG_NONE */
+ dev_private->stop_counter = 0;
+
+ /* Set timer pacer */
+ dev_private->scan_delay = 0;
+ if (cmd->convert_src == TRIG_TIMER) {
+ pci9111_trigger_source_set(dev, software);
+ pci9111_timer_set(dev);
+ pci9111_fifo_reset(dev);
+ 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 (cmd->scan_begin_src == TRIG_TIMER) {
+ dev_private->scan_delay = (cmd->scan_begin_arg /
+ (cmd->convert_arg * cmd->chanlist_len)) - 1;
+ }
+ } else { /* TRIG_EXT */
+ pci9111_trigger_source_set(dev, external);
+ pci9111_fifo_reset(dev);
+ 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);
+
+ }
+
+ dev_private->stop_counter *= (1 + dev_private->scan_delay);
+ dev_private->chunk_counter = 0;
+ dev_private->chunk_num_samples = cmd->chanlist_len *
+ (1 + dev_private->scan_delay);
+
+ 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 short *array = data;
+ unsigned int maxdata = s->maxdata;
+ unsigned int invert = (maxdata + 1) >> 1;
+ unsigned int shift = (maxdata == 0xffff) ? 0 : 4;
+ unsigned int num_samples = num_bytes / sizeof(short);
+ unsigned int i;
+
+ for (i = 0; i < num_samples; i++)
+ array[i] = ((array[i] >> shift) & maxdata) ^ invert;
+}
+
+static void pci9111_handle_fifo_half_full(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9111_private_data *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int total = 0;
+ unsigned int samples;
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ PCI9111_FIFO_HALF_SIZE > devpriv->stop_counter)
+ samples = devpriv->stop_counter;
+ else
+ samples = PCI9111_FIFO_HALF_SIZE;
+
+ insw(dev->iobase + PCI9111_AI_FIFO_REG,
+ devpriv->ai_bounce_buffer, samples);
+
+ if (devpriv->scan_delay < 1) {
+ total = cfc_write_array_to_buffer(s,
+ devpriv->ai_bounce_buffer,
+ samples * sizeof(short));
+ } else {
+ unsigned int pos = 0;
+ unsigned int to_read;
+
+ while (pos < samples) {
+ if (devpriv->chunk_counter < cmd->chanlist_len) {
+ to_read = cmd->chanlist_len -
+ devpriv->chunk_counter;
+
+ if (to_read > samples - pos)
+ to_read = samples - pos;
+
+ total += cfc_write_array_to_buffer(s,
+ devpriv->ai_bounce_buffer + pos,
+ to_read * sizeof(short));
+ } else {
+ to_read = devpriv->chunk_num_samples -
+ devpriv->chunk_counter;
+
+ if (to_read > samples - pos)
+ to_read = samples - pos;
+
+ total += to_read * sizeof(short);
+ }
+
+ pos += to_read;
+ devpriv->chunk_counter += to_read;
+
+ if (devpriv->chunk_counter >=
+ devpriv->chunk_num_samples)
+ devpriv->chunk_counter = 0;
+ }
+ }
+
+ devpriv->stop_counter -= total / sizeof(short);
+}
+
+static irqreturn_t pci9111_interrupt(int irq, void *p_device)
+{
+ struct comedi_device *dev = p_device;
+ struct pci9111_private_data *dev_private = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ unsigned int status;
+ 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 = s->async;
+ cmd = &async->cmd;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+
+ /* Check if we are source of interrupt */
+ intcsr = inb(dev_private->lcr_io_base + PLX9052_INTCSR);
+ if (!(((intcsr & PLX9052_INTCSR_PCIENAB) != 0) &&
+ (((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) ||
+ ((intcsr & PCI9111_LI2_ACTIVE) == PCI9111_LI2_ACTIVE)))) {
+ /* Not the source of the interrupt. */
+ /* (N.B. not using PLX9052_INTCSR_SOFTINT) */
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ return IRQ_NONE;
+ }
+
+ if ((intcsr & PCI9111_LI1_ACTIVE) == PCI9111_LI1_ACTIVE) {
+ /* Interrupt comes from fifo_half-full signal */
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+
+ /* '0' means FIFO is full, data may have been lost */
+ if (!(status & PCI9111_AI_STAT_FF_FF)) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ comedi_error(dev, PCI9111_DRIVER_NAME " fifo overflow");
+ outb(0, dev->iobase + PCI9111_INT_CLR_REG);
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+ }
+
+ /* '0' means FIFO is half-full */
+ if (!(status & PCI9111_AI_STAT_FF_HF))
+ pci9111_handle_fifo_half_full(dev, s);
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && dev_private->stop_counter == 0)
+ async->events |= COMEDI_CB_EOA;
+
+ outb(0, dev->iobase + PCI9111_INT_CLR_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ cfc_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int pci9111_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ if (status & PCI9111_AI_STAT_FF_EF)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci9111_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int maxdata = s->maxdata;
+ unsigned int invert = (maxdata + 1) >> 1;
+ unsigned int shift = (maxdata == 0xffff) ? 0 : 4;
+ unsigned int status;
+ int ret;
+ int i;
+
+ outb(chan, dev->iobase + PCI9111_AI_CHANNEL_REG);
+
+ status = inb(dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ if ((status & PCI9111_AI_RANGE_MASK) != range) {
+ outb(range & PCI9111_AI_RANGE_MASK,
+ dev->iobase + PCI9111_AI_RANGE_STAT_REG);
+ }
+
+ pci9111_fifo_reset(dev);
+
+ for (i = 0; i < insn->n; i++) {
+ /* Generate a software trigger */
+ outb(0, dev->iobase + PCI9111_SOFT_TRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, pci9111_ai_eoc, 0);
+ if (ret) {
+ pci9111_fifo_reset(dev);
+ return ret;
+ }
+
+ data[i] = inw(dev->iobase + PCI9111_AI_FIFO_REG);
+ data[i] = ((data[i] >> shift) & maxdata) ^ invert;
+ }
+
+ return i;
+}
+
+static int pci9111_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ unsigned int val = 0;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, dev->iobase + PCI9111_AO_REG);
+ }
+ dev_private->ao_readback = val;
+
+ return insn->n;
+}
+
+static int pci9111_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = dev_private->ao_readback;
+
+ return insn->n;
+}
+
+static int pci9111_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inw(dev->iobase + PCI9111_DIO_REG);
+
+ return insn->n;
+}
+
+static int pci9111_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI9111_DIO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci9111_reset(struct comedi_device *dev)
+{
+ struct pci9111_private_data *dev_private = dev->private;
+
+ /* 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->div1 = 0;
+ dev_private->div2 = 0;
+ pci9111_timer_set(dev);
+
+ return 0;
+}
+
+static int pci9111_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci9111_private_data *dev_private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private));
+ if (!dev_private)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev_private->lcr_io_base = pci_resource_start(pcidev, 1);
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ pci9111_reset(dev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, pci9111_interrupt,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON;
+ s->n_chan = 16;
+ s->maxdata = 0xffff;
+ s->range_table = &pci9111_ai_range;
+ s->insn_read = pci9111_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = pci9111_ai_do_cmd_test;
+ s->do_cmd = pci9111_ai_do_cmd;
+ s->cancel = pci9111_ai_cancel;
+ s->munge = pci9111_ai_munge;
+ }
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_COMMON;
+ s->n_chan = 1;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 1;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci9111_ao_insn_write;
+ s->insn_read = pci9111_ao_insn_read;
+
+ s = &dev->subdevices[2];
+ 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 = pci9111_di_insn_bits;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci9111_do_insn_bits;
+
+ return 0;
+}
+
+static void pci9111_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci9111_reset(dev);
+ if (dev->irq != 0)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver adl_pci9111_driver = {
+ .driver_name = "adl_pci9111",
+ .module = THIS_MODULE,
+ .auto_attach = pci9111_auto_attach,
+ .detach = pci9111_detach,
+};
+
+static int pci9111_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci9111_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id 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);
+
+static struct pci_driver adl_pci9111_pci_driver = {
+ .name = "adl_pci9111",
+ .id_table = pci9111_pci_table,
+ .probe = pci9111_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci9111_driver, adl_pci9111_pci_driver);
+
+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 00000000000..59a65cbc6db
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adl_pci9118.c
@@ -0,0 +1,2090 @@
+/*
+ * comedi/drivers/adl_pci9118.c
+ *
+ * hardware driver for ADLink cards:
+ * card: PCI-9118DG, PCI-9118HG, PCI-9118HR
+ * driver: pci9118dg, pci9118hg, pci9118hr
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+*/
+/*
+Driver: adl_pci9118
+Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR
+Author: Michal Dobes <dobes@tesnet.cz>
+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
+
+*/
+
+/*
+ * FIXME
+ *
+ * All the supported boards have the same PCI vendor and device IDs, so
+ * auto-attachment of PCI devices will always find the first board type.
+ *
+ * Perhaps the boards have different subdevice IDs that we could use to
+ * distinguish them?
+ *
+ * Need some device attributes so the board type can be corrected after
+ * attachment if necessary, and possibly to set other options supported by
+ * manual attachment.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include "../comedidev.h"
+
+#include "amcc_s5933.h"
+#include "8253.h"
+#include "comedi_fc.h"
+
+/* paranoid checks are broken */
+#undef PCI9118_PARANOIDCHECK /*
+ * if defined, then is used code which control
+ * correct channel number on every 12 bit sample
+ */
+
+#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
+ */
+
+struct boardtype {
+ const char *name; /* board name */
+ int device_id; /* PCI device ID of card */
+ 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 const struct boardtype boardtypes[] = {
+ {
+ .name = "pci9118dg",
+ .device_id = 0x80d9,
+ .iorange_amcc = AMCC_OP_REG_SIZE,
+ .iorange_9118 = IORANGE_9118,
+ .n_aichan = 16,
+ .n_aichand = 8,
+ .mux_aichan = 256,
+ .n_aichanlist = PCI9118_CHANLEN,
+ .n_aochan = 2,
+ .ai_maxdata = 0x0fff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci9118dg_hr,
+ .rangelist_ao = &range_bipolar10,
+ .ai_ns_min = 3000,
+ .ai_pacer_min = 12,
+ .half_fifo_size = 512,
+ }, {
+ .name = "pci9118hg",
+ .device_id = 0x80d9,
+ .iorange_amcc = AMCC_OP_REG_SIZE,
+ .iorange_9118 = IORANGE_9118,
+ .n_aichan = 16,
+ .n_aichand = 8,
+ .mux_aichan = 256,
+ .n_aichanlist = PCI9118_CHANLEN,
+ .n_aochan = 2,
+ .ai_maxdata = 0x0fff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci9118hg,
+ .rangelist_ao = &range_bipolar10,
+ .ai_ns_min = 3000,
+ .ai_pacer_min = 12,
+ .half_fifo_size = 512,
+ }, {
+ .name = "pci9118hr",
+ .device_id = 0x80d9,
+ .iorange_amcc = AMCC_OP_REG_SIZE,
+ .iorange_9118 = IORANGE_9118,
+ .n_aichan = 16,
+ .n_aichand = 8,
+ .mux_aichan = 256,
+ .n_aichanlist = PCI9118_CHANLEN,
+ .n_aochan = 2,
+ .ai_maxdata = 0xffff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci9118dg_hr,
+ .rangelist_ao = &range_bipolar10,
+ .ai_ns_min = 10000,
+ .ai_pacer_min = 40,
+ .half_fifo_size = 512,
+ },
+};
+
+struct pci9118_private {
+ unsigned long iobase_a; /* base+size for AMCC chip */
+ unsigned int master; /* master capable */
+ 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 ai_neverending; /* we do unlimited AI */
+ 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_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_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 short ao_data[2]; /* data output buffer */
+ char dma_doublebuf; /* we can use double buffering */
+ unsigned int dma_actbuf; /* which buffer is used now */
+ unsigned 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];
+ int dmabuf_pages[2]; /* number of pages in buffer */
+ unsigned char exttrg_users; /*
+ * bit field of external trigger
+ * users(0-AI, 1-AO, 2-DI, 3-DO)
+ */
+ unsigned char usedma; /* =1 use DMA transfer and not INT */
+ 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 */
+};
+
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n_chan,
+ unsigned int *chanlist, int frontadd, int backadd)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci9118_private *devpriv = dev->private;
+ 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) {
+ comedi_error(dev,
+ "range/channel list is too long for actual configuration!\n");
+ 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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int i, differencial = 0, bipolar = 0;
+ unsigned int scanquad, gain, ssh = 0x00;
+
+ 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;
+ 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);
+ ssh = devpriv->softsshhold;
+ }
+ }
+
+ 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);
+ }
+
+ if (backadd) { /* insert channels for fit onto 32bit DMA */
+ 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);
+ }
+ }
+#ifdef PCI9118_PARANOIDCHECK
+ devpriv->chanlist[n_chan ^ usedma] = devpriv->chanlist[0 ^ usedma];
+ /* for 32bit operations */
+#endif
+ outl(0, dev->iobase + PCI9118_SCANMOD); /* close scan queue */
+ /* udelay(100); important delay, or first sample will be crippled */
+
+ return 1; /* we can serve this with scan logic */
+}
+
+static int pci9118_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inl(dev->iobase + PCI9118_ADSTAT);
+ if (status & AdStatus_ADrdy)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci9118_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci9118_private *devpriv = dev->private;
+ int ret;
+ int n;
+
+ 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))
+ 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);
+
+ ret = comedi_timeout(dev, s, insn, pci9118_ai_eoc, 0);
+ if (ret) {
+ outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */
+ return ret;
+ }
+
+ if (s->maxdata == 0xffff) {
+ 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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ 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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ 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 insn->n;
+}
+
+static int pci9118_insn_bits_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outl(s->state & 0x0f, dev->iobase + PCI9118_DO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void interrupt_pci9118_ai_mode4_switch(struct comedi_device *dev)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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,
+ unsigned short *dma_buffer,
+ unsigned int num_samples)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int i = 0, j = 0;
+ unsigned int start_pos = devpriv->ai_add_front,
+ stop_pos = devpriv->ai_add_front + cmd->chanlist_len;
+ unsigned int raw_scanlen = devpriv->ai_add_front + cmd->chanlist_len +
+ 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,
+ unsigned short *dma_buffer,
+ unsigned int num_samples)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ 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) / cmd->scan_end_arg;
+ s->async->cur_chan += num_samples;
+ s->async->cur_chan %= cmd->scan_end_arg;
+ 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 int pci9118_exttrg_add(struct comedi_device *dev, unsigned char source)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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 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,
+ unsigned int chnsshfront)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ switch (mode) {
+ case 1:
+ case 4:
+ if (*tim2 < this_board->ai_ns_min)
+ *tim2 = this_board->ai_ns_min;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ,
+ div1, div2,
+ tim2, flags & TRIG_ROUND_NEAREST);
+ break;
+ case 2:
+ if (*tim2 < this_board->ai_ns_min)
+ *tim2 = this_board->ai_ns_min;
+ *div1 = *tim2 / I8254_OSC_BASE_4MHZ;
+ /* convert timer (burst) */
+ if (*div1 < this_board->ai_pacer_min)
+ *div1 = this_board->ai_pacer_min;
+ *div2 = *tim1 / I8254_OSC_BASE_4MHZ; /* scan timer */
+ *div2 = *div2 / *div1; /* major timer is c1*c2 */
+ if (*div2 < chans)
+ *div2 = chans;
+
+ *tim2 = *div1 * I8254_OSC_BASE_4MHZ; /* real convert timer */
+
+ if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
+ /* use BSSH signal */
+ if (*div2 < (chans + 2))
+ *div2 = chans + 2;
+ }
+
+ *tim1 = *div1 * *div2 * I8254_OSC_BASE_4MHZ;
+ break;
+ }
+}
+
+static void pci9118_start_pacer(struct comedi_device *dev, int mode)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int divisor1 = devpriv->ai_divisor1;
+ unsigned int divisor2 = devpriv->ai_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_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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);
+ pci9118_start_pacer(dev, 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_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 char pci9118_decode_error_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned char m)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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;
+ cfc_handle_events(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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int i, num_samples = num_bytes / sizeof(short);
+ unsigned short *array = data;
+
+ for (i = 0; i < num_samples; i++) {
+ if (devpriv->usedma)
+ array[i] = be16_to_cpu(array[i]);
+ if (s->maxdata == 0xffff)
+ 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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short sampl;
+
+ 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 (s->maxdata != 0xffff) {
+ if ((sampl & 0x000f) != devpriv->chanlist[s->async->cur_chan]) {
+ /* data dropout! */
+ dev_info(dev->class_dev,
+ "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;
+ cfc_handle_events(dev, s);
+ return;
+ }
+ }
+#endif
+ cfc_write_to_buffer(s, sampl);
+ s->async->cur_chan++;
+ if (s->async->cur_chan >= cmd->scan_end_arg) {
+ /* one scan done */
+ s->async->cur_chan %= cmd->scan_end_arg;
+ devpriv->ai_act_scan++;
+ if (!devpriv->ai_neverending) {
+ /* all data sampled? */
+ if (devpriv->ai_act_scan >= cmd->stop_arg)
+ s->async->events |= COMEDI_CB_EOA;
+ }
+ }
+
+ cfc_handle_events(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)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ 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;
+ cfc_handle_events(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;
+ cfc_handle_events(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 */
+
+ 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) {
+ /* how many samples is to end of buffer */
+ m = s->async->prealloc_bufsz >> 1;
+ sampls = m;
+ move_block_from_dma(dev, s,
+ devpriv->dmabuf_virt[devpriv->dma_actbuf],
+ samplesinbuf);
+ m = m - sampls; /* m=how many samples was transferred */
+ }
+
+ if (!devpriv->ai_neverending) {
+ /* all data sampled? */
+ if (devpriv->ai_act_scan >= cmd->stop_arg)
+ 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);
+ }
+
+ cfc_handle_events(dev, s);
+}
+
+static irqreturn_t pci9118_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pci9118_private *devpriv = dev->private;
+ unsigned int intsrc; /* IRQ reasons from card */
+ unsigned int intcsr; /* INT register from AMCC chip */
+ unsigned int adstat; /* STATUS register */
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ intsrc = inl(dev->iobase + PCI9118_INTSRC) & 0xf;
+ intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+
+ if (!intsrc && !(intcsr & ANY_S593X_INT))
+ return IRQ_NONE;
+
+ outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
+
+ adstat = inw(dev->iobase + PCI9118_ADSTAT) & 0x1ff;
+
+ if (!devpriv->ai_do)
+ return IRQ_HANDLED;
+
+ if (devpriv->ai12_startstop) {
+ if ((adstat & AdStatus_DTH) && (intsrc & Int_DTrg)) {
+ /* start/stop of measure */
+ if (devpriv->ai12_startstop & START_AI_EXT) {
+ /* deactivate EXT trigger */
+ devpriv->ai12_startstop &= ~START_AI_EXT;
+ if (!(devpriv->ai12_startstop & STOP_AI_EXT))
+ pci9118_exttrg_del(dev, EXTTRG_AI);
+
+ /* start pacer */
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ outl(devpriv->AdControlReg,
+ dev->iobase + PCI9118_ADCNTRL);
+ } else if (devpriv->ai12_startstop & STOP_AI_EXT) {
+ /* deactivate EXT trigger */
+ devpriv->ai12_startstop &= ~STOP_AI_EXT;
+ pci9118_exttrg_del(dev, EXTTRG_AI);
+
+ /* on next interrupt measure will stop */
+ devpriv->ai_neverending = 0;
+ }
+ }
+ }
+
+ if (devpriv->usedma)
+ interrupt_pci9118_ai_dma(dev, s, adstat, intcsr, intsrc);
+ else
+ interrupt_pci9118_ai_onesample(dev, s, adstat, intcsr, intsrc);
+
+ return IRQ_HANDLED;
+}
+
+static int pci9118_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ 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) {
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ 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)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci9118_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int flags;
+ unsigned int arg;
+ unsigned int divisor1 = 0, divisor2 = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_EXT | TRIG_INT);
+
+ flags = TRIG_FOLLOW;
+ if (devpriv->master)
+ flags |= TRIG_TIMER | TRIG_EXT;
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ flags = TRIG_TIMER | TRIG_EXT;
+ if (devpriv->master)
+ flags |= TRIG_NOW;
+ err |= cfc_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_NONE | TRIG_EXT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (cmd->start_src == TRIG_INT && cmd->scan_begin_src == TRIG_INT)
+ err |= -EINVAL;
+
+ if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) &&
+ (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW))))
+ err |= -EINVAL;
+
+ if ((cmd->scan_begin_src == TRIG_FOLLOW) &&
+ (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT))))
+ err |= -EINVAL;
+
+ if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_EXT:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_INT:
+ /* start_arg is the internal trigger (any value) */
+ break;
+ }
+
+ if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT))
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ 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)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ this_board->ai_ns_min);
+
+ if (cmd->scan_begin_src == TRIG_EXT)
+ if (cmd->scan_begin_arg) {
+ cmd->scan_begin_arg = 0;
+ err |= -EINVAL;
+ err |= cfc_check_trigger_arg_max(&cmd->scan_end_arg,
+ 65535);
+ }
+
+ if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW))
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ this_board->ai_ns_min);
+
+ if (cmd->convert_src == TRIG_EXT)
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+
+ err |= cfc_check_trigger_arg_min(&cmd->scan_end_arg,
+ cmd->chanlist_len);
+
+ if ((cmd->scan_end_arg % cmd->chanlist_len)) {
+ cmd->scan_end_arg =
+ cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len);
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ,
+ &divisor1, &divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_4MHZ,
+ &divisor1, &divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER &&
+ cmd->convert_src == TRIG_NOW) {
+ if (cmd->convert_arg == 0) {
+ arg = this_board->ai_ns_min *
+ (cmd->scan_end_arg + 2);
+ } else {
+ arg = cmd->convert_arg * cmd->chanlist_len;
+ }
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ 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,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int dmalen0, dmalen1, i;
+
+ dmalen0 = devpriv->dmabuf_size[0];
+ dmalen1 = devpriv->dmabuf_size[1];
+ /* isn't output buff smaller that our DMA buff? */
+ if (dmalen0 > s->async->prealloc_bufsz) {
+ /* align to 32bit down */
+ dmalen0 = s->async->prealloc_bufsz & ~3L;
+ }
+ if (dmalen1 > s->async->prealloc_bufsz) {
+ /* align to 32bit down */
+ dmalen1 = s->async->prealloc_bufsz & ~3L;
+ }
+
+ /* 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);
+ dev_info(dev->class_dev,
+ "WAR: DMA0 buf too short, can't support TRIG_WAKE_EOS (%d<%d)\n",
+ dmalen0, devpriv->ai_n_realscanlen << 1);
+ } else {
+ /* short first DMA buffer to one scan */
+ dmalen0 = devpriv->ai_n_realscanlen << 1;
+ if (dmalen0 < 4) {
+ dev_info(dev->class_dev,
+ "ERR: DMA0 buf len bug? (%d<4)\n",
+ 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);
+ dev_info(dev->class_dev,
+ "WAR: DMA1 buf too short, can't support TRIG_WAKE_EOS (%d<%d)\n",
+ dmalen1, devpriv->ai_n_realscanlen << 1);
+ } else {
+ /* short second DMA buffer to one scan */
+ dmalen1 = devpriv->ai_n_realscanlen << 1;
+ if (dmalen1 < 4) {
+ dev_info(dev->class_dev,
+ "ERR: DMA1 buf len bug? (%d<4)\n",
+ dmalen1);
+ dmalen1 = 4;
+ }
+ }
+ }
+
+ /* 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) *
+ cmd->stop_arg)) {
+ dmalen0 =
+ (devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg;
+ dmalen0 &= ~3L;
+ } else { /*
+ * fits whole measure into
+ * two DMA buffer?
+ */
+ if (dmalen1 >
+ ((devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg - dmalen0))
+ dmalen1 =
+ (devpriv->ai_n_realscanlen << 1) *
+ cmd->stop_arg - dmalen0;
+ dmalen1 &= ~3L;
+ }
+ }
+ }
+
+ /* these DMA buffer size will be used */
+ devpriv->dma_actbuf = 0;
+ devpriv->dmabuf_use_size[0] = dmalen0;
+ devpriv->dmabuf_use_size[1] = dmalen1;
+
+#if 0
+ if (cmd->scan_end_arg < this_board->half_fifo_size) {
+ devpriv->dmabuf_panic_size[0] =
+ (this_board->half_fifo_size / cmd->scan_end_arg +
+ 1) * cmd->scan_end_arg * sizeof(short);
+ devpriv->dmabuf_panic_size[1] =
+ (this_board->half_fifo_size / cmd->scan_end_arg +
+ 1) * cmd->scan_end_arg * sizeof(short);
+ } else {
+ devpriv->dmabuf_panic_size[0] =
+ (cmd->scan_end_arg << 1) % devpriv->dmabuf_size[0];
+ devpriv->dmabuf_panic_size[1] =
+ (cmd->scan_end_arg << 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 */
+
+ return 0;
+}
+
+static int pci9118_ai_docmd_sampl(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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;
+ }
+
+ 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) {
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ devpriv->AdControlReg |= AdControl_SoftG;
+ }
+ outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL);
+ }
+
+ return 0;
+}
+
+static int pci9118_ai_docmd_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ Compute_and_setup_dma(dev, s);
+
+ 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 (cmd->convert_src == TRIG_NOW && !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 */
+ }
+
+ 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) {
+ pci9118_start_pacer(dev, devpriv->ai_do);
+ devpriv->AdControlReg |= AdControl_SoftG;
+ }
+ outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL);
+ }
+
+ return 0;
+}
+
+static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci9118_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int addchans = 0;
+ int ret = 0;
+
+ devpriv->ai12_startstop = 0;
+ devpriv->ai_flags = cmd->flags;
+ 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;
+ s->async->inttrig = pci9118_ai_inttrig;
+ }
+ if (cmd->stop_src == TRIG_NONE)
+ devpriv->ai_neverending = 1;
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->ai_neverending = 0;
+
+ /*
+ * use additional sample at end of every scan
+ * to satisty DMA 32 bit transfer?
+ */
+ devpriv->ai_add_front = 0;
+ devpriv->ai_add_back = 0;
+ if (devpriv->master) {
+ devpriv->usedma = 1;
+ if ((cmd->flags & TRIG_WAKE_EOS) &&
+ (cmd->scan_end_arg == 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) &&
+ (cmd->scan_end_arg & 1) &&
+ (cmd->scan_end_arg > 1)) {
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ 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 (cmd->convert_src == TRIG_NOW && 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 +
+ cmd->chanlist_len +
+ 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 cmd->scan_end_arg on output?
+ */
+ (devpriv->ai_add_front + cmd->chanlist_len +
+ devpriv->ai_add_back) * (cmd->scan_end_arg /
+ cmd->chanlist_len);
+
+ /* check and setup channel list */
+ if (!check_channel_list(dev, s, cmd->chanlist_len,
+ cmd->chanlist, devpriv->ai_add_front,
+ devpriv->ai_add_back))
+ return -EINVAL;
+ if (!setup_channel_list(dev, s, cmd->chanlist_len,
+ cmd->chanlist, 0, devpriv->ai_add_front,
+ devpriv->ai_add_back, devpriv->usedma))
+ 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->ai_add_front);
+ }
+
+ 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->ai_add_front);
+ }
+
+ if ((cmd->scan_begin_src == TRIG_FOLLOW)
+ && (cmd->convert_src == TRIG_EXT)) {
+ devpriv->ai_do = 3;
+ }
+
+ pci9118_start_pacer(dev, -1); /* 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;
+
+ if (devpriv->usedma)
+ ret = pci9118_ai_docmd_dma(dev, s);
+ else
+ ret = pci9118_ai_docmd_sampl(dev, s);
+
+ return ret;
+}
+
+static int pci9118_reset(struct comedi_device *dev)
+{
+ struct pci9118_private *devpriv = dev->private;
+
+ 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); */
+ pci9118_start_pacer(dev, 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->exttrg_users = 0;
+
+ return 0;
+}
+
+/*
+ * FIXME - this is pretty ineffective because all the supported board types
+ * have the same device ID!
+ */
+static const struct boardtype *pci9118_find_boardinfo(struct pci_dev *pcidev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(boardtypes); i++)
+ if (pcidev->device == boardtypes[i].device_id)
+ return &boardtypes[i];
+ return NULL;
+}
+
+static struct pci_dev *pci9118_find_pci(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci_dev *pcidev = NULL;
+ int bus = it->options[0];
+ int slot = it->options[1];
+
+ for_each_pci_dev(pcidev) {
+ if (pcidev->vendor != PCI_VENDOR_ID_AMCC)
+ continue;
+ if (pcidev->device != this_board->device_id)
+ continue;
+ if (bus || slot) {
+ /* requested particular bus/slot */
+ if (pcidev->bus->number != bus ||
+ PCI_SLOT(pcidev->devfn) != slot)
+ continue;
+ }
+ return pcidev;
+ }
+ dev_err(dev->class_dev,
+ "no supported board found! (req. bus/slot : %d/%d)\n",
+ bus, slot);
+ return NULL;
+}
+
+static int pci9118_common_attach(struct comedi_device *dev, int disable_irq,
+ int master, int ext_mux, int softsshdelay,
+ int hw_err_mask)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci9118_private *devpriv = dev->private;
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret, pages, i;
+ u16 u16w;
+
+ dev->board_name = this_board->name;
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ if (master)
+ pci_set_master(pcidev);
+
+ devpriv->iobase_a = pci_resource_start(pcidev, 0);
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ pci9118_reset(dev);
+
+ 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] =
+ (unsigned 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_hw[i] =
+ virt_to_bus((void *)
+ devpriv->dmabuf_virt[i]);
+ }
+ }
+ if (!devpriv->dmabuf_virt[0]) {
+ dev_warn(dev->class_dev,
+ "Can't allocate DMA buffer, DMA disabled!\n");
+ master = 0;
+ }
+ if (devpriv->dmabuf_virt[1])
+ devpriv->dma_doublebuf = 1;
+ }
+ devpriv->master = master;
+
+ if (ext_mux > 0) {
+ if (ext_mux > 256)
+ ext_mux = 256; /* max 256 channels! */
+ if (softsshdelay > 0)
+ if (ext_mux > 128)
+ ext_mux = 128;
+ devpriv->usemux = ext_mux;
+ } else {
+ devpriv->usemux = 0;
+ }
+
+ if (softsshdelay < 0) {
+ /* select sample&hold signal polarity */
+ devpriv->softsshdelay = -softsshdelay;
+ devpriv->softsshsample = 0x80;
+ devpriv->softsshhold = 0x00;
+ } else {
+ devpriv->softsshdelay = softsshdelay;
+ devpriv->softsshsample = 0x00;
+ devpriv->softsshhold = 0x80;
+ }
+
+ pci_read_config_word(pcidev, PCI_COMMAND, &u16w);
+ pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64);
+ /* Enable parity check for parity error */
+
+ if (!disable_irq && pcidev->irq) {
+ ret = request_irq(pcidev->irq, pci9118_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ 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->range_table = this_board->rangelist_ai;
+ s->insn_read = pci9118_insn_read_ai;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = this_board->n_aichanlist;
+ s->do_cmdtest = pci9118_ai_cmdtest;
+ s->do_cmd = pci9118_ai_cmd;
+ s->cancel = pci9118_ai_cancel;
+ 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->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->insn_bits = pci9118_insn_bits_do;
+
+ devpriv->ai_maskharderr = 0x10a;
+ /* default measure crash condition */
+ if (hw_err_mask) /* disable some requested */
+ devpriv->ai_maskharderr &= ~hw_err_mask;
+
+ return 0;
+}
+
+static int pci9118_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pci9118_private *devpriv;
+ struct pci_dev *pcidev;
+ int ext_mux, disable_irq, master, softsshdelay, hw_err_mask;
+
+ ext_mux = it->options[2];
+ master = ((it->options[3] & 1) == 0);
+ disable_irq = ((it->options[3] & 2) != 0);
+ softsshdelay = it->options[4];
+ hw_err_mask = it->options[5];
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ pcidev = pci9118_find_pci(dev, it);
+ if (!pcidev)
+ return -EIO;
+ comedi_set_hw_dev(dev, &pcidev->dev);
+
+ return pci9118_common_attach(dev, disable_irq, master, ext_mux,
+ softsshdelay, hw_err_mask);
+}
+
+static int pci9118_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci9118_private *devpriv;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_ptr = pci9118_find_boardinfo(pcidev);
+ if (dev->board_ptr == NULL) {
+ dev_err(dev->class_dev,
+ "adl_pci9118: cannot determine board type for pci %s\n",
+ pci_name(pcidev));
+ return -EINVAL;
+ }
+ /*
+ * Need to 'get' the PCI device to match the 'put' in pci9118_detach().
+ * (The 'put' also matches the implicit 'get' by pci9118_find_pci().)
+ */
+ pci_dev_get(pcidev);
+ /* Don't disable irq, use bus master, no external mux,
+ * no sample-hold delay, no error mask. */
+ return pci9118_common_attach(dev, 0, 1, 0, 0, 0);
+}
+
+static void pci9118_detach(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci9118_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (dev->iobase)
+ pci9118_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ 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]);
+ }
+ comedi_pci_disable(dev);
+ if (pcidev)
+ pci_dev_put(pcidev);
+}
+
+static struct comedi_driver adl_pci9118_driver = {
+ .driver_name = "adl_pci9118",
+ .module = THIS_MODULE,
+ .attach = pci9118_attach,
+ .auto_attach = pci9118_auto_attach,
+ .detach = pci9118_detach,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .board_name = &boardtypes[0].name,
+ .offset = sizeof(struct boardtype),
+};
+
+static int adl_pci9118_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adl_pci9118_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adl_pci9118_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80d9) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table);
+
+static struct pci_driver adl_pci9118_pci_driver = {
+ .name = "adl_pci9118",
+ .id_table = adl_pci9118_pci_table,
+ .probe = adl_pci9118_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver);
+
+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 00000000000..b4ea37704ea
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adq12b.c
@@ -0,0 +1,293 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: adq12b
+Description: driver for MicroAxial ADQ12-B data acquisition and control card
+Devices: [MicroAxial] ADQ12-B (adq12b)
+Author: jeremy theler <thelerg@ib.cnea.gov.ar>
+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 <thelerg@ib.cnea.gov.ar>
+
+ 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 <linux/module.h>
+#include <linux/delay.h>
+
+#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
+
+/* 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_private {
+ int unipolar; /* option 2 of comedi_config (1 is iobase) */
+ int differential; /* option 3 of comedi_config */
+ int last_channel;
+ int last_range;
+};
+
+static int adq12b_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + ADQ12B_STINR);
+ if (status & ADQ12B_EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int adq12b_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct adq12b_private *devpriv = dev->private;
+ int n;
+ int range, channel;
+ unsigned char hi, lo, status;
+ int ret;
+
+ /* 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 */
+ ret = comedi_timeout(dev, s, insn, adq12b_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ hi = inb(dev->iobase + ADQ12B_ADHIG);
+ lo = inb(dev->iobase + ADQ12B_ADLOW);
+
+ 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 insn->n;
+}
+
+static int adq12b_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int chan;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ for (chan = 0; chan < 8; chan++) {
+ if ((mask >> chan) & 0x01) {
+ val = (s->state >> chan) & 0x01;
+ outb((val << 3) | chan,
+ dev->iobase + ADQ12B_OUTBR);
+ }
+ }
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct adq12b_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], ADQ12B_SIZE);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->unipolar = it->options[1];
+ devpriv->differential = it->options[2];
+ /*
+ * 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;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ if (devpriv->differential) {
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 8;
+ } else {
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16;
+ }
+
+ if (devpriv->unipolar)
+ s->range_table = &range_adq12b_ai_unipolar;
+ else
+ s->range_table = &range_adq12b_ai_bipolar;
+
+ s->maxdata = 0xfff;
+
+ 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 = 5;
+ 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 = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = adq12b_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver adq12b_driver = {
+ .driver_name = "adq12b",
+ .module = THIS_MODULE,
+ .attach = adq12b_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(adq12b_driver);
+
+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 00000000000..602b7a1e40e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1710.c
@@ -0,0 +1,1338 @@
+/*
+ * comedi/drivers/adv_pci1710.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn>
+ * 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 <dobes@tesnet.cz>
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+#include "amcc_s5933.h"
+
+/* hardware types of the cards */
+#define TYPE_PCI171X 0
+#define TYPE_PCI1713 2
+#define TYPE_PCI1720 3
+
+#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_TIMER_BASE 0x18
+
+#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)
+ }
+};
+
+enum pci1710_boardid {
+ BOARD_PCI1710,
+ BOARD_PCI1710HG,
+ BOARD_PCI1711,
+ BOARD_PCI1713,
+ BOARD_PCI1720,
+ BOARD_PCI1731,
+};
+
+struct boardtype {
+ const char *name; /* board name */
+ 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 const struct boardtype boardtypes[] = {
+ [BOARD_PCI1710] = {
+ .name = "pci1710",
+ .have_irq = 1,
+ .cardtype = TYPE_PCI171X,
+ .n_aichan = 16,
+ .n_aichand = 8,
+ .n_aochan = 2,
+ .n_dichan = 16,
+ .n_dochan = 16,
+ .n_counter = 1,
+ .ai_maxdata = 0x0fff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci1710_3,
+ .rangecode_ai = range_codes_pci1710_3,
+ .rangelist_ao = &range_pci171x_da,
+ .ai_ns_min = 10000,
+ .fifo_half_size = 2048,
+ },
+ [BOARD_PCI1710HG] = {
+ .name = "pci1710hg",
+ .have_irq = 1,
+ .cardtype = TYPE_PCI171X,
+ .n_aichan = 16,
+ .n_aichand = 8,
+ .n_aochan = 2,
+ .n_dichan = 16,
+ .n_dochan = 16,
+ .n_counter = 1,
+ .ai_maxdata = 0x0fff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci1710hg,
+ .rangecode_ai = range_codes_pci1710hg,
+ .rangelist_ao = &range_pci171x_da,
+ .ai_ns_min = 10000,
+ .fifo_half_size = 2048,
+ },
+ [BOARD_PCI1711] = {
+ .name = "pci1711",
+ .have_irq = 1,
+ .cardtype = TYPE_PCI171X,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .n_dichan = 16,
+ .n_dochan = 16,
+ .n_counter = 1,
+ .ai_maxdata = 0x0fff,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci17x1,
+ .rangecode_ai = range_codes_pci17x1,
+ .rangelist_ao = &range_pci171x_da,
+ .ai_ns_min = 10000,
+ .fifo_half_size = 512,
+ },
+ [BOARD_PCI1713] = {
+ .name = "pci1713",
+ .have_irq = 1,
+ .cardtype = TYPE_PCI1713,
+ .n_aichan = 32,
+ .n_aichand = 16,
+ .ai_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci1710_3,
+ .rangecode_ai = range_codes_pci1710_3,
+ .ai_ns_min = 10000,
+ .fifo_half_size = 2048,
+ },
+ [BOARD_PCI1720] = {
+ .name = "pci1720",
+ .cardtype = TYPE_PCI1720,
+ .n_aochan = 4,
+ .ao_maxdata = 0x0fff,
+ .rangelist_ao = &range_pci1720,
+ },
+ [BOARD_PCI1731] = {
+ .name = "pci1731",
+ .have_irq = 1,
+ .cardtype = TYPE_PCI171X,
+ .n_aichan = 16,
+ .n_dichan = 16,
+ .n_dochan = 16,
+ .ai_maxdata = 0x0fff,
+ .rangelist_ai = &range_pci17x1,
+ .rangecode_ai = range_codes_pci17x1,
+ .ai_ns_min = 10000,
+ .fifo_half_size = 512,
+ },
+};
+
+struct pci1710_private {
+ unsigned int CntrlReg; /* Control register */
+ unsigned int ai_act_scan; /* how many scans we finished */
+ unsigned char ai_et;
+ unsigned int ai_et_CntrlReg;
+ unsigned int ai_et_MuxVal;
+ unsigned int next_divisor1;
+ unsigned int next_divisor2;
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int act_chanlist[32]; /* list of scanned channel */
+ unsigned char saved_seglen; /* len of the non-repeating chanlist */
+ unsigned char da_ranges; /* copy of D/A outpit range register */
+ unsigned short ao_data[4]; /* data output buffer */
+ unsigned int cnt0_write_wait; /* after a write, wait for update of the
+ * internal state */
+};
+
+/* 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_ai_dropout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int val)
+{
+ const struct boardtype *board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+
+ if (board->cardtype != TYPE_PCI1713) {
+ if ((val & 0xf000) != devpriv->act_chanlist[chan]) {
+ dev_err(dev->class_dev,
+ "A/D data droput: received from channel %d, expected %d\n",
+ (val >> 12) & 0xf,
+ (devpriv->act_chanlist[chan] >> 12) & 0xf);
+ return -ENODATA;
+ }
+ }
+ return 0;
+}
+
+static int pci171x_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int last_aref = CR_AREF(cmd->chanlist[0]);
+ unsigned int next_chan = (chan0 + 1) % s->n_chan;
+ unsigned int chansegment[32];
+ unsigned int seglen;
+ int i;
+
+ if (cmd->chanlist_len == 1) {
+ devpriv->saved_seglen = cmd->chanlist_len;
+ return 0;
+ }
+
+ /* first channel is always ok */
+ chansegment[0] = cmd->chanlist[0];
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (cmd->chanlist[0] == cmd->chanlist[i])
+ break; /* we detected a loop, stop */
+
+ if (aref == AREF_DIFF && (chan & 1)) {
+ dev_err(dev->class_dev,
+ "Odd channel cannot be differential input!\n");
+ return -EINVAL;
+ }
+
+ if (last_aref == AREF_DIFF)
+ next_chan = (next_chan + 1) % s->n_chan;
+ if (chan != next_chan) {
+ dev_err(dev->class_dev,
+ "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
+ i, chan, next_chan, chan0);
+ return -EINVAL;
+ }
+
+ /* next correct channel in list */
+ chansegment[i] = cmd->chanlist[i];
+ last_aref = aref;
+ }
+ seglen = i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ if (cmd->chanlist[i] != chansegment[i % seglen]) {
+ dev_err(dev->class_dev,
+ "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(cmd->chanlist[i % seglen]),
+ CR_RANGE(cmd->chanlist[i % seglen]),
+ CR_AREF(chansegment[i % seglen]));
+ return -EINVAL;
+ }
+ }
+ devpriv->saved_seglen = seglen;
+
+ return 0;
+}
+
+static void setup_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan,
+ unsigned int seglen)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int i, range, chanprog;
+
+ 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 */
+ devpriv->act_chanlist[i] =
+ (CR_CHAN(chanlist[i]) << 12) & 0xf000;
+ }
+ for ( ; i < n_chan; i++) { /* store remainder of channel list */
+ devpriv->act_chanlist[i] =
+ (CR_CHAN(chanlist[i]) << 12) & 0xf000;
+ }
+
+ devpriv->ai_et_MuxVal =
+ CR_CHAN(chanlist[0]) | (CR_CHAN(chanlist[seglen - 1]) << 8);
+ /* select channel interval to scan */
+ outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX);
+}
+
+static int pci171x_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + PCI171x_STATUS);
+ if ((status & Status_FE) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci171x_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ 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);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val;
+
+ outw(0, dev->iobase + PCI171x_SOFTTRG); /* start conversion */
+
+ ret = comedi_timeout(dev, s, insn, pci171x_ai_eoc, 0);
+ if (ret)
+ break;
+
+ val = inw(dev->iobase + PCI171x_AD_DATA);
+ ret = pci171x_ai_dropout(dev, s, chan, val);
+ if (ret)
+ break;
+
+ data[i] = val & s->maxdata;
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ return ret ? ret : insn->n;
+}
+
+/*
+==============================================================================
+*/
+static int pci171x_insn_write_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int val;
+ 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;
+ }
+ val = devpriv->ao_data[chan];
+
+ for (n = 0; n < insn->n; n++) {
+ val = data[n];
+ outw(val, dev->iobase + ofs);
+ }
+
+ devpriv->ao_data[chan] = val;
+
+ return n;
+
+}
+
+/*
+==============================================================================
+*/
+static int pci171x_insn_read_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci1710_private *devpriv = dev->private;
+ 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 insn->n;
+}
+
+static int pci171x_insn_bits_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI171x_DO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pci171x_start_pacer(struct comedi_device *dev,
+ bool load_counters)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + PCI171X_TIMER_BASE;
+
+ i8254_set_mode(timer_base, 1, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 1, 1, I8254_MODE2 | I8254_BINARY);
+
+ if (load_counters) {
+ i8254_write(timer_base, 1, 2, devpriv->divisor2);
+ i8254_write(timer_base, 1, 1, devpriv->divisor1);
+ }
+}
+
+/*
+==============================================================================
+*/
+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)
+{
+ struct pci1710_private *devpriv = dev->private;
+ 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 */
+ struct pci1710_private *devpriv = dev->private;
+ 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)
+{
+ struct pci1710_private *devpriv = dev->private;
+ unsigned int val;
+ 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;
+ }
+ val = devpriv->ao_data[chan];
+
+ for (n = 0; n < insn->n; n++) {
+ val = data[n];
+ outw(val, dev->iobase + PCI1720_DA0 + (chan << 1));
+ outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */
+ }
+
+ devpriv->ao_data[chan] = val;
+
+ return n;
+}
+
+/*
+==============================================================================
+*/
+static int pci171x_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+
+ switch (this_board->cardtype) {
+ default:
+ devpriv->CntrlReg &= Control_CNT0;
+ devpriv->CntrlReg |= Control_SW;
+
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); /* reset any operations */
+ pci171x_start_pacer(dev, false);
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+ break;
+ }
+
+ devpriv->ai_act_scan = 0;
+ s->async->cur_chan = 0;
+
+ return 0;
+}
+
+static void pci1710_handle_every_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int status;
+ unsigned int val;
+ int ret;
+
+ status = inw(dev->iobase + PCI171x_STATUS);
+ if (status & Status_FE) {
+ dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status);
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ cfc_handle_events(dev, s);
+ return;
+ }
+ if (status & Status_FF) {
+ dev_dbg(dev->class_dev,
+ "A/D FIFO Full status (Fatal Error!) (%4x)\n", status);
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ cfc_handle_events(dev, s);
+ return;
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+
+ for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) {
+ val = inw(dev->iobase + PCI171x_AD_DATA);
+ ret = pci171x_ai_dropout(dev, s, s->async->cur_chan, val);
+ if (ret) {
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ break;
+ }
+
+ comedi_buf_put(s, val & s->maxdata);
+
+ s->async->cur_chan++;
+ if (s->async->cur_chan >= cmd->chanlist_len)
+ s->async->cur_chan = 0;
+
+
+ if (s->async->cur_chan == 0) { /* one scan done */
+ devpriv->ai_act_scan++;
+ if (cmd->stop_src == TRIG_COUNT &&
+ devpriv->ai_act_scan >= cmd->stop_arg) {
+ /* all data sampled */
+ s->async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+ }
+
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+
+ cfc_handle_events(dev, s);
+}
+
+/*
+==============================================================================
+*/
+static int move_block_from_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n, int turn)
+{
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int val;
+ int ret;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ val = inw(dev->iobase + PCI171x_AD_DATA);
+
+ ret = pci171x_ai_dropout(dev, s, s->async->cur_chan, val);
+ if (ret) {
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return ret;
+ }
+
+ comedi_buf_put(s, val & s->maxdata);
+
+ s->async->cur_chan++;
+ if (s->async->cur_chan >= cmd->chanlist_len) {
+ s->async->cur_chan = 0;
+ devpriv->ai_act_scan++;
+ }
+ }
+ return 0;
+}
+
+static void pci1710_handle_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int m, samplesinbuf;
+
+ m = inw(dev->iobase + PCI171x_STATUS);
+ if (!(m & Status_FH)) {
+ dev_dbg(dev->class_dev, "A/D FIFO not half full! (%4x)\n", m);
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ cfc_handle_events(dev, s);
+ return;
+ }
+ if (m & Status_FF) {
+ dev_dbg(dev->class_dev,
+ "A/D FIFO Full status (Fatal Error!) (%4x)\n", m);
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ cfc_handle_events(dev, s);
+ return;
+ }
+
+ samplesinbuf = this_board->fifo_half_size;
+ if (samplesinbuf * sizeof(short) >= s->async->prealloc_bufsz) {
+ m = s->async->prealloc_bufsz / 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 (cmd->stop_src == TRIG_COUNT &&
+ devpriv->ai_act_scan >= cmd->stop_arg) {
+ /* all data sampled */
+ s->async->events |= COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+ return;
+ }
+ outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */
+
+ cfc_handle_events(dev, s);
+}
+
+/*
+==============================================================================
+*/
+static irqreturn_t interrupt_service_pci1710(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ struct comedi_cmd *cmd;
+
+ if (!dev->attached) /* is device attached? */
+ return IRQ_NONE; /* no, exit */
+
+ s = dev->read_subdev;
+ cmd = &s->async->cmd;
+
+ /* is this interrupt from our board? */
+ if (!(inw(dev->iobase + PCI171x_STATUS) & Status_IRQ))
+ return IRQ_NONE; /* no, exit */
+
+ 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);
+ pci171x_start_pacer(dev, true);
+ return IRQ_HANDLED;
+ }
+
+ if (cmd->flags & TRIG_WAKE_EOS)
+ pci1710_handle_every_sample(dev, s);
+ else
+ pci1710_handle_fifo(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int pci171x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci1710_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ pci171x_start_pacer(dev, false);
+
+ setup_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len,
+ devpriv->saved_seglen);
+
+ outb(0, dev->iobase + PCI171x_CLRFIFO);
+ outb(0, dev->iobase + PCI171x_CLRINT);
+
+ devpriv->ai_act_scan = 0;
+ s->async->cur_chan = 0;
+
+ devpriv->CntrlReg &= Control_CNT0;
+ if ((cmd->flags & TRIG_WAKE_EOS) == 0)
+ devpriv->CntrlReg |= Control_ONEFH;
+
+ devpriv->divisor1 = devpriv->next_divisor1;
+ devpriv->divisor2 = devpriv->next_divisor2;
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ devpriv->CntrlReg |= Control_PACER | Control_IRQEN;
+ if (cmd->start_src == TRIG_EXT) {
+ devpriv->ai_et_CntrlReg = devpriv->CntrlReg;
+ devpriv->CntrlReg &=
+ ~(Control_PACER | Control_ONEFH | Control_GATE);
+ devpriv->CntrlReg |= Control_EXT;
+ devpriv->ai_et = 1;
+ } else { /* TRIG_NOW */
+ devpriv->ai_et = 0;
+ }
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+
+ if (cmd->start_src == TRIG_NOW)
+ pci171x_start_pacer(dev, true);
+ } else { /* TRIG_EXT */
+ devpriv->CntrlReg |= Control_EXT | Control_IRQEN;
+ outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+ }
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci171x_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* step 2a: make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* step 2b: and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ this_board->ai_ns_min);
+ else /* TRIG_FOLLOW */
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->next_divisor1,
+ &devpriv->next_divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list */
+
+ err |= pci171x_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci171x_reset(struct comedi_device *dev)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+ struct pci1710_private *devpriv = dev->private;
+
+ 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 */
+ pci171x_start_pacer(dev, false);
+ 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 */
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci1720_reset(struct comedi_device *dev)
+{
+ struct pci1710_private *devpriv = dev->private;
+
+ 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;
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci1710_reset(struct comedi_device *dev)
+{
+ const struct boardtype *this_board = comedi_board(dev);
+
+ switch (this_board->cardtype) {
+ case TYPE_PCI1720:
+ return pci1720_reset(dev);
+ default:
+ return pci171x_reset(dev);
+ }
+}
+
+static int pci1710_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct boardtype *this_board = NULL;
+ struct pci1710_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, subdev, n_subdevices;
+
+ if (context < ARRAY_SIZE(boardtypes))
+ this_board = &boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ 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 = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ pci1710_reset(dev);
+
+ if (this_board->have_irq && pcidev->irq) {
+ ret = request_irq(pcidev->irq, interrupt_service_pci1710,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ subdev = 0;
+
+ if (this_board->n_aichan) {
+ s = &dev->subdevices[subdev];
+ 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->range_table = this_board->rangelist_ai;
+ s->insn_read = pci171x_insn_read_ai;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = pci171x_ai_cmdtest;
+ s->do_cmd = pci171x_ai_cmd;
+ s->cancel = pci171x_ai_cancel;
+ }
+ 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->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;
+ 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++;
+ }
+
+ return 0;
+}
+
+static void pci1710_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci1710_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver adv_pci1710_driver = {
+ .driver_name = "adv_pci1710",
+ .module = THIS_MODULE,
+ .auto_attach = pci1710_auto_attach,
+ .detach = pci1710_detach,
+};
+
+static int adv_pci1710_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1710_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1710_pci_table[] = {
+ {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0x0000),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb200),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc200),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100),
+ .driver_data = BOARD_PCI1710,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0x0002),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb102),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xb202),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc102),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+ PCI_VENDOR_ID_ADVANTECH, 0xc202),
+ .driver_data = BOARD_PCI1710HG,
+ }, {
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102),
+ .driver_data = BOARD_PCI1710HG,
+ },
+ { PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 },
+ { PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 },
+ { PCI_VDEVICE(ADVANTECH, 0x1720), BOARD_PCI1720 },
+ { PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table);
+
+static struct pci_driver adv_pci1710_pci_driver = {
+ .name = "adv_pci1710",
+ .id_table = adv_pci1710_pci_table,
+ .probe = adv_pci1710_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver);
+
+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 00000000000..07b107d1ab3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1723.c
@@ -0,0 +1,323 @@
+/*
+ comedi/drivers/pci1723.c
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: adv_pci1723
+Description: Advantech PCI-1723
+Author: yonggang <rsmgnu@gmail.com>, Ian Abbott <abbotti@mev.co.uk>
+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 <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+/* 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 */
+
+struct pci1723_private {
+ unsigned char da_range[8]; /* D/A output range for each channel */
+ unsigned short ao_data[8]; /* data output buffer */
+};
+
+/*
+ * The pci1723 card reset;
+ */
+static int pci1723_reset(struct comedi_device *dev)
+{
+ struct pci1723_private *devpriv = dev->private;
+ int i;
+
+ 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);
+
+ return 0;
+}
+
+static int pci1723_insn_read_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct pci1723_private *devpriv = dev->private;
+ int n, chan;
+
+ chan = CR_CHAN(insn->chanspec);
+ 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)
+{
+ struct pci1723_private *devpriv = dev->private;
+ int n, chan;
+ chan = CR_CHAN(insn->chanspec);
+
+ 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 chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned short mode;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x00ff;
+ else
+ mask = 0xff00;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ /* update hardware DIO mode */
+ mode = 0x0000; /* assume output */
+ if (!(s->io_bits & 0x00ff))
+ mode |= 0x0001; /* low byte input */
+ if (!(s->io_bits & 0xff00))
+ mode |= 0x0002; /* high byte input */
+ outw(mode, dev->iobase + PCI1723_DIGITAL_IO_PORT_SET);
+
+ return insn->n;
+}
+
+static int pci1723_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PCI1723_WRITE_DIGITAL_OUTPUT_CMD);
+
+ data[1] = inw(dev->iobase + PCI1723_READ_DIGITAL_INPUT_DATA);
+
+ return insn->n;
+}
+
+static int pci1723_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pci1723_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ dev->write_subdev = s;
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 8;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 8;
+ s->range_table = &range_bipolar10;
+ s->insn_write = pci1723_ao_write_winsn;
+ s->insn_read = pci1723_insn_read_ao;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->len_chanlist = 16;
+ s->range_table = &range_digital;
+ s->insn_config = pci1723_dio_insn_config;
+ s->insn_bits = pci1723_dio_insn_bits;
+
+ /* 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);
+
+ pci1723_reset(dev);
+
+ return 0;
+}
+
+static void pci1723_detach(struct comedi_device *dev)
+{
+ if (dev->iobase)
+ pci1723_reset(dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver adv_pci1723_driver = {
+ .driver_name = "adv_pci1723",
+ .module = THIS_MODULE,
+ .auto_attach = pci1723_auto_attach,
+ .detach = pci1723_detach,
+};
+
+static int adv_pci1723_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1723_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1723_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1723) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1723_pci_table);
+
+static struct pci_driver adv_pci1723_pci_driver = {
+ .name = "adv_pci1723",
+ .id_table = adv_pci1723_pci_table,
+ .probe = adv_pci1723_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1723_driver, adv_pci1723_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/adv_pci1724.c b/drivers/staging/comedi/drivers/adv_pci1724.c
new file mode 100644
index 00000000000..af670acb03d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1724.c
@@ -0,0 +1,401 @@
+/*
+ comedi/drivers/adv_pci1724.c
+ This is a driver for the Advantech PCI-1724U card.
+
+ Author: Frank Mori Hess <fmh6jj@gmail.com>
+ Copyright (C) 2013 GnuBIO Inc
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+
+Driver: adv_1724
+Description: Advantech PCI-1724U
+Author: Frank Mori Hess <fmh6jj@gmail.com>
+Status: works
+Updated: 2013-02-09
+Devices: [Advantech] PCI-1724U (adv_pci1724)
+
+Subdevice 0 is the analog output.
+Subdevice 1 is the offset calibration for the analog output.
+Subdevice 2 is the gain calibration for the analog output.
+
+The calibration offset and gains have quite a large effect
+on the analog output, so it is possible to adjust the analog output to
+have an output range significantly different from the board's
+nominal output ranges. For a calibrated +/- 10V range, the analog
+output's offset will be set somewhere near mid-range (0x2000) and its
+gain will be near maximum (0x3fff).
+
+There is really no difference between the board's documented 0-20mA
+versus 4-20mA output ranges. To pick one or the other is simply a matter
+of adjusting the offset and gain calibration until the board outputs in
+the desired range.
+
+Configuration options:
+ None
+
+Manual configuration of comedi devices is not supported by this driver;
+supported PCI devices are configured as comedi devices automatically.
+
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#define PCI_VENDOR_ID_ADVANTECH 0x13fe
+
+#define NUM_AO_CHANNELS 32
+
+/* register offsets */
+enum board_registers {
+ DAC_CONTROL_REG = 0x0,
+ SYNC_OUTPUT_REG = 0x4,
+ EEPROM_CONTROL_REG = 0x8,
+ SYNC_OUTPUT_TRIGGER_REG = 0xc,
+ BOARD_ID_REG = 0x10
+};
+
+/* bit definitions for registers */
+enum dac_control_contents {
+ DAC_DATA_MASK = 0x3fff,
+ DAC_DESTINATION_MASK = 0xc000,
+ DAC_NORMAL_MODE = 0xc000,
+ DAC_OFFSET_MODE = 0x8000,
+ DAC_GAIN_MODE = 0x4000,
+ DAC_CHANNEL_SELECT_MASK = 0xf0000,
+ DAC_GROUP_SELECT_MASK = 0xf00000
+};
+
+static uint32_t dac_data_bits(uint16_t dac_data)
+{
+ return dac_data & DAC_DATA_MASK;
+}
+
+static uint32_t dac_channel_select_bits(unsigned channel)
+{
+ return (channel << 16) & DAC_CHANNEL_SELECT_MASK;
+}
+
+static uint32_t dac_group_select_bits(unsigned group)
+{
+ return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK;
+}
+
+static uint32_t dac_channel_and_group_select_bits(unsigned comedi_channel)
+{
+ return dac_channel_select_bits(comedi_channel % 8) |
+ dac_group_select_bits(comedi_channel / 8);
+}
+
+enum sync_output_contents {
+ SYNC_MODE = 0x1,
+ DAC_BUSY = 0x2, /* dac state machine is not ready */
+};
+
+enum sync_output_trigger_contents {
+ SYNC_TRIGGER_BITS = 0x0 /* any value works */
+};
+
+enum board_id_contents {
+ BOARD_ID_MASK = 0xf
+};
+
+static const struct comedi_lrange ao_ranges_1724 = {
+ 4, {
+ BIP_RANGE(10),
+ RANGE_mA(0, 20),
+ RANGE_mA(4, 20),
+ RANGE_unitless(0, 1)
+ }
+};
+
+/* this structure is for data unique to this hardware driver. */
+struct adv_pci1724_private {
+ int ao_value[NUM_AO_CHANNELS];
+ int offset_value[NUM_AO_CHANNELS];
+ int gain_value[NUM_AO_CHANNELS];
+};
+
+static int wait_for_dac_idle(struct comedi_device *dev)
+{
+ static const int timeout = 10000;
+ int i;
+
+ for (i = 0; i < timeout; ++i) {
+ if ((inl(dev->iobase + SYNC_OUTPUT_REG) & DAC_BUSY) == 0)
+ break;
+ udelay(1);
+ }
+ if (i == timeout) {
+ comedi_error(dev, "Timed out waiting for dac to become idle.");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int set_dac(struct comedi_device *dev, unsigned mode, unsigned channel,
+ unsigned data)
+{
+ int retval;
+ unsigned control_bits;
+
+ retval = wait_for_dac_idle(dev);
+ if (retval < 0)
+ return retval;
+
+ control_bits = mode;
+ control_bits |= dac_channel_and_group_select_bits(channel);
+ control_bits |= dac_data_bits(data);
+ outl(control_bits, dev->iobase + DAC_CONTROL_REG);
+ return 0;
+}
+
+static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+ int retval;
+ int i;
+
+ /* turn off synchronous mode */
+ outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+ for (i = 0; i < insn->n; ++i) {
+ retval = set_dac(dev, DAC_NORMAL_MODE, channel, data[i]);
+ if (retval < 0)
+ return retval;
+ devpriv->ao_value[channel] = data[i];
+ }
+ return insn->n;
+}
+
+static int ao_readback_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (devpriv->ao_value[channel] < 0) {
+ comedi_error(dev,
+ "Cannot read back channels which have not yet been written to.");
+ return -EIO;
+ }
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_value[channel];
+
+ return insn->n;
+}
+
+static int offset_write_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+ int retval;
+ int i;
+
+ /* turn off synchronous mode */
+ outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+ for (i = 0; i < insn->n; ++i) {
+ retval = set_dac(dev, DAC_OFFSET_MODE, channel, data[i]);
+ if (retval < 0)
+ return retval;
+ devpriv->offset_value[channel] = data[i];
+ }
+
+ return insn->n;
+}
+
+static int offset_read_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (devpriv->offset_value[channel] < 0) {
+ comedi_error(dev,
+ "Cannot read back channels which have not yet been written to.");
+ return -EIO;
+ }
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->offset_value[channel];
+
+ return insn->n;
+}
+
+static int gain_write_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+ int retval;
+ int i;
+
+ /* turn off synchronous mode */
+ outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+ for (i = 0; i < insn->n; ++i) {
+ retval = set_dac(dev, DAC_GAIN_MODE, channel, data[i]);
+ if (retval < 0)
+ return retval;
+ devpriv->gain_value[channel] = data[i];
+ }
+
+ return insn->n;
+}
+
+static int gain_read_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct adv_pci1724_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+ int i;
+
+ if (devpriv->gain_value[channel] < 0) {
+ comedi_error(dev,
+ "Cannot read back channels which have not yet been written to.");
+ return -EIO;
+ }
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->gain_value[channel];
+
+ return insn->n;
+}
+
+/* Allocate and initialize the subdevice structures.
+ */
+static int setup_subdevices(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* analog output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = NUM_AO_CHANNELS;
+ s->maxdata = 0x3fff;
+ s->range_table = &ao_ranges_1724;
+ s->insn_read = ao_readback_insn;
+ s->insn_write = ao_winsn;
+
+ /* offset calibration */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = NUM_AO_CHANNELS;
+ s->insn_read = offset_read_insn;
+ s->insn_write = offset_write_insn;
+ s->maxdata = 0x3fff;
+
+ /* gain calibration */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = NUM_AO_CHANNELS;
+ s->insn_read = gain_read_insn;
+ s->insn_write = gain_write_insn;
+ s->maxdata = 0x3fff;
+
+ return 0;
+}
+
+static int adv_pci1724_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct adv_pci1724_private *devpriv;
+ int i;
+ int retval;
+ unsigned int board_id;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* init software copies of output values to indicate we don't know
+ * what the output value is since it has never been written. */
+ for (i = 0; i < NUM_AO_CHANNELS; ++i) {
+ devpriv->ao_value[i] = -1;
+ devpriv->offset_value[i] = -1;
+ devpriv->gain_value[i] = -1;
+ }
+
+ retval = comedi_pci_enable(dev);
+ if (retval)
+ return retval;
+
+ dev->iobase = pci_resource_start(pcidev, 2);
+ board_id = inl(dev->iobase + BOARD_ID_REG) & BOARD_ID_MASK;
+ dev_info(dev->class_dev, "board id: %d\n", board_id);
+
+ retval = setup_subdevices(dev);
+ if (retval < 0)
+ return retval;
+
+ dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n",
+ dev->board_name, pci_name(pcidev), board_id);
+ return 0;
+}
+
+static struct comedi_driver adv_pci1724_driver = {
+ .driver_name = "adv_pci1724",
+ .module = THIS_MODULE,
+ .auto_attach = adv_pci1724_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int adv_pci1724_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &adv_pci1724_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1724_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
+
+static struct pci_driver adv_pci1724_pci_driver = {
+ .name = "adv_pci1724",
+ .id_table = adv_pci1724_pci_table,
+ .probe = adv_pci1724_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
+
+MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
+MODULE_DESCRIPTION("Advantech PCI-1724U Comedi 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 00000000000..2d966a87f2e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci_dio.c
@@ -0,0 +1,1222 @@
+/*
+ * comedi/drivers/adv_pci_dio.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * 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 <dobes@tesnet.cz>
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "8253.h"
+
+/* 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 */
+
+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 main_pci_region; /* main I/O PCI region */
+ enum hw_cards_id cardtype;
+ int nsubdevs;
+ 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 const struct dio_boardtype boardtypes[] = {
+ [TYPE_PCI1730] = {
+ .name = "pci1730",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1730,
+ .nsubdevs = 5,
+ .sdi[0] = { 16, PCI1730_DI, 2, 0, },
+ .sdi[1] = { 16, PCI1730_IDI, 2, 0, },
+ .sdo[0] = { 16, PCI1730_DO, 2, 0, },
+ .sdo[1] = { 16, PCI1730_IDO, 2, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1733] = {
+ .name = "pci1733",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1733,
+ .nsubdevs = 2,
+ .sdi[1] = { 32, PCI1733_IDI, 4, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1734] = {
+ .name = "pci1734",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1734,
+ .nsubdevs = 2,
+ .sdo[1] = { 32, PCI1734_IDO, 4, 0, },
+ .boardid = { 4, PCI173x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1735] = {
+ .name = "pci1735",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1735,
+ .nsubdevs = 4,
+ .sdi[0] = { 32, PCI1735_DI, 4, 0, },
+ .sdo[0] = { 32, PCI1735_DO, 4, 0, },
+ .boardid = { 4, PCI1735_BOARDID, 1, SDF_INTERNAL, },
+ .s8254[0] = { 3, PCI1735_C8254, 1, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1736] = {
+ .name = "pci1736",
+ .main_pci_region = PCI1736_MAINREG,
+ .cardtype = TYPE_PCI1736,
+ .nsubdevs = 3,
+ .sdi[1] = { 16, PCI1736_IDI, 2, 0, },
+ .sdo[1] = { 16, PCI1736_IDO, 2, 0, },
+ .boardid = { 4, PCI1736_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1739] = {
+ .name = "pci1739",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1739,
+ .nsubdevs = 2,
+ .sdio[0] = { 48, PCI1739_DIO, 2, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1750] = {
+ .name = "pci1750",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1750,
+ .nsubdevs = 2,
+ .sdi[1] = { 16, PCI1750_IDI, 2, 0, },
+ .sdo[1] = { 16, PCI1750_IDO, 2, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1751] = {
+ .name = "pci1751",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1751,
+ .nsubdevs = 3,
+ .sdio[0] = { 48, PCI1751_DIO, 2, 0, },
+ .s8254[0] = { 3, PCI1751_CNT, 1, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1752] = {
+ .name = "pci1752",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1752,
+ .nsubdevs = 3,
+ .sdo[0] = { 32, PCI1752_IDO, 2, 0, },
+ .sdo[1] = { 32, PCI1752_IDO2, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1753] = {
+ .name = "pci1753",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1753,
+ .nsubdevs = 4,
+ .sdio[0] = { 96, PCI1753_DIO, 4, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1753E] = {
+ .name = "pci1753e",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1753E,
+ .nsubdevs = 8,
+ .sdio[0] = { 96, PCI1753_DIO, 4, 0, },
+ .sdio[1] = { 96, PCI1753E_DIO, 4, 0, },
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1754] = {
+ .name = "pci1754",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1754,
+ .nsubdevs = 3,
+ .sdi[0] = { 32, PCI1754_IDI, 2, 0, },
+ .sdi[1] = { 32, PCI1754_IDI2, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1756] = {
+ .name = "pci1756",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1756,
+ .nsubdevs = 3,
+ .sdi[1] = { 32, PCI1756_IDI, 2, 0, },
+ .sdo[1] = { 32, PCI1756_IDO, 2, 0, },
+ .boardid = { 4, PCI175x_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+ [TYPE_PCI1760] = {
+ /* This card has its own 'attach' */
+ .name = "pci1760",
+ .main_pci_region = 0,
+ .cardtype = TYPE_PCI1760,
+ .nsubdevs = 4,
+ .io_access = IO_8b,
+ },
+ [TYPE_PCI1762] = {
+ .name = "pci1762",
+ .main_pci_region = PCIDIO_MAINREG,
+ .cardtype = TYPE_PCI1762,
+ .nsubdevs = 3,
+ .sdi[1] = { 16, PCI1762_IDI, 1, 0, },
+ .sdo[1] = { 16, PCI1762_RO, 1, 0, },
+ .boardid = { 4, PCI1762_BOARDID, 1, SDF_INTERNAL, },
+ .io_access = IO_16b,
+ },
+};
+
+struct pci_dio_private {
+ 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 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 insn->n;
+}
+
+/*
+==============================================================================
+*/
+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 insn->n;
+}
+
+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 (comedi_dio_update_state(s, data)) {
+ for (i = 0; i < d->regs; i++)
+ outb((s->state >> (8 * i)) & 0xff,
+ dev->iobase + d->addr + i);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+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 (comedi_dio_update_state(s, data)) {
+ for (i = 0; i < d->regs; i++)
+ outw((s->state >> (16 * i)) & 0xffff,
+ dev->iobase + d->addr + 2 * i);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+==============================================================================
+*/
+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 insn->n;
+}
+
+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 (comedi_dio_update_state(s, data)) {
+ omb[0] = s->state;
+ ret = pci1760_mbxrequest(dev, omb, imb);
+ if (!ret)
+ return ret;
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+==============================================================================
+*/
+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)
+{
+ struct pci_dio_private *devpriv = dev->private;
+ 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)
+{
+ struct pci_dio_private *devpriv = dev->private;
+ 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)
+{
+ const struct dio_boardtype *this_board = comedi_board(dev);
+
+ 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;
+ }
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci1760_attach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+
+ s = &dev->subdevices[0];
+ 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;
+
+ s = &dev->subdevices[1];
+ 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;
+
+ s = &dev->subdevices[2];
+ 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; */
+
+ s = &dev->subdevices[3];
+ 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; */
+
+ return 0;
+}
+
+/*
+==============================================================================
+*/
+static int pci_dio_add_di(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ const struct diosubd_data *d)
+{
+ const struct dio_boardtype *this_board = comedi_board(dev);
+
+ 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)
+{
+ const struct dio_boardtype *this_board = comedi_board(dev);
+
+ 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)
+{
+ 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 unsigned long pci_dio_override_cardtype(struct pci_dev *pcidev,
+ unsigned long cardtype)
+{
+ /*
+ * Change cardtype from TYPE_PCI1753 to TYPE_PCI1753E if expansion
+ * board available. Need to enable PCI device and request the main
+ * registers PCI BAR temporarily to perform the test.
+ */
+ if (cardtype != TYPE_PCI1753)
+ return cardtype;
+ if (pci_enable_device(pcidev) < 0)
+ return cardtype;
+ if (pci_request_region(pcidev, PCIDIO_MAINREG, "adv_pci_dio") == 0) {
+ /*
+ * This test is based on Advantech's "advdaq" driver source
+ * (which declares its module licence as "GPL" although the
+ * driver source does not include a "COPYING" file).
+ */
+ unsigned long reg =
+ pci_resource_start(pcidev, PCIDIO_MAINREG) + 53;
+
+ outb(0x05, reg);
+ if ((inb(reg) & 0x07) == 0x02) {
+ outb(0x02, reg);
+ if ((inb(reg) & 0x07) == 0x05)
+ cardtype = TYPE_PCI1753E;
+ }
+ pci_release_region(pcidev, PCIDIO_MAINREG);
+ }
+ pci_disable_device(pcidev);
+ return cardtype;
+}
+
+static int pci_dio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct dio_boardtype *this_board = NULL;
+ struct pci_dio_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret, subdev, i, j;
+
+ if (context < ARRAY_SIZE(boardtypes))
+ this_board = &boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, this_board->main_pci_region);
+
+ ret = comedi_alloc_subdevices(dev, this_board->nsubdevs);
+ if (ret)
+ 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++;
+ }
+
+ 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++;
+ }
+
+ for (i = 0; i < MAX_DIO_SUBDEVG; i++)
+ for (j = 0; j < this_board->sdio[i].regs; j++) {
+ s = &dev->subdevices[subdev];
+ ret = subdev_8255_init(dev, s, NULL,
+ dev->iobase +
+ this_board->sdio[i].addr +
+ SIZE_8255 * j);
+ if (ret)
+ return ret;
+ 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++;
+ }
+
+ 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++;
+ }
+
+ if (this_board->cardtype == TYPE_PCI1760)
+ pci1760_attach(dev);
+
+ devpriv->valid = 1;
+
+ pci_dio_reset(dev);
+
+ return 0;
+}
+
+static void pci_dio_detach(struct comedi_device *dev)
+{
+ struct pci_dio_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->valid)
+ pci_dio_reset(dev);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver adv_pci_dio_driver = {
+ .driver_name = "adv_pci_dio",
+ .module = THIS_MODULE,
+ .auto_attach = pci_dio_auto_attach,
+ .detach = pci_dio_detach,
+};
+
+static int adv_pci_dio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ unsigned long cardtype;
+
+ cardtype = pci_dio_override_cardtype(dev, id->driver_data);
+ return comedi_pci_auto_config(dev, &adv_pci_dio_driver, cardtype);
+}
+
+static const struct pci_device_id adv_pci_dio_pci_table[] = {
+ { PCI_VDEVICE(ADVANTECH, 0x1730), TYPE_PCI1730 },
+ { PCI_VDEVICE(ADVANTECH, 0x1733), TYPE_PCI1733 },
+ { PCI_VDEVICE(ADVANTECH, 0x1734), TYPE_PCI1734 },
+ { PCI_VDEVICE(ADVANTECH, 0x1735), TYPE_PCI1735 },
+ { PCI_VDEVICE(ADVANTECH, 0x1736), TYPE_PCI1736 },
+ { PCI_VDEVICE(ADVANTECH, 0x1739), TYPE_PCI1739 },
+ { PCI_VDEVICE(ADVANTECH, 0x1750), TYPE_PCI1750 },
+ { PCI_VDEVICE(ADVANTECH, 0x1751), TYPE_PCI1751 },
+ { PCI_VDEVICE(ADVANTECH, 0x1752), TYPE_PCI1752 },
+ { PCI_VDEVICE(ADVANTECH, 0x1753), TYPE_PCI1753 },
+ { PCI_VDEVICE(ADVANTECH, 0x1754), TYPE_PCI1754 },
+ { PCI_VDEVICE(ADVANTECH, 0x1756), TYPE_PCI1756 },
+ { PCI_VDEVICE(ADVANTECH, 0x1760), TYPE_PCI1760 },
+ { PCI_VDEVICE(ADVANTECH, 0x1762), TYPE_PCI1762 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci_dio_pci_table);
+
+static struct pci_driver adv_pci_dio_pci_driver = {
+ .name = "adv_pci_dio",
+ .id_table = adv_pci_dio_pci_table,
+ .probe = adv_pci_dio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci_dio_driver, adv_pci_dio_pci_driver);
+
+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 00000000000..324746b1493
--- /dev/null
+++ b/drivers/staging/comedi/drivers/aio_aio12_8.c
@@ -0,0 +1,272 @@
+/*
+
+ 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.
+*/
+
+/*
+
+Driver: aio_aio12_8
+Description: Access I/O Products PC-104 AIO12-8 Analog I/O Board
+Author: Pablo Mejia <pablo.mejia@cctechnol.com>
+Devices: [Access I/O] PC-104 AIO12-8 (aio_aio12_8)
+ [Access I/O] PC-104 AI12-8 (aio_ai12_8)
+ [Access I/O] PC-104 AO12-8 (aio_ao12_8)
+Status: experimental
+
+Configuration Options:
+ [0] - I/O port base address
+
+Notes:
+
+ Only synchronous operations are supported.
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include "8255.h"
+
+/*
+ * Register map
+ */
+#define AIO12_8_STATUS_REG 0x00
+#define AIO12_8_STATUS_ADC_EOC (1 << 7)
+#define AIO12_8_STATUS_PORT_C_COS (1 << 6)
+#define AIO12_8_STATUS_IRQ_ENA (1 << 2)
+#define AIO12_8_INTERRUPT_REG 0x01
+#define AIO12_8_INTERRUPT_ADC (1 << 7)
+#define AIO12_8_INTERRUPT_COS (1 << 6)
+#define AIO12_8_INTERRUPT_COUNTER1 (1 << 5)
+#define AIO12_8_INTERRUPT_PORT_C3 (1 << 4)
+#define AIO12_8_INTERRUPT_PORT_C0 (1 << 3)
+#define AIO12_8_INTERRUPT_ENA (1 << 2)
+#define AIO12_8_ADC_REG 0x02
+#define AIO12_8_ADC_MODE_NORMAL (0 << 6)
+#define AIO12_8_ADC_MODE_INT_CLK (1 << 6)
+#define AIO12_8_ADC_MODE_STANDBY (2 << 6)
+#define AIO12_8_ADC_MODE_POWERDOWN (3 << 6)
+#define AIO12_8_ADC_ACQ_3USEC (0 << 5)
+#define AIO12_8_ADC_ACQ_PROGRAM (1 << 5)
+#define AIO12_8_ADC_RANGE(x) ((x) << 3)
+#define AIO12_8_ADC_CHAN(x) ((x) << 0)
+#define AIO12_8_DAC_REG(x) (0x04 + (x) * 2)
+#define AIO12_8_8254_BASE_REG 0x0c
+#define AIO12_8_8255_BASE_REG 0x10
+#define AIO12_8_DIO_CONTROL_REG 0x14
+#define AIO12_8_DIO_CONTROL_TST (1 << 0)
+#define AIO12_8_ADC_TRIGGER_REG 0x15
+#define AIO12_8_ADC_TRIGGER_RANGE(x) ((x) << 3)
+#define AIO12_8_ADC_TRIGGER_CHAN(x) ((x) << 0)
+#define AIO12_8_TRIGGER_REG 0x16
+#define AIO12_8_TRIGGER_ADTRIG (1 << 1)
+#define AIO12_8_TRIGGER_DACTRIG (1 << 0)
+#define AIO12_8_COS_REG 0x17
+#define AIO12_8_DAC_ENABLE_REG 0x18
+#define AIO12_8_DAC_ENABLE_REF_ENA (1 << 0)
+
+struct aio12_8_boardtype {
+ const char *name;
+ int ai_nchan;
+ int ao_nchan;
+};
+
+static const struct aio12_8_boardtype board_types[] = {
+ {
+ .name = "aio_aio12_8",
+ .ai_nchan = 8,
+ .ao_nchan = 4,
+ }, {
+ .name = "aio_ai12_8",
+ .ai_nchan = 8,
+ }, {
+ .name = "aio_ao12_8",
+ .ao_nchan = 4,
+ },
+};
+
+struct aio12_8_private {
+ unsigned int ao_readback[4];
+};
+
+static int aio_aio12_8_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + AIO12_8_STATUS_REG);
+ if (status & AIO12_8_STATUS_ADC_EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int aio_aio12_8_ai_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned char control;
+ int ret;
+ int n;
+
+ /*
+ * Setup the control byte for internal 2MHz clock, 3uS conversion,
+ * at the desired range of the requested channel.
+ */
+ control = AIO12_8_ADC_MODE_NORMAL | AIO12_8_ADC_ACQ_3USEC |
+ AIO12_8_ADC_RANGE(range) | AIO12_8_ADC_CHAN(chan);
+
+ /* Read status to clear EOC latch */
+ inb(dev->iobase + AIO12_8_STATUS_REG);
+
+ for (n = 0; n < insn->n; n++) {
+ /* Setup and start conversion */
+ outb(control, dev->iobase + AIO12_8_ADC_REG);
+
+ /* Wait for conversion to complete */
+ ret = comedi_timeout(dev, s, insn, aio_aio12_8_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[n] = inw(dev->iobase + AIO12_8_ADC_REG) & s->maxdata;
+ }
+
+ return insn->n;
+}
+
+static int aio_aio12_8_ao_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct aio12_8_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int val = devpriv->ao_readback[chan];
+ int i;
+
+ 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)
+{
+ struct aio12_8_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long port = dev->iobase + AIO12_8_DAC_REG(chan);
+ unsigned int val = 0;
+ int i;
+
+ /* enable DACs */
+ outb(AIO12_8_DAC_ENABLE_REF_ENA, dev->iobase + AIO12_8_DAC_ENABLE_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ outw(val, port);
+ }
+
+ devpriv->ao_readback[chan] = val;
+
+ 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)
+{
+ const struct aio12_8_boardtype *board = comedi_board(dev);
+ struct aio12_8_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 32);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (board->ai_nchan) {
+ /* Analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = board->ai_nchan;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_aio_aio12_8;
+ s->insn_read = aio_aio12_8_ai_read;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ if (board->ao_nchan) {
+ /* Analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_aio_aio12_8;
+ s->insn_read = aio_aio12_8_ao_read;
+ s->insn_write = aio_aio12_8_ao_write;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* 8255 Digital i/o subdevice */
+ ret = subdev_8255_init(dev, s, NULL,
+ dev->iobase + AIO12_8_8255_BASE_REG);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[3];
+ /* 8254 counter/timer subdevice */
+ s->type = COMEDI_SUBD_UNUSED;
+
+ return 0;
+}
+
+static struct comedi_driver aio_aio12_8_driver = {
+ .driver_name = "aio_aio12_8",
+ .module = THIS_MODULE,
+ .attach = aio_aio12_8_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &board_types[0].name,
+ .num_names = ARRAY_SIZE(board_types),
+ .offset = sizeof(struct aio12_8_boardtype),
+};
+module_comedi_driver(aio_aio12_8_driver);
+
+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 00000000000..781104aa533
--- /dev/null
+++ b/drivers/staging/comedi/drivers/aio_iiro_16.c
@@ -0,0 +1,114 @@
+/*
+
+ 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.
+*/
+
+/*
+
+Driver: aio_iiro_16
+Description: Access I/O Products PC-104 IIRO16 Relay And Isolated Input Board
+Author: Zachary Ware <zach.ware@cctechnol.com>
+Devices:
+ [Access I/O] PC-104 AIO12-8
+Status: experimental
+
+Configuration Options:
+ [0] - I/O port base address
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#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
+
+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 (comedi_dio_update_state(s, data)) {
+ 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 insn->n;
+}
+
+static int aio_iiro_16_dio_insn_bits_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ 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 insn->n;
+}
+
+static int aio_iiro_16_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], AIO_IIRO_16_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ 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;
+
+ return 0;
+}
+
+static struct comedi_driver aio_iiro_16_driver = {
+ .driver_name = "aio_iiro_16",
+ .module = THIS_MODULE,
+ .attach = aio_iiro_16_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(aio_iiro_16_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amcc_s5933.h b/drivers/staging/comedi/drivers/amcc_s5933.h
new file mode 100644
index 00000000000..2ba73644461
--- /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 <dobes@tesnet.cz>
+
+ Inspirated from general-purpose AMCC S5933 PCI Matchmaker driver
+ made by Andrea Cisternino <acister@pcape1.pi.infn.it>
+ 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 00000000000..dc1dee79fc1
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200.c
@@ -0,0 +1,305 @@
+/*
+ comedi/drivers/amplc_dio200.c
+
+ Driver for Amplicon PC212E, PC214E, PC215E, PC218E, PC272E.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+ * Driver: amplc_dio200
+ * Description: Amplicon 200 Series ISA Digital I/O
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PC212E (pc212e), PC214E (pc214e), PC215E (pc215e),
+ * PC218E (pc218e), PC272E (pc272e)
+ * Updated: Mon, 18 Mar 2013 14:40:41 +0000
+ *
+ * Status: works
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional, but commands won't work without it)
+ *
+ * Passing a zero for an option is the same as leaving it unspecified.
+ *
+ * SUBDEVICES
+ *
+ * PC212E PC214E PC215E
+ * ------------- ------------- -------------
+ * Subdevices 6 4 5
+ * 0 PPI-X PPI-X PPI-X
+ * 1 CTR-Y1 PPI-Y PPI-Y
+ * 2 CTR-Y2 CTR-Z1* CTR-Z1
+ * 3 CTR-Z1 INTERRUPT* CTR-Z2
+ * 4 CTR-Z2 INTERRUPT
+ * 5 INTERRUPT
+ *
+ * PC218E PC272E
+ * ------------- -------------
+ * Subdevices 7 4
+ * 0 CTR-X1 PPI-X
+ * 1 CTR-X2 PPI-Y
+ * 2 CTR-Y1 PPI-Z
+ * 3 CTR-Y2 INTERRUPT
+ * 4 CTR-Z1
+ * 5 CTR-Z2
+ * 6 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
+ *
+ * PC212E PC214E PC215E
+ * ------------- ------------- -------------
+ * Sources 6 1 6
+ * 0 PPI-X-C0 JUMPER-J5 PPI-X-C0
+ * 1 PPI-X-C3 PPI-X-C3
+ * 2 CTR-Y1-OUT1 PPI-Y-C0
+ * 3 CTR-Y2-OUT1 PPI-Y-C3
+ * 4 CTR-Z1-OUT1 CTR-Z1-OUT1
+ * 5 CTR-Z2-OUT1 CTR-Z2-OUT1
+ *
+ * PC218E PC272E
+ * ------------- -------------
+ * Sources 6 6
+ * 0 CTR-X1-OUT1 PPI-X-C0
+ * 1 CTR-X2-OUT1 PPI-X-C3
+ * 2 CTR-Y1-OUT1 PPI-Y-C0
+ * 3 CTR-Y2-OUT1 PPI-Y-C3
+ * 4 CTR-Z1-OUT1 PPI-Z-C0
+ * 5 CTR-Z2-OUT1 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. 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 <linux/module.h>
+#include "../comedidev.h"
+
+#include "amplc_dio200.h"
+
+/*
+ * Board descriptions.
+ */
+static const struct dio200_board dio200_isa_boards[] = {
+ {
+ .name = "pc212e",
+ .bustype = isa_bustype,
+ .mainsize = DIO200_IO_SIZE,
+ .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 = true,
+ .has_clk_gat_sce = true,
+ },
+ },
+ {
+ .name = "pc214e",
+ .bustype = isa_bustype,
+ .mainsize = DIO200_IO_SIZE,
+ .layout = {
+ .n_subdevs = 4,
+ .sdtype = {sd_8255, sd_8255, sd_8254, sd_intr},
+ .sdinfo = {0x00, 0x08, 0x10, 0x01},
+ },
+ },
+ {
+ .name = "pc215e",
+ .bustype = isa_bustype,
+ .mainsize = DIO200_IO_SIZE,
+ .layout = {
+ .n_subdevs = 5,
+ .sdtype = {sd_8255, sd_8255, sd_8254, sd_8254, sd_intr},
+ .sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F},
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ },
+ },
+ {
+ .name = "pc218e",
+ .bustype = isa_bustype,
+ .mainsize = DIO200_IO_SIZE,
+ .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 = true,
+ .has_clk_gat_sce = true,
+ },
+ },
+ {
+ .name = "pc272e",
+ .bustype = isa_bustype,
+ .mainsize = DIO200_IO_SIZE,
+ .layout = {
+ .n_subdevs = 4,
+ .sdtype = {sd_8255, sd_8255, sd_8255, sd_intr},
+ .sdinfo = {0x00, 0x08, 0x10, 0x3F},
+ .has_int_sce = true,
+ },
+ },
+};
+
+static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv;
+ unsigned int irq;
+ int ret;
+
+ irq = it->options[1];
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], thisboard->mainsize);
+ if (ret)
+ return ret;
+ devpriv->io.u.iobase = dev->iobase;
+ devpriv->io.regtype = io_regtype;
+ return amplc_dio200_common_attach(dev, irq, 0);
+}
+
+static void dio200_detach(struct comedi_device *dev)
+{
+ amplc_dio200_common_detach(dev);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver amplc_dio200_driver = {
+ .driver_name = "amplc_dio200",
+ .module = THIS_MODULE,
+ .attach = dio200_attach,
+ .detach = dio200_detach,
+ .board_name = &dio200_isa_boards[0].name,
+ .offset = sizeof(struct dio200_board),
+ .num_names = ARRAY_SIZE(dio200_isa_boards),
+};
+module_comedi_driver(amplc_dio200_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series ISA DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_dio200.h b/drivers/staging/comedi/drivers/amplc_dio200.h
new file mode 100644
index 00000000000..43160b9944b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200.h
@@ -0,0 +1,90 @@
+/*
+ comedi/drivers/amplc_dio.h
+
+ Header for amplc_dio200.c, amplc_dio200_common.c and
+ amplc_dio200_pci.c.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef AMPLC_DIO200_H_INCLUDED
+#define AMPLC_DIO200_H_INCLUDED
+
+/* 200 series register area sizes */
+#define DIO200_IO_SIZE 0x20
+#define DIO200_PCIE_IO_SIZE 0x4000
+
+/*
+ * Register region.
+ */
+enum dio200_regtype { no_regtype = 0, io_regtype, mmio_regtype };
+struct dio200_region {
+ union {
+ unsigned long iobase; /* I/O base address */
+ unsigned char __iomem *membase; /* mapped MMIO base address */
+ } u;
+ enum dio200_regtype regtype;
+};
+
+/*
+ * Subdevice types.
+ */
+enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254, sd_timer };
+
+#define DIO200_MAX_SUBDEVS 8
+#define DIO200_MAX_ISNS 6
+
+/*
+ * Board descriptions.
+ */
+
+struct dio200_layout {
+ 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 */
+ bool has_int_sce:1; /* has interrupt enable/status reg */
+ bool has_clk_gat_sce:1; /* has clock/gate selection registers */
+ bool has_enhancements:1; /* has enhanced features */
+};
+
+enum dio200_bustype { isa_bustype, pci_bustype };
+
+struct dio200_board {
+ const char *name;
+ struct dio200_layout layout;
+ enum dio200_bustype bustype;
+ unsigned char mainbar;
+ unsigned char mainshift;
+ unsigned int mainsize;
+};
+
+/*
+ * Comedi device private data.
+ */
+struct dio200_private {
+ struct dio200_region io; /* Register region */
+ int intr_sd;
+};
+
+int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
+ unsigned long req_irq_flags);
+
+void amplc_dio200_common_detach(struct comedi_device *dev);
+
+/* Used by initialization of PCIe boards. */
+void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val);
+
+#endif
diff --git a/drivers/staging/comedi/drivers/amplc_dio200_common.c b/drivers/staging/comedi/drivers/amplc_dio200_common.c
new file mode 100644
index 00000000000..3edaa4028da
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200_common.c
@@ -0,0 +1,1223 @@
+/*
+ comedi/drivers/amplc_dio200_common.c
+
+ Common support code for "amplc_dio200" and "amplc_dio200_pci".
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "amplc_dio200.h"
+#include "comedi_fc.h"
+#include "8253.h"
+
+/* 8255 control register bits */
+#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
+
+/* 200 series registers */
+#define DIO200_IO_SIZE 0x20
+#define DIO200_PCIE_IO_SIZE 0x4000
+#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 */
+/* Extra registers for new PCIe boards */
+#define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */
+#define DIO200_VERSION 0x24 /* Hardware version register */
+#define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */
+#define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */
+
+/*
+ * Functions 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, or 0 to 31 for "enhanced" boards.
+ */
+static unsigned char clk_gat_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return (which << 5) | (chan << 3) |
+ ((source & 030) << 3) | (source & 007);
+}
+
+static unsigned char clk_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return clk_gat_sce(which, chan, source);
+}
+
+static unsigned char gat_sce(unsigned int which, unsigned int chan,
+ unsigned int source)
+{
+ return clk_gat_sce(which, chan, source);
+}
+
+/*
+ * Periods of the internal clock sources in nanoseconds.
+ */
+static const unsigned int clock_period[32] = {
+ [1] = 100, /* 10 MHz */
+ [2] = 1000, /* 1 MHz */
+ [3] = 10000, /* 100 kHz */
+ [4] = 100000, /* 10 kHz */
+ [5] = 1000000, /* 1 kHz */
+ [11] = 50, /* 20 MHz (enhanced boards) */
+ /* clock sources 12 and later reserved for enhanced boards */
+};
+
+/*
+ * Timestamp timer configuration register (for new PCIe boards).
+ */
+#define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */
+#define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */
+#define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */
+
+/*
+ * Periods of the timestamp timer clock sources in nanoseconds.
+ */
+static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = {
+ 1, /* 1 nanosecond (but with 20 ns granularity). */
+ 1000, /* 1 microsecond. */
+ 1000000, /* 1 millisecond. */
+};
+
+struct dio200_subdev_8254 {
+ unsigned int ofs; /* Counter base offset */
+ unsigned int clk_sce_ofs; /* CLK_SCE base address */
+ unsigned int gat_sce_ofs; /* GAT_SCE base address */
+ int which; /* Bit 5 of CLK_SCE or GAT_SCE */
+ unsigned int clock_src[3]; /* Current clock sources */
+ unsigned int gate_src[3]; /* Current gate sources */
+ spinlock_t spinlock;
+};
+
+struct dio200_subdev_8255 {
+ unsigned int ofs; /* DIO base offset */
+};
+
+struct dio200_subdev_intr {
+ spinlock_t spinlock;
+ unsigned int ofs;
+ unsigned int valid_isns;
+ unsigned int enabled_isns;
+ unsigned int stopcount;
+ bool active:1;
+};
+
+static inline const struct dio200_layout *
+dio200_board_layout(const struct dio200_board *board)
+{
+ return &board->layout;
+}
+
+static inline const struct dio200_layout *
+dio200_dev_layout(struct comedi_device *dev)
+{
+ return dio200_board_layout(comedi_board(dev));
+}
+
+/*
+ * Read 8-bit register.
+ */
+static unsigned char dio200_read8(struct comedi_device *dev,
+ unsigned int offset)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ offset <<= thisboard->mainshift;
+ if (devpriv->io.regtype == io_regtype)
+ return inb(devpriv->io.u.iobase + offset);
+ else
+ return readb(devpriv->io.u.membase + offset);
+}
+
+/*
+ * Write 8-bit register.
+ */
+static void dio200_write8(struct comedi_device *dev, unsigned int offset,
+ unsigned char val)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ offset <<= thisboard->mainshift;
+ if (devpriv->io.regtype == io_regtype)
+ outb(val, devpriv->io.u.iobase + offset);
+ else
+ writeb(val, devpriv->io.u.membase + offset);
+}
+
+/*
+ * Read 32-bit register.
+ */
+static unsigned int dio200_read32(struct comedi_device *dev,
+ unsigned int offset)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ offset <<= thisboard->mainshift;
+ if (devpriv->io.regtype == io_regtype)
+ return inl(devpriv->io.u.iobase + offset);
+ else
+ return readl(devpriv->io.u.membase + offset);
+}
+
+/*
+ * Write 32-bit register.
+ */
+static void dio200_write32(struct comedi_device *dev, unsigned int offset,
+ unsigned int val)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ offset <<= thisboard->mainshift;
+ if (devpriv->io.regtype == io_regtype)
+ outl(val, devpriv->io.u.iobase + offset);
+ else
+ writel(val, devpriv->io.u.membase + offset);
+}
+
+/*
+ * '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)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_intr *subpriv = s->private;
+
+ if (layout->has_int_sce) {
+ /* Just read the interrupt status register. */
+ data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns;
+ } else {
+ /* No interrupt status register. */
+ data[0] = 0;
+ }
+
+ return insn->n;
+}
+
+/*
+ * Called to stop acquisition for an 'INTERRUPT' subdevice.
+ */
+static void dio200_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_intr *subpriv = s->private;
+
+ subpriv->active = false;
+ subpriv->enabled_isns = 0;
+ if (layout->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, 0);
+}
+
+/*
+ * 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;
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_intr *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int retval = 0;
+
+ if (cmd->stop_src == TRIG_COUNT && subpriv->stopcount == 0) {
+ /* An empty acquisition! */
+ s->async->events |= COMEDI_CB_EOA;
+ subpriv->active = false;
+ 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 (layout->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, isn_bits);
+ }
+
+ return retval;
+}
+
+static int dio200_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct dio200_subdev_intr *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+ int event = 0;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ 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;
+}
+
+static void dio200_read_scan_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int triggered)
+{
+ struct dio200_subdev_intr *subpriv = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned short val;
+ unsigned int n, ch;
+
+ val = 0;
+ for (n = 0; n < cmd->chanlist_len; n++) {
+ ch = CR_CHAN(cmd->chanlist[n]);
+ if (triggered & (1U << ch))
+ val |= (1U << n);
+ }
+ /* Write the scan to the buffer. */
+ if (comedi_buf_put(s, 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 (cmd->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);
+ }
+ }
+ }
+}
+
+/*
+ * 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)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ 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 (layout->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 = (dio200_read8(dev, subpriv->ofs) &
+ subpriv->valid_isns & ~triggered)) != 0) {
+ triggered |= intstat;
+ cur_enabled &= ~triggered;
+ dio200_write8(dev, subpriv->ofs, cur_enabled);
+ }
+ } 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 (layout->has_int_sce)
+ dio200_write8(dev, subpriv->ofs, cur_enabled);
+
+ 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. */
+ dio200_read_scan_intr(dev, s, triggered);
+ }
+ }
+ 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;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* any count allowed */
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ 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 = true;
+
+ /* Set up end of acquisition. */
+ if (cmd->stop_src == TRIG_COUNT)
+ subpriv->stopcount = cmd->stop_arg;
+ else /* TRIG_NONE */
+ subpriv->stopcount = 0;
+
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = dio200_inttrig_start_intr;
+ else /* TRIG_NOW */
+ event = dio200_start_intr(dev, s);
+
+ 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 int offset, unsigned valid_isns)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_intr *subpriv;
+
+ subpriv = comedi_alloc_spriv(s, sizeof(*subpriv));
+ if (!subpriv)
+ return -ENOMEM;
+
+ subpriv->ofs = offset;
+ subpriv->valid_isns = valid_isns;
+ spin_lock_init(&subpriv->spinlock);
+
+ if (layout->has_int_sce)
+ /* Disable interrupt sources. */
+ dio200_write8(dev, subpriv->ofs, 0);
+
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
+ if (layout->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;
+}
+
+/*
+ * Interrupt service routine.
+ */
+static irqreturn_t dio200_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct dio200_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int handled;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ if (devpriv->intr_sd >= 0) {
+ s = &dev->subdevices[devpriv->intr_sd];
+ handled = dio200_handle_read_intr(dev, s);
+ } else {
+ handled = 0;
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+/*
+ * Read an '8254' counter subdevice channel.
+ */
+static unsigned int
+dio200_subdev_8254_read_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int chan)
+{
+ struct dio200_subdev_8254 *subpriv = s->private;
+ unsigned int val;
+
+ /* latch counter */
+ val = chan << 6;
+ dio200_write8(dev, subpriv->ofs + i8254_control_reg, val);
+ /* read lsb, msb */
+ val = dio200_read8(dev, subpriv->ofs + chan);
+ val += dio200_read8(dev, subpriv->ofs + chan) << 8;
+ return val;
+}
+
+/*
+ * Write an '8254' subdevice channel.
+ */
+static void
+dio200_subdev_8254_write_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int chan,
+ unsigned int count)
+{
+ struct dio200_subdev_8254 *subpriv = s->private;
+
+ /* write lsb, msb */
+ dio200_write8(dev, subpriv->ofs + chan, count & 0xff);
+ dio200_write8(dev, subpriv->ofs + chan, (count >> 8) & 0xff);
+}
+
+/*
+ * Set mode of an '8254' subdevice channel.
+ */
+static void
+dio200_subdev_8254_set_mode(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int chan,
+ unsigned int mode)
+{
+ struct dio200_subdev_8254 *subpriv = s->private;
+ unsigned int byte;
+
+ byte = chan << 6;
+ byte |= 0x30; /* access order: lsb, msb */
+ byte |= (mode & 0xf); /* counter mode and BCD|binary */
+ dio200_write8(dev, subpriv->ofs + i8254_control_reg, byte);
+}
+
+/*
+ * Read status byte of an '8254' counter subdevice channel.
+ */
+static unsigned int
+dio200_subdev_8254_status(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int chan)
+{
+ struct dio200_subdev_8254 *subpriv = s->private;
+
+ /* latch status */
+ dio200_write8(dev, subpriv->ofs + i8254_control_reg,
+ 0xe0 | (2 << chan));
+ /* read status */
+ return dio200_read8(dev, subpriv->ofs + chan);
+}
+
+/*
+ * 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 int n;
+ unsigned long flags;
+
+ for (n = 0; n < insn->n; n++) {
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ data[n] = dio200_subdev_8254_read_chan(dev, s, chan);
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+ }
+ return insn->n;
+}
+
+/*
+ * 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 int n;
+ unsigned long flags;
+
+ for (n = 0; n < insn->n; n++) {
+ spin_lock_irqsave(&subpriv->spinlock, flags);
+ dio200_subdev_8254_write_chan(dev, s, chan, data[n]);
+ spin_unlock_irqrestore(&subpriv->spinlock, flags);
+ }
+ return insn->n;
+}
+
+/*
+ * Set gate source for an '8254' counter subdevice channel.
+ */
+static int
+dio200_subdev_8254_set_gate_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int counter_number,
+ unsigned int gate_src)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_8254 *subpriv = s->private;
+ unsigned char byte;
+
+ if (!layout->has_clk_gat_sce)
+ return -1;
+ if (counter_number > 2)
+ return -1;
+ if (gate_src > (layout->has_enhancements ? 31 : 7))
+ return -1;
+
+ subpriv->gate_src[counter_number] = gate_src;
+ byte = gat_sce(subpriv->which, counter_number, gate_src);
+ dio200_write8(dev, subpriv->gat_sce_ofs, byte);
+
+ return 0;
+}
+
+/*
+ * Get gate source for an '8254' counter subdevice channel.
+ */
+static int
+dio200_subdev_8254_get_gate_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int counter_number)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_8254 *subpriv = s->private;
+
+ if (!layout->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_subdev_8254_set_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int counter_number,
+ unsigned int clock_src)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_8254 *subpriv = s->private;
+ unsigned char byte;
+
+ if (!layout->has_clk_gat_sce)
+ return -1;
+ if (counter_number > 2)
+ return -1;
+ if (clock_src > (layout->has_enhancements ? 31 : 7))
+ return -1;
+
+ subpriv->clock_src[counter_number] = clock_src;
+ byte = clk_sce(subpriv->which, counter_number, clock_src);
+ dio200_write8(dev, subpriv->clk_sce_ofs, byte);
+
+ return 0;
+}
+
+/*
+ * Get clock source for an '8254' counter subdevice channel.
+ */
+static int
+dio200_subdev_8254_get_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int counter_number,
+ unsigned int *period_ns)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_8254 *subpriv = s->private;
+ unsigned clock_src;
+
+ if (!layout->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:
+ if (data[1] > (I8254_MODE5 | I8254_BCD))
+ ret = -EINVAL;
+ else
+ dio200_subdev_8254_set_mode(dev, s, chan, data[1]);
+ break;
+ case INSN_CONFIG_8254_READ_STATUS:
+ data[1] = dio200_subdev_8254_status(dev, s, chan);
+ break;
+ case INSN_CONFIG_SET_GATE_SRC:
+ ret = dio200_subdev_8254_set_gate_src(dev, s, chan, data[2]);
+ if (ret < 0)
+ ret = -EINVAL;
+ break;
+ case INSN_CONFIG_GET_GATE_SRC:
+ ret = dio200_subdev_8254_get_gate_src(dev, s, chan);
+ if (ret < 0) {
+ ret = -EINVAL;
+ break;
+ }
+ data[2] = ret;
+ break;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ ret = dio200_subdev_8254_set_clock_src(dev, s, chan, data[1]);
+ if (ret < 0)
+ ret = -EINVAL;
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ ret = dio200_subdev_8254_get_clock_src(dev, s, 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.
+ */
+static int
+dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned int offset)
+{
+ const struct dio200_layout *layout = dio200_dev_layout(dev);
+ struct dio200_subdev_8254 *subpriv;
+ unsigned int chan;
+
+ subpriv = comedi_alloc_spriv(s, sizeof(*subpriv));
+ if (!subpriv)
+ return -ENOMEM;
+
+ 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->ofs = offset;
+ if (layout->has_clk_gat_sce) {
+ /* Derive CLK_SCE and GAT_SCE register offsets from
+ * 8254 offset. */
+ subpriv->clk_sce_ofs = DIO200_XCLK_SCE + (offset >> 3);
+ subpriv->gat_sce_ofs = DIO200_XGAT_SCE + (offset >> 3);
+ subpriv->which = (offset >> 2) & 1;
+ }
+
+ /* Initialize channels. */
+ for (chan = 0; chan < 3; chan++) {
+ dio200_subdev_8254_set_mode(dev, s, chan,
+ I8254_MODE0 | I8254_BINARY);
+ if (layout->has_clk_gat_sce) {
+ /* Gate source 0 is VCC (logic 1). */
+ dio200_subdev_8254_set_gate_src(dev, s, chan, 0);
+ /* Clock source 0 is the dedicated clock input. */
+ dio200_subdev_8254_set_clock_src(dev, s, chan, 0);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This function sets I/O directions for an '8255' DIO subdevice.
+ */
+static void dio200_subdev_8255_set_dir(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dio200_subdev_8255 *subpriv = s->private;
+ 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;
+ dio200_write8(dev, subpriv->ofs + 3, config);
+}
+
+static int dio200_subdev_8255_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dio200_subdev_8255 *subpriv = s->private;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ dio200_write8(dev, subpriv->ofs, s->state & 0xff);
+ if (mask & 0xff00)
+ dio200_write8(dev, subpriv->ofs + 1,
+ (s->state >> 8) & 0xff);
+ if (mask & 0xff0000)
+ dio200_write8(dev, subpriv->ofs + 2,
+ (s->state >> 16) & 0xff);
+ }
+
+ val = dio200_read8(dev, subpriv->ofs);
+ val |= dio200_read8(dev, subpriv->ofs + 1) << 8;
+ val |= dio200_read8(dev, subpriv->ofs + 2) << 16;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+/*
+ * Handle 'insn_config' for an '8255' DIO subdevice.
+ */
+static int dio200_subdev_8255_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ dio200_subdev_8255_set_dir(dev, s);
+
+ return insn->n;
+}
+
+/*
+ * This function initializes an '8255' DIO subdevice.
+ *
+ * offset is the offset to the 8255 chip.
+ */
+static int dio200_subdev_8255_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int offset)
+{
+ struct dio200_subdev_8255 *subpriv;
+
+ subpriv = comedi_alloc_spriv(s, sizeof(*subpriv));
+ if (!subpriv)
+ return -ENOMEM;
+
+ subpriv->ofs = offset;
+
+ 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->insn_bits = dio200_subdev_8255_bits;
+ s->insn_config = dio200_subdev_8255_config;
+ dio200_subdev_8255_set_dir(dev, s);
+ return 0;
+}
+
+/*
+ * Handle 'insn_read' for a timer subdevice.
+ */
+static int dio200_subdev_timer_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int n;
+
+ for (n = 0; n < insn->n; n++)
+ data[n] = dio200_read32(dev, DIO200_TS_COUNT);
+ return n;
+}
+
+/*
+ * Reset timer subdevice.
+ */
+static void dio200_subdev_timer_reset(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int clock;
+
+ clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
+ dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET);
+ dio200_write32(dev, DIO200_TS_CONFIG, clock);
+}
+
+/*
+ * Get timer subdevice clock source and period.
+ */
+static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *src,
+ unsigned int *period)
+{
+ unsigned int clk;
+
+ clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK;
+ *src = clk;
+ *period = (clk < ARRAY_SIZE(ts_clock_period)) ?
+ ts_clock_period[clk] : 0;
+}
+
+/*
+ * Set timer subdevice clock source.
+ */
+static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int src)
+{
+ if (src > TS_CONFIG_MAX_CLK_SRC)
+ return -EINVAL;
+ dio200_write32(dev, DIO200_TS_CONFIG, src);
+ return 0;
+}
+
+/*
+ * Handle 'insn_config' for a timer subdevice.
+ */
+static int dio200_subdev_timer_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret = 0;
+
+ switch (data[0]) {
+ case INSN_CONFIG_RESET:
+ dio200_subdev_timer_reset(dev, s);
+ break;
+ case INSN_CONFIG_SET_CLOCK_SRC:
+ ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]);
+ if (ret < 0)
+ ret = -EINVAL;
+ break;
+ case INSN_CONFIG_GET_CLOCK_SRC:
+ dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret < 0 ? ret : insn->n;
+}
+
+/*
+ * This function initializes a timer subdevice.
+ *
+ * Uses the timestamp timer registers. There is only one timestamp timer.
+ */
+static int dio200_subdev_timer_init(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 1;
+ s->maxdata = 0xFFFFFFFF;
+ s->insn_read = dio200_subdev_timer_read;
+ s->insn_config = dio200_subdev_timer_config;
+ return 0;
+}
+
+void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val)
+{
+ dio200_write8(dev, DIO200_ENHANCE, val);
+}
+EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance);
+
+int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq,
+ unsigned long req_irq_flags)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+ const struct dio200_layout *layout = dio200_board_layout(thisboard);
+ struct comedi_subdevice *s;
+ int sdx;
+ unsigned int n;
+ int ret;
+
+ devpriv->intr_sd = -1;
+
+ ret = comedi_alloc_subdevices(dev, layout->n_subdevs);
+ if (ret)
+ 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,
+ layout->sdinfo[n]);
+ if (ret < 0)
+ return ret;
+ break;
+ case sd_8255:
+ /* digital i/o subdevice (8255) */
+ ret = dio200_subdev_8255_init(dev, s,
+ layout->sdinfo[n]);
+ if (ret < 0)
+ return ret;
+ break;
+ case sd_intr:
+ /* 'INTERRUPT' subdevice */
+ if (irq) {
+ ret = dio200_subdev_intr_init(dev, s,
+ DIO200_INT_SCE,
+ layout->sdinfo[n]
+ );
+ if (ret < 0)
+ return ret;
+ devpriv->intr_sd = n;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+ break;
+ case sd_timer:
+ ret = dio200_subdev_timer_init(dev, s);
+ if (ret < 0)
+ return ret;
+ 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];
+ if (irq) {
+ if (request_irq(irq, dio200_interrupt, req_irq_flags,
+ dev->board_name, dev) >= 0) {
+ dev->irq = irq;
+ } else {
+ dev_warn(dev->class_dev,
+ "warning! irq %u unavailable!\n", irq);
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(amplc_dio200_common_attach);
+
+void amplc_dio200_common_detach(struct comedi_device *dev)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ if (!thisboard || !devpriv)
+ return;
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+}
+EXPORT_SYMBOL_GPL(amplc_dio200_common_detach);
+
+static int __init amplc_dio200_common_init(void)
+{
+ return 0;
+}
+module_init(amplc_dio200_common_init);
+
+static void __exit amplc_dio200_common_exit(void)
+{
+}
+module_exit(amplc_dio200_common_exit);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_dio200_pci.c b/drivers/staging/comedi/drivers/amplc_dio200_pci.c
new file mode 100644
index 00000000000..e0367380b37
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_dio200_pci.c
@@ -0,0 +1,480 @@
+/* comedi/drivers/amplc_dio200_pci.c
+
+ Driver for Amplicon PCI215, PCI272, PCIe215, PCIe236, PCIe296.
+
+ Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+ * Driver: amplc_dio200_pci
+ * Description: Amplicon 200 Series PCI Digital I/O
+ * Author: Ian Abbott <abbotti@mev.co.uk>
+ * Devices: [Amplicon] PCI215 (amplc_dio200_pci), PCIe215, PCIe236,
+ * PCI272, PCIe296
+ * Updated: Mon, 18 Mar 2013 15:03:50 +0000
+ * Status: works
+ *
+ * Configuration options:
+ * none
+ *
+ * Manual configuration of PCI(e) cards is not supported; they are configured
+ * automatically.
+ *
+ * SUBDEVICES
+ *
+ * PCI215 PCIe215 PCIe236
+ * ------------- ------------- -------------
+ * Subdevices 5 8 8
+ * 0 PPI-X PPI-X PPI-X
+ * 1 PPI-Y UNUSED UNUSED
+ * 2 CTR-Z1 PPI-Y UNUSED
+ * 3 CTR-Z2 UNUSED UNUSED
+ * 4 INTERRUPT CTR-Z1 CTR-Z1
+ * 5 CTR-Z2 CTR-Z2
+ * 6 TIMER TIMER
+ * 7 INTERRUPT INTERRUPT
+ *
+ *
+ * PCI272 PCIe296
+ * ------------- -------------
+ * Subdevices 4 8
+ * 0 PPI-X PPI-X1
+ * 1 PPI-Y PPI-X2
+ * 2 PPI-Z PPI-Y1
+ * 3 INTERRUPT PPI-Y2
+ * 4 CTR-Z1
+ * 5 CTR-Z2
+ * 6 TIMER
+ * 7 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.
+ *
+ * For the PCIe boards, clock sources in the range 0 to 31 are allowed
+ * and the following additional clock sources are defined:
+ *
+ * 8. HIGH logic level.
+ * 9. LOW logic level.
+ * 10. "Pattern present" signal.
+ * 11. Internal 20 MHz clock.
+ *
+ * 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.
+ *
+ * For the PCIe boards, gate sources in the range 0 to 31 are allowed;
+ * the following additional clock sources and clock sources 6 and 7 are
+ * (re)defined:
+ *
+ * 6. /GAT n, negated version of the counter channel's dedicated
+ * GAT input (negated version of gate source 2).
+ * 7. OUT n-2, the non-inverted output of counter channel n-2
+ * (negated version of gate source 3).
+ * 8. "Pattern present" signal, HIGH while pattern present.
+ * 9. "Pattern occurred" latched signal, latches HIGH when pattern
+ * occurs.
+ * 10. "Pattern gone away" latched signal, latches LOW when pattern
+ * goes away after it occurred.
+ * 11. Negated "pattern present" signal, LOW while pattern present
+ * (negated version of gate source 8).
+ * 12. Negated "pattern occurred" latched signal, latches LOW when
+ * pattern occurs (negated version of gate source 9).
+ * 13. Negated "pattern gone away" latched signal, latches LOW when
+ * pattern goes away after it occurred (negated version of gate
+ * source 10).
+ *
+ * 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 'TIMER' subdevice is a free-running 32-bit timer subdevice.
+ *
+ * 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
+ *
+ * PCI215 PCIe215 PCIe236
+ * ------------- ------------- -------------
+ * Sources 6 6 6
+ * 0 PPI-X-C0 PPI-X-C0 PPI-X-C0
+ * 1 PPI-X-C3 PPI-X-C3 PPI-X-C3
+ * 2 PPI-Y-C0 PPI-Y-C0 unused
+ * 3 PPI-Y-C3 PPI-Y-C3 unused
+ * 4 CTR-Z1-OUT1 CTR-Z1-OUT1 CTR-Z1-OUT1
+ * 5 CTR-Z2-OUT1 CTR-Z2-OUT1 CTR-Z2-OUT1
+ *
+ * PCI272 PCIe296
+ * ------------- -------------
+ * Sources 6 6
+ * 0 PPI-X-C0 PPI-X1-C0
+ * 1 PPI-X-C3 PPI-X1-C3
+ * 2 PPI-Y-C0 PPI-Y1-C0
+ * 3 PPI-Y-C3 PPI-Y1-C3
+ * 4 PPI-Z-C0 CTR-Z1-OUT1
+ * 5 PPI-Z-C3 CTR-Z2-OUT1
+ *
+ * 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. The interrupt will remain asserted 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).
+ *
+ * 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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "amplc_dio200.h"
+
+/* PCI IDs */
+#define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a
+#define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b
+#define PCI_DEVICE_ID_AMPLICON_PCIE236 0x0011
+#define PCI_DEVICE_ID_AMPLICON_PCIE215 0x0012
+#define PCI_DEVICE_ID_AMPLICON_PCIE296 0x0014
+
+/*
+ * Board descriptions.
+ */
+
+enum dio200_pci_model {
+ pci215_model,
+ pci272_model,
+ pcie215_model,
+ pcie236_model,
+ pcie296_model
+};
+
+static const struct dio200_board dio200_pci_boards[] = {
+ [pci215_model] = {
+ .name = "pci215",
+ .bustype = pci_bustype,
+ .mainbar = 2,
+ .mainsize = DIO200_IO_SIZE,
+ .layout = {
+ .n_subdevs = 5,
+ .sdtype = {sd_8255, sd_8255, sd_8254, sd_8254, sd_intr},
+ .sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F},
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ },
+ },
+ [pci272_model] = {
+ .name = "pci272",
+ .bustype = pci_bustype,
+ .mainbar = 2,
+ .mainsize = DIO200_IO_SIZE,
+ .layout = {
+ .n_subdevs = 4,
+ .sdtype = {sd_8255, sd_8255, sd_8255, sd_intr},
+ .sdinfo = {0x00, 0x08, 0x10, 0x3F},
+ .has_int_sce = true,
+ },
+ },
+ [pcie215_model] = {
+ .name = "pcie215",
+ .bustype = pci_bustype,
+ .mainbar = 1,
+ .mainshift = 3,
+ .mainsize = DIO200_PCIE_IO_SIZE,
+ .layout = {
+ .n_subdevs = 8,
+ .sdtype = {sd_8255, sd_none, sd_8255, sd_none,
+ sd_8254, sd_8254, sd_timer, sd_intr},
+ .sdinfo = {0x00, 0x00, 0x08, 0x00,
+ 0x10, 0x14, 0x00, 0x3F},
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .has_enhancements = true,
+ },
+ },
+ [pcie236_model] = {
+ .name = "pcie236",
+ .bustype = pci_bustype,
+ .mainbar = 1,
+ .mainshift = 3,
+ .mainsize = DIO200_PCIE_IO_SIZE,
+ .layout = {
+ .n_subdevs = 8,
+ .sdtype = {sd_8255, sd_none, sd_none, sd_none,
+ sd_8254, sd_8254, sd_timer, sd_intr},
+ .sdinfo = {0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x14, 0x00, 0x3F},
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .has_enhancements = true,
+ },
+ },
+ [pcie296_model] = {
+ .name = "pcie296",
+ .bustype = pci_bustype,
+ .mainbar = 1,
+ .mainshift = 3,
+ .mainsize = DIO200_PCIE_IO_SIZE,
+ .layout = {
+ .n_subdevs = 8,
+ .sdtype = {sd_8255, sd_8255, sd_8255, sd_8255,
+ sd_8254, sd_8254, sd_timer, sd_intr},
+ .sdinfo = {0x00, 0x04, 0x08, 0x0C,
+ 0x10, 0x14, 0x00, 0x3F},
+ .has_int_sce = true,
+ .has_clk_gat_sce = true,
+ .has_enhancements = true,
+ },
+ },
+};
+
+/*
+ * This function does some special set-up for the PCIe boards
+ * PCIe215, PCIe236, PCIe296.
+ */
+static int dio200_pcie_board_setup(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ void __iomem *brbase;
+
+ /*
+ * The board uses Altera Cyclone IV with PCI-Express hard IP.
+ * The FPGA configuration has the PCI-Express Avalon-MM Bridge
+ * Control registers in PCI BAR 0, offset 0, and the length of
+ * these registers is 0x4000.
+ *
+ * We need to write 0x80 to the "Avalon-MM to PCI-Express Interrupt
+ * Enable" register at offset 0x50 to allow generation of PCIe
+ * interrupts when RXmlrq_i is asserted in the SOPC Builder system.
+ */
+ if (pci_resource_len(pcidev, 0) < 0x4000) {
+ dev_err(dev->class_dev, "error! bad PCI region!\n");
+ return -EINVAL;
+ }
+ brbase = pci_ioremap_bar(pcidev, 0);
+ if (!brbase) {
+ dev_err(dev->class_dev, "error! failed to map registers!\n");
+ return -ENOMEM;
+ }
+ writel(0x80, brbase + 0x50);
+ iounmap(brbase);
+ /* Enable "enhanced" features of board. */
+ amplc_dio200_set_enhance(dev, 1);
+ return 0;
+}
+
+static int dio200_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context_model)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ const struct dio200_board *thisboard = NULL;
+ struct dio200_private *devpriv;
+ unsigned int bar;
+ int ret;
+
+ if (context_model < ARRAY_SIZE(dio200_pci_boards))
+ thisboard = &dio200_pci_boards[context_model];
+ if (!thisboard)
+ return -EINVAL;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ dev_info(dev->class_dev, "%s: attach pci %s (%s)\n",
+ dev->driver->driver_name, pci_name(pci_dev), dev->board_name);
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ bar = thisboard->mainbar;
+ if (pci_resource_len(pci_dev, bar) < thisboard->mainsize) {
+ dev_err(dev->class_dev, "error! PCI region size too small!\n");
+ return -EINVAL;
+ }
+ if (pci_resource_flags(pci_dev, bar) & IORESOURCE_MEM) {
+ devpriv->io.u.membase = pci_ioremap_bar(pci_dev, bar);
+ if (!devpriv->io.u.membase) {
+ dev_err(dev->class_dev,
+ "error! cannot remap registers\n");
+ return -ENOMEM;
+ }
+ devpriv->io.regtype = mmio_regtype;
+ } else {
+ devpriv->io.u.iobase = pci_resource_start(pci_dev, bar);
+ devpriv->io.regtype = io_regtype;
+ }
+ switch (context_model) {
+ case pcie215_model:
+ case pcie236_model:
+ case pcie296_model:
+ ret = dio200_pcie_board_setup(dev);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ break;
+ }
+ return amplc_dio200_common_attach(dev, pci_dev->irq, IRQF_SHARED);
+}
+
+static void dio200_pci_detach(struct comedi_device *dev)
+{
+ const struct dio200_board *thisboard = comedi_board(dev);
+ struct dio200_private *devpriv = dev->private;
+
+ if (!thisboard || !devpriv)
+ return;
+ amplc_dio200_common_detach(dev);
+ if (devpriv->io.regtype == mmio_regtype)
+ iounmap(devpriv->io.u.membase);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver dio200_pci_comedi_driver = {
+ .driver_name = "amplc_dio200_pci",
+ .module = THIS_MODULE,
+ .auto_attach = dio200_pci_auto_attach,
+ .detach = dio200_pci_detach,
+};
+
+static const struct pci_device_id dio200_pci_table[] = {
+ {
+ PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215),
+ pci215_model
+ }, {
+ PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272),
+ pci272_model
+ }, {
+ PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE236),
+ pcie236_model
+ }, {
+ PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE215),
+ pcie215_model
+ }, {
+ PCI_VDEVICE(AMPLICON, PCI_DEVICE_ID_AMPLICON_PCIE296),
+ pcie296_model
+ },
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, dio200_pci_table);
+
+static int dio200_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dio200_pci_comedi_driver,
+ id->driver_data);
+}
+
+static struct pci_driver dio200_pci_pci_driver = {
+ .name = "amplc_dio200_pci",
+ .id_table = dio200_pci_table,
+ .probe = dio200_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dio200_pci_comedi_driver, dio200_pci_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon 200 Series PCI(e) DIO boards");
+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 00000000000..c9a96ad0055
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc236.c
@@ -0,0 +1,572 @@
+/*
+ comedi/drivers/amplc_pc236.c
+ Driver for Amplicon PC36AT and PCI236 DIO boards.
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: amplc_pc236
+Description: Amplicon PC36AT, PCI236
+Author: Ian Abbott <abbotti@mev.co.uk>
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8255.h"
+#include "plx9052.h"
+
+#define PC236_DRIVER_NAME "amplc_pc236"
+
+#define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
+#define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
+
+/* PCI236 PCI configuration register information */
+#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
+
+/* Disable, and clear, interrupts */
+#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1POL | \
+ PLX9052_INTCSR_LI2POL | \
+ PLX9052_INTCSR_LI1SEL | \
+ PLX9052_INTCSR_LI1CLRINT)
+
+/* Enable, and clear, interrupts */
+#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB | \
+ PLX9052_INTCSR_LI1POL | \
+ PLX9052_INTCSR_LI2POL | \
+ PLX9052_INTCSR_PCIENAB | \
+ PLX9052_INTCSR_LI1SEL | \
+ PLX9052_INTCSR_LI1CLRINT)
+
+/*
+ * 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;
+ unsigned short devid;
+ enum pc236_bustype bustype;
+ enum pc236_model model;
+};
+static const struct pc236_board pc236_boards[] = {
+#if DO_ISA
+ {
+ .name = "pc36at",
+ .bustype = isa_bustype,
+ .model = pc36at_model,
+ },
+#endif
+#if DO_PCI
+ {
+ .name = "pci236",
+ .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
+ .bustype = pci_bustype,
+ .model = pci236_model,
+ },
+ {
+ .name = PC236_DRIVER_NAME,
+ .devid = PCI_DEVICE_ID_INVALID,
+ .bustype = pci_bustype,
+ .model = anypci_model, /* wildcard */
+ },
+#endif
+};
+
+/* 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 {
+ unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
+ int enable_irq;
+};
+
+/* test if ISA supported and this is an ISA board */
+static inline bool is_isa_board(const struct pc236_board *board)
+{
+ return DO_ISA && board->bustype == isa_bustype;
+}
+
+/* test if PCI supported and this is a PCI board */
+static inline bool is_pci_board(const struct pc236_board *board)
+{
+ return DO_PCI && board->bustype == pci_bustype;
+}
+
+/*
+ * This function looks for a board matching the supplied PCI device.
+ */
+static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
+ if (is_pci_board(&pc236_boards[i]) &&
+ pci_dev->device == pc236_boards[i].devid)
+ return &pc236_boards[i];
+ return NULL;
+}
+
+/*
+ * This function looks for a PCI device matching the requested board name,
+ * bus and slot.
+ */
+static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct pci_dev *pci_dev = NULL;
+ int bus = it->options[0];
+ int slot = it->options[1];
+
+ for_each_pci_dev(pci_dev) {
+ if (bus || slot) {
+ if (bus != pci_dev->bus->number ||
+ slot != PCI_SLOT(pci_dev->devfn))
+ continue;
+ }
+ if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
+ continue;
+
+ if (thisboard->model == anypci_model) {
+ /* Wildcard board matches any supported PCI board. */
+ const struct pc236_board *foundboard;
+
+ foundboard = pc236_find_pci_board(pci_dev);
+ if (foundboard == NULL)
+ continue;
+ /* Replace wildcard board_ptr. */
+ dev->board_ptr = foundboard;
+ } else {
+ /* Match specific model name. */
+ if (pci_dev->device != thisboard->devid)
+ continue;
+ }
+ return pci_dev;
+ }
+ dev_err(dev->class_dev,
+ "No supported board found! (req. bus %d, slot %d)\n",
+ bus, slot);
+ return NULL;
+}
+
+/*
+ * 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)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct pc236_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->enable_irq = 0;
+ if (is_pci_board(thisboard))
+ outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
+ 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)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct pc236_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->enable_irq = 1;
+ if (is_pci_board(thisboard))
+ outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
+ 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)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct pc236_private *devpriv = dev->private;
+ int retval = 0;
+ unsigned long flags;
+ unsigned int intcsr;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->enable_irq) {
+ retval = 1;
+ if (is_pci_board(thisboard)) {
+ intcsr = inl(devpriv->lcr_iobase + PLX9052_INTCSR);
+ if (!(intcsr & PLX9052_INTCSR_LI1STAT)) {
+ retval = 0;
+ } else {
+ /* Clear interrupt and keep it enabled. */
+ outl(PCI236_INTR_ENABLE,
+ devpriv->lcr_iobase + PLX9052_INTCSR);
+ }
+ }
+ }
+ 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 insn->n;
+}
+
+/*
+ * 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;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check it arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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->read_subdev;
+ int handled;
+
+ handled = pc236_intr_check(dev);
+ if (dev->attached && handled) {
+ comedi_buf_put(s, 0);
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ comedi_event(dev, s);
+ }
+ return IRQ_RETVAL(handled);
+}
+
+static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
+ unsigned int irq, unsigned long req_irq_flags)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev->board_name = thisboard->name;
+ dev->iobase = iobase;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital i/o subdevice (8255) */
+ ret = subdev_8255_init(dev, s, NULL, iobase);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[1];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_UNUSED;
+ pc236_intr_disable(dev);
+ if (irq) {
+ if (request_irq(irq, pc236_interrupt, req_irq_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->len_chanlist = 1;
+ s->do_cmdtest = pc236_intr_cmdtest;
+ s->do_cmd = pc236_intr_cmd;
+ s->cancel = pc236_intr_cancel;
+ }
+ }
+
+ return 0;
+}
+
+static int pc236_pci_common_attach(struct comedi_device *dev,
+ struct pci_dev *pci_dev)
+{
+ struct pc236_private *devpriv = dev->private;
+ unsigned long iobase;
+ int ret;
+
+ comedi_set_hw_dev(dev, &pci_dev->dev);
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
+ iobase = pci_resource_start(pci_dev, 2);
+ return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
+}
+
+/*
+ * 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)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+ struct pc236_private *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* Process options according to bus type. */
+ if (is_isa_board(thisboard)) {
+ ret = comedi_request_region(dev, it->options[0], PC236_IO_SIZE);
+ if (ret)
+ return ret;
+
+ return pc236_common_attach(dev, dev->iobase, it->options[1], 0);
+ } else if (is_pci_board(thisboard)) {
+ struct pci_dev *pci_dev;
+
+ pci_dev = pc236_find_pci_dev(dev, it);
+ if (!pci_dev)
+ return -EIO;
+ return pc236_pci_common_attach(dev, pci_dev);
+ } else {
+ dev_err(dev->class_dev, PC236_DRIVER_NAME
+ ": BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+}
+
+/*
+ * The auto_attach hook is called at PCI probe time via
+ * comedi_pci_auto_config(). dev->board_ptr is NULL on entry.
+ * There should be a board entry matching the supplied PCI device.
+ */
+static int pc236_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ struct pc236_private *devpriv;
+
+ if (!DO_PCI)
+ return -EINVAL;
+
+ dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
+ pci_name(pci_dev));
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_ptr = pc236_find_pci_board(pci_dev);
+ if (dev->board_ptr == NULL) {
+ dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+ /*
+ * Need to 'get' the PCI device to match the 'put' in pc236_detach().
+ * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
+ * support for manual attachment of PCI devices via pc236_attach()
+ * has been removed.
+ */
+ pci_dev_get(pci_dev);
+ return pc236_pci_common_attach(dev, pci_dev);
+}
+
+static void pc236_detach(struct comedi_device *dev)
+{
+ const struct pc236_board *thisboard = comedi_board(dev);
+
+ if (!thisboard)
+ return;
+ if (dev->iobase)
+ pc236_intr_disable(dev);
+ if (is_isa_board(thisboard)) {
+ comedi_legacy_detach(dev);
+ } else if (is_pci_board(thisboard)) {
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+ if (pcidev)
+ pci_dev_put(pcidev);
+ }
+}
+
+/*
+ * 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 amplc_pc236_driver = {
+ .driver_name = PC236_DRIVER_NAME,
+ .module = THIS_MODULE,
+ .attach = pc236_attach,
+ .auto_attach = pc236_auto_attach,
+ .detach = pc236_detach,
+ .board_name = &pc236_boards[0].name,
+ .offset = sizeof(struct pc236_board),
+ .num_names = ARRAY_SIZE(pc236_boards),
+};
+
+#if DO_PCI
+static const struct pci_device_id pc236_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, pc236_pci_table);
+
+static int amplc_pc236_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pc236_driver,
+ id->driver_data);
+}
+
+static struct pci_driver amplc_pc236_pci_driver = {
+ .name = PC236_DRIVER_NAME,
+ .id_table = pc236_pci_table,
+ .probe = &amplc_pc236_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
+#else
+module_comedi_driver(amplc_pc236_driver);
+#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_pc263.c b/drivers/staging/comedi/drivers/amplc_pc263.c
new file mode 100644
index 00000000000..7c10d28d278
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pc263.c
@@ -0,0 +1,114 @@
+/*
+ comedi/drivers/amplc_pc263.c
+ Driver for Amplicon PC263 and PCI263 relay boards.
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: amplc_pc263
+Description: Amplicon PC263
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PC263 (pc263)
+Updated: Fri, 12 Apr 2013 15:19:36 +0100
+Status: works
+
+Configuration options:
+ [0] - I/O port base address
+
+The 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 <linux/module.h>
+#include "../comedidev.h"
+
+#define PC263_DRIVER_NAME "amplc_pc263"
+
+/* PC263 registers */
+#define PC263_IO_SIZE 2
+
+/*
+ * Board descriptions for Amplicon PC263.
+ */
+
+struct pc263_board {
+ const char *name;
+};
+
+static const struct pc263_board pc263_boards[] = {
+ {
+ .name = "pc263",
+ },
+};
+
+static int pc263_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase);
+ outb((s->state >> 8) & 0xff, dev->iobase + 1);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], PC263_IO_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pc263_do_insn_bits;
+ /* read initial relay state */
+ s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8);
+
+ return 0;
+}
+
+static struct comedi_driver amplc_pc263_driver = {
+ .driver_name = PC263_DRIVER_NAME,
+ .module = THIS_MODULE,
+ .attach = pc263_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pc263_boards[0].name,
+ .offset = sizeof(struct pc263_board),
+ .num_names = ARRAY_SIZE(pc263_boards),
+};
+
+module_comedi_driver(amplc_pc263_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PC263 relay board");
+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 00000000000..339c47c1eb9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci224.c
@@ -0,0 +1,1372 @@
+/*
+ comedi/drivers/amplc_pci224.c
+ Driver for Amplicon PCI224 and PCI234 AO boards.
+
+ Copyright (C) 2005 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: amplc_pci224
+Description: Amplicon PCI224, PCI234
+Author: Ian Abbott <abbotti@mev.co.uk>
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+#define DRIVER_NAME "amplc_pci224"
+
+/*
+ * PCI IDs.
+ */
+#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))
+
+/*
+ * 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)))
+
+/* 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 */
+ },
+};
+
+/* 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 {
+ const unsigned short *hwrange;
+ unsigned long iobase1;
+ unsigned long state;
+ spinlock_t ao_spinlock;
+ unsigned int *ao_readback;
+ unsigned 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;
+ unsigned short ao_enab; /* max 16 channels so 'short' will do */
+ unsigned char intsce;
+};
+
+/*
+ * 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)
+{
+ const struct pci224_board *thisboard = comedi_board(dev);
+ struct pci224_private *devpriv = dev->private;
+ 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)
+{
+ struct pci224_private *devpriv = dev->private;
+ int i;
+ int chan;
+
+ chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+
+ return i;
+}
+
+/*
+ * Kills a command running on the AO subdevice.
+ */
+static void pci224_ao_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ 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 pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ set_bit(AO_CMD_STARTED, &devpriv->state);
+ if (cmd->stop_src == TRIG_COUNT && devpriv->ao_stop_count == 0) {
+ /* An empty acquisition! */
+ s->async->events |= COMEDI_CB_EOA;
+ cfc_handle_events(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 pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int bytes_per_scan = cfc_bytes_per_scan(s);
+ unsigned int num_scans;
+ unsigned int room;
+ unsigned short dacstat;
+ unsigned int i, n;
+
+ /* Determine number of scans available in buffer. */
+ num_scans = comedi_buf_read_n_available(s) / bytes_per_scan;
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* 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 (cmd->stop_src == TRIG_COUNT && devpriv->ao_stop_count == 0) {
+ /* FIFO empty at end of counted acquisition. */
+ s->async->events |= COMEDI_CB_EOA;
+ cfc_handle_events(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. */
+ dev_err(dev->class_dev, "AO buffer underrun\n");
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ }
+ }
+ /* Determine how many new scans can be put in the FIFO. */
+ 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 (cmd->stop_src == TRIG_COUNT) {
+ 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);
+ }
+
+ cfc_handle_events(dev, s);
+}
+
+static int pci224_ao_inttrig_start(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci224_ao_start(dev, s);
+
+ return 1;
+}
+
+static int pci224_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int chan_mask = 0;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan_mask & (1 << chan)) {
+ dev_dbg(dev->class_dev,
+ "%s: entries in chanlist must contain no duplicate channels\n",
+ __func__);
+ return -EINVAL;
+ }
+ chan_mask |= (1 << chan);
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "%s: entries in chanlist must all have the same range index\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+#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)
+{
+ struct pci224_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_EXT | TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /*
+ * There's only one external trigger signal (which makes these
+ * tests easier). Only one thing can use it.
+ */
+ arg = 0;
+ if (cmd->start_src & TRIG_EXT)
+ arg++;
+ if (cmd->scan_begin_src & TRIG_EXT)
+ arg++;
+ if (cmd->stop_src & TRIG_EXT)
+ arg++;
+ if (arg > 1)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ 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 |= -EINVAL;
+ }
+ /* 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 |= -EINVAL;
+ }
+ break;
+ }
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MAX_SCAN_PERIOD);
+
+ arg = cmd->chanlist_len * CONVERT_PERIOD;
+ if (arg < MIN_SCAN_PERIOD)
+ arg = MIN_SCAN_PERIOD;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, arg);
+ 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 |= -EINVAL;
+ }
+ /* 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 |= -EINVAL;
+ }
+ break;
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ 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 |= -EINVAL;
+ }
+ /* 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:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments. */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ /* Use two timers. */
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->cached_div1,
+ &devpriv->cached_div2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= pci224_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci224_ao_start_pacer(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ unsigned long timer_base = devpriv->iobase1 + PCI224_Z2_CT0;
+
+ /*
+ * 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);
+ /* 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_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_write(timer_base, 0, 2, devpriv->cached_div1);
+ /* 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_set_mode(timer_base, 0, 0, I8254_MODE2 | I8254_BINARY);
+ i8254_write(timer_base, 0, 0, devpriv->cached_div2);
+}
+
+static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci224_private *devpriv = dev->private;
+ 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)
+ pci224_ao_start_pacer(dev, s);
+
+ /*
+ * Sort out end of acquisition.
+ */
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->ao_stop_count = cmd->stop_arg;
+ else /* TRIG_EXT | TRIG_NONE */
+ devpriv->ao_stop_count = 0;
+
+ spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (cmd->start_src == TRIG_INT) {
+ s->async->inttrig = pci224_ao_inttrig_start;
+ } else { /* TRIG_EXT */
+ /* Enable external interrupt trigger to start acquisition. */
+ devpriv->intsce |= PCI224_INTR_EXT;
+ outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE);
+ }
+ spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+
+ 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)
+{
+ const struct pci224_board *thisboard = comedi_board(dev);
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned 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(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 pci224_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ 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 board matching the supplied PCI device.
+ */
+static const struct pci224_board
+*pci224_find_pci_board(struct pci_dev *pci_dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pci224_boards); i++)
+ if (pci_dev->device == pci224_boards[i].devid)
+ return &pci224_boards[i];
+ return NULL;
+}
+
+/*
+ * This function looks for a PCI device matching the requested board name,
+ * bus and slot.
+ */
+static struct pci_dev *pci224_find_pci_dev(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pci224_board *thisboard = comedi_board(dev);
+ struct pci_dev *pci_dev = NULL;
+ int bus = it->options[0];
+ int slot = it->options[1];
+
+ for_each_pci_dev(pci_dev) {
+ if (bus || slot) {
+ if (bus != pci_dev->bus->number ||
+ slot != PCI_SLOT(pci_dev->devfn))
+ continue;
+ }
+ if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
+ continue;
+
+ if (thisboard->model == any_model) {
+ /* Match any supported model. */
+ const struct pci224_board *board_ptr;
+
+ board_ptr = pci224_find_pci_board(pci_dev);
+ if (board_ptr == NULL)
+ continue;
+ /* Change board_ptr to matched board. */
+ dev->board_ptr = board_ptr;
+ } else {
+ /* Match specific model name. */
+ if (thisboard->devid != pci_dev->device)
+ continue;
+ }
+ return pci_dev;
+ }
+ dev_err(dev->class_dev,
+ "No supported board found! (req. bus %d, slot %d)\n",
+ bus, slot);
+ return NULL;
+}
+
+/*
+ * Common part of attach and auto_attach.
+ */
+static int pci224_attach_common(struct comedi_device *dev,
+ struct pci_dev *pci_dev, int *options)
+{
+ const struct pci224_board *thisboard = comedi_board(dev);
+ struct pci224_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ unsigned int irq;
+ unsigned n;
+ int ret;
+
+ comedi_set_hw_dev(dev, &pci_dev->dev);
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ 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);
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ 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;
+
+ if (options) {
+ for (n = 2; n < 3 + s->n_chan; n++) {
+ if (options[n] < 0 || options[n] > 1) {
+ dev_warn(dev->class_dev, DRIVER_NAME
+ ": warning! bad options[%u]=%d\n",
+ n, options[n]);
+ }
+ }
+ }
+ for (n = 0; n < s->n_chan; n++) {
+ if (n < COMEDI_NDEVCONFOPTS - 3 && options &&
+ options[3 + n] == 1) {
+ if (options[2] == 1)
+ range_table_list[n] = &range_pci234_ext;
+ else
+ range_table_list[n] = &range_bipolar5;
+
+ } else {
+ if (options && 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 (options && options[2] == 1) {
+ s->range_table = &range_pci224_external;
+ devpriv->hwrange = hwrange_pci224_external;
+ } else {
+ if (options && options[2] != 0) {
+ dev_warn(dev->class_dev, DRIVER_NAME
+ ": warning! bad options[2]=%d\n",
+ 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) {
+ dev_err(dev->class_dev,
+ "error! unable to allocate irq %u\n", irq);
+ return ret;
+ } else {
+ dev->irq = irq;
+ }
+ }
+
+ return 0;
+}
+
+static int pci224_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct pci224_private *devpriv;
+ struct pci_dev *pci_dev;
+
+ dev_info(dev->class_dev, DRIVER_NAME ": attach\n");
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ pci_dev = pci224_find_pci_dev(dev, it);
+ if (!pci_dev)
+ return -EIO;
+
+ return pci224_attach_common(dev, pci_dev, it->options);
+}
+
+static int
+pci224_auto_attach(struct comedi_device *dev, unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ struct pci224_private *devpriv;
+
+ dev_info(dev->class_dev, DRIVER_NAME ": attach pci %s\n",
+ pci_name(pci_dev));
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_ptr = pci224_find_pci_board(pci_dev);
+ if (dev->board_ptr == NULL) {
+ dev_err(dev->class_dev,
+ DRIVER_NAME ": BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+ /*
+ * Need to 'get' the PCI device to match the 'put' in pci224_detach().
+ * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
+ * support for manual attachment of PCI devices via pci224_attach()
+ * has been removed.
+ */
+ pci_dev_get(pci_dev);
+ return pci224_attach_common(dev, pci_dev, NULL);
+}
+
+static void pci224_detach(struct comedi_device *dev)
+{
+ struct pci224_private *devpriv = dev->private;
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ 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);
+ }
+ comedi_pci_disable(dev);
+ if (pcidev)
+ pci_dev_put(pcidev);
+}
+
+static struct comedi_driver amplc_pci224_driver = {
+ .driver_name = "amplc_pci224",
+ .module = THIS_MODULE,
+ .attach = pci224_attach,
+ .detach = pci224_detach,
+ .auto_attach = pci224_auto_attach,
+ .board_name = &pci224_boards[0].name,
+ .offset = sizeof(struct pci224_board),
+ .num_names = ARRAY_SIZE(pci224_boards),
+};
+
+static int amplc_pci224_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci224_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id amplc_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, amplc_pci224_pci_table);
+
+static struct pci_driver amplc_pci224_pci_driver = {
+ .name = "amplc_pci224",
+ .id_table = amplc_pci224_pci_table,
+ .probe = amplc_pci224_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci224_driver, amplc_pci224_pci_driver);
+
+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 00000000000..3895bc7cb3e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci230.c
@@ -0,0 +1,2825 @@
+ /*
+ comedi/drivers/amplc_pci230.c
+ Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards.
+
+ Copyright (C) 2001 Allan Willcox <allanwillcox@ozemail.com.au>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+Driver: amplc_pci230
+Description: Amplicon PCI230, PCI260 Multifunction I/O boards
+Author: Allan Willcox <allanwillcox@ozemail.com.au>,
+ Steve D Sharples <steve.sharples@nottingham.ac.uk>,
+ Ian Abbott <abbotti@mev.co.uk>
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+#include "8255.h"
+
+/* PCI230 PCI configuration register information */
+#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)))
+
+/* 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,
+ },
+};
+
+/* 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 {
+ 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_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. */
+};
+
+/* 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 };
+
+static unsigned short pci230_ai_read(struct comedi_device *dev)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci230_private *devpriv = dev->private;
+ unsigned short data;
+
+ /* Read sample. */
+ data = 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,
+ unsigned short datum)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci230_private *devpriv = dev->private;
+
+ /* 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 datum;
+}
+
+static inline void pci230_ao_write_nofifo(struct comedi_device *dev,
+ unsigned short datum,
+ unsigned int chan)
+{
+ struct pci230_private *devpriv = dev->private;
+
+ /* 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,
+ unsigned short datum, unsigned int chan)
+{
+ struct pci230_private *devpriv = dev->private;
+
+ /* 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);
+}
+
+static int get_resources(struct comedi_device *dev, unsigned int res_mask,
+ unsigned char owner)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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);
+}
+
+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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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)
+{
+ struct pci230_private *devpriv = dev->private;
+
+ i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct,
+ I8254_MODE1);
+ /* Counter ct, 8254 mode 1, initial count not written. */
+}
+
+static int pci230_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + PCI230_ADCCON);
+ if ((status & PCI230_ADC_FIFO_EMPTY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pci230_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int n;
+ unsigned int chan, range, aref;
+ unsigned int gainshift;
+ unsigned short adccon, adcen;
+ int ret;
+
+ /* 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) {
+ dev_dbg(dev->class_dev,
+ "%s: differential channel number out of range 0 to %u\n",
+ __func__, (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);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, pci230_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* 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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int prev_chan = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan < prev_chan) {
+ dev_dbg(dev->class_dev,
+ "%s: channel numbers must increase\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "%s: channels must have the same range\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ prev_chan = chan;
+ }
+
+ return 0;
+}
+
+static int pci230_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci230_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT);
+
+ tmp = TRIG_TIMER | TRIG_INT;
+ 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!
+ */
+ tmp |= TRIG_EXT;
+ }
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, tmp);
+
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#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:
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED_AO);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MIN_SPEED_AO);
+ 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 |= -EINVAL;
+ }
+ /* 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 |= -EINVAL;
+ }
+ break;
+ default:
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ break;
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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)
+ err |= pci230_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci230_ao_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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 void pci230_handle_ao_nofifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned short data;
+ int i, ret;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+
+ if (cmd->stop_src == TRIG_COUNT && 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, &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 (cmd->stop_src == TRIG_COUNT) {
+ 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 pci230_private *devpriv = dev->private;
+ 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 events = 0;
+ int running;
+
+ /* Get DAC FIFO status. */
+ dacstat = inw(dev->iobase + PCI230_DACCON);
+ /* Determine number of scans available in buffer. */
+ num_scans = comedi_buf_read_n_available(s) / cfc_bytes_per_scan(s);
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* 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++) {
+ unsigned short datum;
+
+ comedi_buf_get(s, &datum);
+ pci230_ao_write_fifo(dev, datum,
+ CR_CHAN(cmd->chanlist[i]));
+ }
+ }
+ events |= COMEDI_CB_EOS | COMEDI_CB_BLOCK;
+ if (cmd->stop_src == TRIG_COUNT) {
+ 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 int pci230_ao_inttrig_scan_begin(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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);
+ } else {
+ spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
+ }
+
+ return 1;
+}
+
+static void pci230_ao_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long irqflags;
+
+ set_bit(AO_CMD_STARTED, &devpriv->state);
+ if (cmd->stop_src == TRIG_COUNT && 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)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_src)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci230_ao_start(dev, s);
+
+ return 1;
+}
+
+static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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;
+ else /* TRIG_NONE, user calls cancel */
+ devpriv->ao_scan_count = 0;
+
+ /* 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_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pci230_ao_stop(dev, s);
+ 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_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct pci230_private *devpriv = dev->private;
+ unsigned int max_diff_chan = (s->n_chan / 2) - 1;
+ unsigned int prev_chan = 0;
+ unsigned int prev_range = 0;
+ unsigned int prev_aref = 0;
+ unsigned int prev_polarity = 0;
+ unsigned int subseq_len = 0;
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int polarity = pci230_ai_bipolar[range];
+
+ if (aref == AREF_DIFF && chan >= max_diff_chan) {
+ dev_dbg(dev->class_dev,
+ "%s: differential channel number out of range 0 to %u\n",
+ __func__, max_diff_chan);
+ return -EINVAL;
+ }
+
+ if (i > 0) {
+ /*
+ * Channel numbers must strictly increase or
+ * subsequence must repeat exactly.
+ */
+ if (chan <= prev_chan && subseq_len == 0)
+ subseq_len = i;
+
+ if (subseq_len > 0 &&
+ cmd->chanlist[i % subseq_len] != chanspec) {
+ dev_dbg(dev->class_dev,
+ "%s: channel numbers must increase or sequence must repeat exactly\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (aref != prev_aref) {
+ dev_dbg(dev->class_dev,
+ "%s: channel sequence analogue references must be all the same (single-ended or differential)\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (polarity != prev_polarity) {
+ dev_dbg(dev->class_dev,
+ "%s: channel sequence ranges must be all bipolar or all unipolar\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (aref != AREF_DIFF && range != prev_range &&
+ ((chan ^ prev_chan) & ~1) == 0) {
+ dev_dbg(dev->class_dev,
+ "%s: single-ended channel pairs must have the same range\n",
+ __func__);
+ return -EINVAL;
+ }
+ }
+ prev_chan = chan;
+ prev_range = range;
+ prev_aref = aref;
+ prev_polarity = polarity;
+ }
+
+ if (subseq_len == 0)
+ subseq_len = cmd->chanlist_len;
+
+ if ((cmd->chanlist_len % subseq_len) != 0) {
+ dev_dbg(dev->class_dev,
+ "%s: sequence must repeat exactly\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * 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 (devpriv->hwver > 0 && devpriv->hwver < 4) {
+ if (subseq_len > 1 && CR_CHAN(cmd->chanlist[0]) != 0) {
+ dev_info(dev->class_dev,
+ "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",
+ devpriv->hwver);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int pci230_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci230_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ tmp = TRIG_FOLLOW | TRIG_TIMER | TRIG_INT;
+ if ((thisboard->have_dio) || (thisboard->min_hwver > 0)) {
+ /*
+ * 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.
+ */
+ tmp |= TRIG_EXT;
+ }
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, tmp);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /*
+ * 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 |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#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;
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ max_speed_ai);
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg,
+ MIN_SPEED_AI);
+ } 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 |= -EINVAL;
+ }
+ /* 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 |= -EINVAL;
+ }
+ } else {
+ /* Backwards compatibility with previous versions. */
+ /* convert_arg == 0 => trigger on -ve edge. */
+ /* convert_arg == 1 => trigger on +ve edge. */
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 1);
+ }
+ } else {
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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 |= -EINVAL;
+ }
+ /* 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 |= -EINVAL;
+ }
+ } 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 |= -EINVAL;
+
+ } else {
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ 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)
+ err |= pci230_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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 (cmd->stop_src != TRIG_COUNT ||
+ 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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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_stop(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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 void pci230_ai_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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 (cmd->stop_src == TRIG_COUNT && 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)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+ pci230_ai_start(dev, s);
+
+ return 1;
+}
+
+static void pci230_handle_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int scanlen = cmd->scan_end_arg;
+ unsigned int events = 0;
+ unsigned int status_fifo;
+ unsigned int i;
+ unsigned int todo;
+ unsigned int fifoamount;
+
+ /* Determine number of samples to read. */
+ if (cmd->stop_src != TRIG_COUNT) {
+ 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(s, 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 (cmd->stop_src == TRIG_COUNT && 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 int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pci230_private *devpriv = dev->private;
+ 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;
+ else /* TRIG_NONE, user calls cancel */
+ devpriv->ai_scan_count = 0;
+ 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 int pci230_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ pci230_ai_stop(dev, s);
+ return 0;
+}
+
+/* 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 pci230_private *devpriv = dev->private;
+ 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;
+}
+
+/* Check if PCI device matches a specific board. */
+static bool pci230_match_pci_board(const struct pci230_board *board,
+ struct pci_dev *pci_dev)
+{
+ /* assume pci_dev->device != PCI_DEVICE_ID_INVALID */
+ if (board->id != pci_dev->device)
+ return false;
+ if (board->min_hwver == 0)
+ return true;
+ /* Looking for a '+' model. First check length of registers. */
+ if (pci_resource_len(pci_dev, 3) < 32)
+ return false; /* Not a '+' model. */
+ /* TODO: temporarily enable PCI device and read the hardware version
+ * register. For now, assume it's okay. */
+ return true;
+}
+
+/* Look for board matching PCI device. */
+static const struct pci230_board *pci230_find_pci_board(struct pci_dev *pci_dev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(pci230_boards); i++)
+ if (pci230_match_pci_board(&pci230_boards[i], pci_dev))
+ return &pci230_boards[i];
+ return NULL;
+}
+
+/* Look for PCI device matching requested board name, bus and slot. */
+static struct pci_dev *pci230_find_pci_dev(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci_dev *pci_dev = NULL;
+ int bus = it->options[0];
+ int slot = it->options[1];
+
+ for_each_pci_dev(pci_dev) {
+ /* Check vendor ID (same for all supported PCI boards). */
+ if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
+ continue;
+ /* If bus/slot specified, check them. */
+ if ((bus || slot) &&
+ (bus != pci_dev->bus->number ||
+ slot != PCI_SLOT(pci_dev->devfn)))
+ continue;
+ if (thisboard->id == PCI_DEVICE_ID_INVALID) {
+ /* Wildcard board matches any supported PCI board. */
+ const struct pci230_board *foundboard;
+
+ foundboard = pci230_find_pci_board(pci_dev);
+ if (foundboard == NULL)
+ continue;
+ /* Replace wildcard board_ptr. */
+ dev->board_ptr = foundboard;
+ } else {
+ /* Need to match a specific board. */
+ if (!pci230_match_pci_board(thisboard, pci_dev))
+ continue;
+ }
+ return pci_dev;
+ }
+ dev_err(dev->class_dev,
+ "No supported board found! (req. bus %d, slot %d)\n",
+ bus, slot);
+ return NULL;
+}
+
+static int pci230_alloc_private(struct comedi_device *dev)
+{
+ struct pci230_private *devpriv;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ 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);
+ return 0;
+}
+
+/* Common part of attach and auto_attach. */
+static int pci230_attach_common(struct comedi_device *dev,
+ struct pci_dev *pci_dev)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci230_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ unsigned long iobase1, iobase2;
+ /* PCI230's I/O spaces 1 and 2 respectively. */
+ int rc;
+
+ comedi_set_hw_dev(dev, &pci_dev->dev);
+
+ dev->board_name = thisboard->name;
+
+ rc = comedi_pci_enable(dev);
+ if (rc)
+ return rc;
+
+ /* 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);
+ dev_dbg(dev->class_dev,
+ "%s I/O region 1 0x%04lx I/O region 2 0x%04lx\n",
+ 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) {
+ dev_err(dev->class_dev,
+ "%s - bad hardware version - got %u, need %u\n",
+ 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);
+
+ if (pci_dev->irq) {
+ rc = request_irq(pci_dev->irq, pci230_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (rc == 0)
+ dev->irq = pci_dev->irq;
+ }
+
+ rc = comedi_alloc_subdevices(dev, 3);
+ if (rc)
+ return rc;
+
+ 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. */
+ if (dev->irq) {
+ 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;
+ if (dev->irq) {
+ 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)
+ return rc;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static int pci230_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pci230_board *thisboard = comedi_board(dev);
+ struct pci_dev *pci_dev;
+ int rc;
+
+ dev_info(dev->class_dev, "amplc_pci230: attach %s %d,%d\n",
+ thisboard->name, it->options[0], it->options[1]);
+
+ rc = pci230_alloc_private(dev);
+ if (rc)
+ return rc;
+
+ pci_dev = pci230_find_pci_dev(dev, it);
+ if (!pci_dev)
+ return -EIO;
+ return pci230_attach_common(dev, pci_dev);
+}
+
+static int pci230_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ int rc;
+
+ dev_info(dev->class_dev, "amplc_pci230: attach pci %s\n",
+ pci_name(pci_dev));
+
+ rc = pci230_alloc_private(dev);
+ if (rc)
+ return rc;
+
+ dev->board_ptr = pci230_find_pci_board(pci_dev);
+ if (dev->board_ptr == NULL) {
+ dev_err(dev->class_dev,
+ "amplc_pci230: BUG! cannot determine board type!\n");
+ return -EINVAL;
+ }
+ /*
+ * Need to 'get' the PCI device to match the 'put' in pci230_detach().
+ * TODO: Remove the pci_dev_get() and matching pci_dev_put() once
+ * support for manual attachment of PCI devices via pci230_attach()
+ * has been removed.
+ */
+ pci_dev_get(pci_dev);
+ return pci230_attach_common(dev, pci_dev);
+}
+
+static void pci230_detach(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+ if (pcidev)
+ pci_dev_put(pcidev);
+}
+
+static struct comedi_driver amplc_pci230_driver = {
+ .driver_name = "amplc_pci230",
+ .module = THIS_MODULE,
+ .attach = pci230_attach,
+ .auto_attach = pci230_auto_attach,
+ .detach = pci230_detach,
+ .board_name = &pci230_boards[0].name,
+ .offset = sizeof(pci230_boards[0]),
+ .num_names = ARRAY_SIZE(pci230_boards),
+};
+
+static int amplc_pci230_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci230_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id amplc_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, amplc_pci230_pci_table);
+
+static struct pci_driver amplc_pci230_pci_driver = {
+ .name = "amplc_pci230",
+ .id_table = amplc_pci230_pci_table,
+ .probe = amplc_pci230_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci230_driver, amplc_pci230_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/amplc_pci263.c b/drivers/staging/comedi/drivers/amplc_pci263.c
new file mode 100644
index 00000000000..93ed03ee416
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci263.c
@@ -0,0 +1,120 @@
+/*
+ comedi/drivers/amplc_pci263.c
+ Driver for Amplicon PCI263 relay board.
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: amplc_pci263
+Description: Amplicon PCI263
+Author: Ian Abbott <abbotti@mev.co.uk>
+Devices: [Amplicon] PCI263 (amplc_pci263)
+Updated: Fri, 12 Apr 2013 15:19:36 +0100
+Status: works
+
+Configuration options: not applicable, uses PCI auto config
+
+The 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 <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#define PCI263_DRIVER_NAME "amplc_pci263"
+
+/* PCI263 PCI configuration register information */
+#define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c
+
+static int pci263_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase);
+ outb((s->state >> 8) & 0xff, dev->iobase + 1);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pci263_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pci_dev, 2);
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pci263_do_insn_bits;
+ /* read initial relay state */
+ s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8);
+
+ return 0;
+}
+
+static struct comedi_driver amplc_pci263_driver = {
+ .driver_name = PCI263_DRIVER_NAME,
+ .module = THIS_MODULE,
+ .auto_attach = pci263_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static const struct pci_device_id pci263_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263) },
+ {0}
+};
+MODULE_DEVICE_TABLE(pci, pci263_pci_table);
+
+static int amplc_pci263_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &amplc_pci263_driver,
+ id->driver_data);
+}
+
+static struct pci_driver amplc_pci263_pci_driver = {
+ .name = PCI263_DRIVER_NAME,
+ .id_table = pci263_pci_table,
+ .probe = &amplc_pci263_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(amplc_pci263_driver, amplc_pci263_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Amplicon PCI263 relay board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/c6xdigio.c b/drivers/staging/comedi/drivers/c6xdigio.c
new file mode 100644
index 00000000000..e03dd6e7141
--- /dev/null
+++ b/drivers/staging/comedi/drivers/c6xdigio.c
@@ -0,0 +1,307 @@
+/*
+ * c6xdigio.c
+ * Hardware driver for Mechatronic Systems Inc. C6x_DIGIO DSP daughter card.
+ * http://web.archive.org/web/%2A/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.
+ */
+
+/*
+ * 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
+ *
+ * Configuration Options:
+ * [0] - base address
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/timex.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+#include <linux/pnp.h>
+
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define C6XDIGIO_DATA_REG 0x00
+#define C6XDIGIO_DATA_CHAN(x) (((x) + 1) << 4)
+#define C6XDIGIO_DATA_PWM (1 << 5)
+#define C6XDIGIO_DATA_ENCODER (1 << 6)
+#define C6XDIGIO_STATUS_REG 0x01
+#define C6XDIGIO_CTRL_REG 0x02
+
+#define C6XDIGIO_TIME_OUT 20
+
+static int c6xdigio_chk_status(struct comedi_device *dev, unsigned long context)
+{
+ unsigned int status;
+ int timeout = 0;
+
+ do {
+ status = inb(dev->iobase + C6XDIGIO_STATUS_REG);
+ if ((status & 0x80) != context)
+ return 0;
+ timeout++;
+ } while (timeout < C6XDIGIO_TIME_OUT);
+
+ return -EBUSY;
+}
+
+static int c6xdigio_write_data(struct comedi_device *dev,
+ unsigned int val, unsigned int status)
+{
+ outb_p(val, dev->iobase + C6XDIGIO_DATA_REG);
+ return c6xdigio_chk_status(dev, status);
+}
+
+static int c6xdigio_get_encoder_bits(struct comedi_device *dev,
+ unsigned int *bits,
+ unsigned int cmd,
+ unsigned int status)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + C6XDIGIO_STATUS_REG);
+ val >>= 3;
+ val &= 0x07;
+
+ *bits = val;
+
+ return c6xdigio_write_data(dev, cmd, status);
+}
+
+static void c6xdigio_pwm_write(struct comedi_device *dev,
+ unsigned int chan, unsigned int val)
+{
+ unsigned int cmd = C6XDIGIO_DATA_PWM | C6XDIGIO_DATA_CHAN(chan);
+ unsigned int bits;
+
+ if (val > 498)
+ val = 498;
+ if (val < 2)
+ val = 2;
+
+ bits = (val >> 0) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+ bits = (val >> 2) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
+ bits = (val >> 4) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+ bits = (val >> 6) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
+ bits = (val >> 8) & 0x03;
+ c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
+
+ c6xdigio_write_data(dev, 0x00, 0x80);
+}
+
+static int c6xdigio_encoder_read(struct comedi_device *dev,
+ unsigned int chan)
+{
+ unsigned int cmd = C6XDIGIO_DATA_ENCODER | C6XDIGIO_DATA_CHAN(chan);
+ unsigned int val = 0;
+ unsigned int bits;
+
+ c6xdigio_write_data(dev, cmd, 0x00);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 0);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 3);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 6);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 9);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 12);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 15);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
+ val |= (bits << 18);
+
+ c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
+ val |= (bits << 21);
+
+ c6xdigio_write_data(dev, 0x00, 0x80);
+
+ return val;
+}
+
+static int c6xdigio_pwm_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 val = (s->state >> (16 * chan)) & 0xffff;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ c6xdigio_pwm_write(dev, chan, val);
+ }
+
+ /*
+ * There are only 2 PWM channels and they have a maxdata of 500.
+ * Instead of allocating private data to save the values in for
+ * readback this driver just packs the values for the two channels
+ * in the s->state.
+ */
+ s->state &= (0xffff << (16 * chan));
+ s->state |= (val << (16 * chan));
+
+ return insn->n;
+}
+
+static int c6xdigio_pwm_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ val = (s->state >> (16 * chan)) & 0xffff;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = val;
+
+ return insn->n;
+}
+
+static int c6xdigio_encoder_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = c6xdigio_encoder_read(dev, chan);
+
+ /* munge two's complement value to offset binary */
+ data[i] = comedi_offset_munge(s, val);
+ }
+
+ return insn->n;
+}
+
+static void c6xdigio_init(struct comedi_device *dev)
+{
+ /* Initialize the PWM */
+ c6xdigio_write_data(dev, 0x70, 0x00);
+ c6xdigio_write_data(dev, 0x74, 0x80);
+ c6xdigio_write_data(dev, 0x70, 0x00);
+ c6xdigio_write_data(dev, 0x00, 0x80);
+
+ /* Reset the encoders */
+ c6xdigio_write_data(dev, 0x68, 0x00);
+ c6xdigio_write_data(dev, 0x6c, 0x80);
+ c6xdigio_write_data(dev, 0x68, 0x00);
+ c6xdigio_write_data(dev, 0x00, 0x80);
+}
+
+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)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x03);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Make sure that PnP ports get activated */
+ pnp_register_driver(&c6xdigio_pnp_driver);
+
+ s = &dev->subdevices[0];
+ /* pwm output subdevice */
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 2;
+ s->maxdata = 500;
+ s->range_table = &range_unknown;
+ s->insn_write = c6xdigio_pwm_insn_write;
+ s->insn_read = c6xdigio_pwm_insn_read;
+
+ s = &dev->subdevices[1];
+ /* encoder (counter) subdevice */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
+ s->n_chan = 2;
+ s->maxdata = 0xffffff;
+ s->range_table = &range_unknown;
+ s->insn_read = c6xdigio_encoder_insn_read;
+
+ /* I will call this init anyway but more than likely the DSP board */
+ /* will not be connected when device driver is loaded. */
+ c6xdigio_init(dev);
+
+ return 0;
+}
+
+static void c6xdigio_detach(struct comedi_device *dev)
+{
+ comedi_legacy_detach(dev);
+ pnp_unregister_driver(&c6xdigio_pnp_driver);
+}
+
+static struct comedi_driver c6xdigio_driver = {
+ .driver_name = "c6xdigio",
+ .module = THIS_MODULE,
+ .attach = c6xdigio_attach,
+ .detach = c6xdigio_detach,
+};
+module_comedi_driver(c6xdigio_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card");
+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 00000000000..eb1b92d72e8
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_das16_cs.c
@@ -0,0 +1,372 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+
+ PCMCIA support code for this driver 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
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+
+*/
+/*
+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 <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "comedi_fc.h"
+#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[] = {
+ {
+ .name = "PC-CARD DAS16/16-AO",
+ .device_id = 0x0039,
+ .n_ao_chans = 2,
+ }, {
+ .name = "PCM-DAS16s/16",
+ .device_id = 0x4009,
+ .n_ao_chans = 0,
+ }, {
+ .name = "PC-CARD DAS16/16",
+ .device_id = 0x0000, /* unknown */
+ .n_ao_chans = 0,
+ },
+};
+
+struct das16cs_private {
+ unsigned int ao_readback[2];
+ unsigned short status1;
+ unsigned short status2;
+};
+
+static const struct comedi_lrange das16cs_ai_range = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ }
+};
+
+static int das16cs_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + DAS16CS_MISC1);
+ if (status & 0x0080)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16cs_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+ int aref = CR_AREF(insn->chanspec);
+ int ret;
+ int i;
+
+ outw(chan, dev->iobase + DAS16CS_DIO_MUX);
+
+ devpriv->status1 &= ~0xf320;
+ devpriv->status1 |= (aref == AREF_DIFF) ? 0 : 0x0020;
+ outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
+
+ devpriv->status2 &= ~0xff00;
+ switch (range) {
+ case 0:
+ devpriv->status2 |= 0x800;
+ break;
+ case 1:
+ devpriv->status2 |= 0x000;
+ break;
+ case 2:
+ devpriv->status2 |= 0x100;
+ break;
+ case 3:
+ devpriv->status2 |= 0x200;
+ break;
+ }
+ outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
+
+ for (i = 0; i < insn->n; i++) {
+ outw(0, dev->iobase + DAS16CS_ADC_DATA);
+
+ ret = comedi_timeout(dev, s, insn, das16cs_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inw(dev->iobase + DAS16CS_ADC_DATA);
+ }
+
+ return i;
+}
+
+static int das16cs_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ 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 + DAS16CS_MISC1);
+ udelay(1);
+
+ status1 = devpriv->status1 & ~0xf;
+ if (chan)
+ status1 |= 0x0001;
+ else
+ status1 |= 0x0008;
+
+ outw(status1, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+
+ for (bit = 15; bit >= 0; bit--) {
+ int b = (d >> bit) & 0x1;
+
+ b <<= 1;
+ outw(status1 | b | 0x0000, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+ outw(status1 | b | 0x0004, dev->iobase + DAS16CS_MISC1);
+ udelay(1);
+ }
+ /*
+ * Make both DAC0CS and DAC1CS high to load
+ * the new data and update analog the output
+ */
+ outw(status1 | 0x9, dev->iobase + DAS16CS_MISC1);
+ }
+
+ return i;
+}
+
+static int das16cs_ao_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ 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 das16cs_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + DAS16CS_DIO);
+
+ data[1] = inw(dev->iobase + DAS16CS_DIO);
+
+ return insn->n;
+}
+
+static int das16cs_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das16cs_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ devpriv->status2 &= ~0x00c0;
+ devpriv->status2 |= (s->io_bits & 0xf0) ? 0x0080 : 0;
+ devpriv->status2 |= (s->io_bits & 0x0f) ? 0x0040 : 0;
+
+ outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
+
+ return insn->n;
+}
+
+static const void *das16cs_find_boardinfo(struct comedi_device *dev,
+ struct pcmcia_device *link)
+{
+ const struct das16cs_board *board;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) {
+ board = &das16cs_boards[i];
+ if (board->device_id == link->card_id)
+ return board;
+ }
+
+ return NULL;
+}
+
+static int das16cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ const struct das16cs_board *board;
+ struct das16cs_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ board = das16cs_find_boardinfo(dev, link);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* 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 = &dev->subdevices[1];
+ /* analog output subdevice */
+ if (board->n_ao_chans) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ao_chans;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar10;
+ s->insn_write = &das16cs_ao_winsn;
+ s->insn_read = &das16cs_ao_rinsn;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* 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 = das16cs_dio_insn_bits;
+ s->insn_config = das16cs_dio_insn_config;
+
+ return 0;
+}
+
+static struct comedi_driver driver_das16cs = {
+ .driver_name = "cb_das16_cs",
+ .module = THIS_MODULE,
+ .auto_attach = das16cs_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int das16cs_pcmcia_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_das16cs);
+}
+
+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);
+
+static struct pcmcia_driver das16cs_driver = {
+ .name = "cb_das16_cs",
+ .owner = THIS_MODULE,
+ .id_table = das16cs_id_table,
+ .probe = das16cs_pcmcia_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_das16cs, das16cs_driver);
+
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/cb_pcidas.c b/drivers/staging/comedi/drivers/cb_pcidas.c
new file mode 100644
index 00000000000..7377da1aff7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidas.c
@@ -0,0 +1,1652 @@
+/*
+ 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 <imr@oersted.dtu.dk>
+ Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: cb_pcidas
+Description: MeasurementComputing PCI-DAS series
+ with the AMCC S5933 PCI controller
+Author: Ivan Martinez <imr@oersted.dtu.dk>,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+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: not applicable, uses PCI auto config
+
+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 trigger (level high)
+ start_arg == CR_INVERT => gated trigger (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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+#include "8255.h"
+#include "amcc_s5933.h"
+#include "comedi_fc.h"
+
+#define AI_BUFFER_SIZE 1024 /* max ai fifo size */
+#define AO_BUFFER_SIZE 1024 /* max ao fifo size */
+#define NUM_CHANNELS_8800 8
+#define NUM_CHANNELS_7376 1
+#define NUM_CHANNELS_8402 2
+#define NUM_CHANNELS_DAC08 1
+
+/* Control/Status registers */
+#define INT_ADCFIFO 0 /* INTERRUPT / ADC FIFO register */
+#define INT_EOS 0x1 /* int end of scan */
+#define INT_FHF 0x2 /* int fifo half full */
+#define INT_FNE 0x3 /* int fifo not empty */
+#define INT_MASK 0x3 /* mask of int select bits */
+#define INTE 0x4 /* int enable */
+#define DAHFIE 0x8 /* dac half full int enable */
+#define EOAIE 0x10 /* end of acq. int enable */
+#define DAHFI 0x20 /* dac half full status / clear */
+#define EOAI 0x40 /* end of acq. int status / clear */
+#define INT 0x80 /* int status / clear */
+#define EOBI 0x200 /* end of burst int status */
+#define ADHFI 0x400 /* half-full int status */
+#define ADNEI 0x800 /* fifo not empty int status (latch) */
+#define ADNE 0x1000 /* fifo not empty status (realtime) */
+#define DAEMIE 0x1000 /* dac empty int enable */
+#define LADFUL 0x2000 /* fifo overflow / clear */
+#define DAEMI 0x4000 /* dac fifo empty int status / clear */
+
+#define ADCMUX_CONT 2 /* ADC CHANNEL MUX AND CONTROL reg */
+#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 mode */
+#define SE 0x400 /* Inputs in single-ended mode */
+#define PACER_MASK 0x3000 /* pacer source bits */
+#define PACER_INT 0x1000 /* int. pacer */
+#define PACER_EXT_FALL 0x2000 /* ext. falling edge */
+#define PACER_EXT_RISE 0x3000 /* ext. 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 /* ext. start trigger */
+#define ANALOG_TRIGGER 0x3 /* ext. analog trigger */
+#define TRIGGER_MASK 0x3 /* start trigger mask */
+#define TGPOL 0x04 /* invert trigger (1602 only) */
+#define TGSEL 0x08 /* edge/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 /* calibration source enable */
+#define SERIAL_DATA_IN_BIT 0x8000 /* serial data bit going to caldac */
+
+#define DAC_CSR 0x8 /* dac control and status register */
+#define DACEN 0x02 /* dac enable */
+#define DAC_MODE_UPDATE_BOTH 0x80 /* update both dacs */
+
+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 */
+#define DAC_EMPTY 0x1 /* fifo empty, read, write clear */
+#define DAC_START 0x4 /* start/arm fifo operations */
+#define DAC_PACER_MASK 0x18 /* bits that set pacer source */
+#define DAC_PACER_INT 0x8 /* int. pacing */
+#define DAC_PACER_EXT_FALL 0x10 /* ext. pacing, falling edge */
+#define DAC_PACER_EXT_RISE 0x18 /* ext. 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 */
+
+#define IS_UNIPOLAR 0x4 /* unipolar range mask */
+
+/* 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,
+};
+
+enum cb_pcidas_boardid {
+ BOARD_PCIDAS1602_16,
+ BOARD_PCIDAS1200,
+ BOARD_PCIDAS1602_12,
+ BOARD_PCIDAS1200_JR,
+ BOARD_PCIDAS1602_16_JR,
+ BOARD_PCIDAS1000,
+ BOARD_PCIDAS1001,
+ BOARD_PCIDAS1002,
+};
+
+struct cb_pcidas_board {
+ const char *name;
+ int ai_nchan; /* Inputs in single-ended 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 scan speed for 1602 series */
+ int fifo_size; /* number of samples fifo can hold */
+ const struct comedi_lrange *ranges;
+ enum trimpot_model trimpot;
+ unsigned has_dac08:1;
+ unsigned is_1602:1;
+};
+
+static const struct cb_pcidas_board cb_pcidas_boards[] = {
+ [BOARD_PCIDAS1602_16] = {
+ .name = "pci-das1602/16",
+ .ai_nchan = 16,
+ .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,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1200] = {
+ .name = "pci-das1200",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 3200,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1602_12] = {
+ .name = "pci-das1602/12",
+ .ai_nchan = 16,
+ .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,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1200_JR] = {
+ .name = "pci-das1200/jr",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 3200,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1602_16_JR] = {
+ .name = "pci-das1602/16/jr",
+ .ai_nchan = 16,
+ .ai_bits = 16,
+ .ai_speed = 5000,
+ .fifo_size = 512,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD8402,
+ .has_dac08 = 1,
+ .is_1602 = 1,
+ },
+ [BOARD_PCIDAS1000] = {
+ .name = "pci-das1000",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 4000,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1001] = {
+ .name = "pci-das1001",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 6800,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_alt_ranges,
+ .trimpot = AD7376,
+ },
+ [BOARD_PCIDAS1002] = {
+ .name = "pci-das1002",
+ .ai_nchan = 16,
+ .ai_bits = 12,
+ .ai_speed = 6800,
+ .ao_nchan = 2,
+ .fifo_size = 1024,
+ .ranges = &cb_pcidas_ranges,
+ .trimpot = AD7376,
+ },
+};
+
+struct cb_pcidas_private {
+ /* 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;
+ /* number of analog input samples remaining */
+ unsigned int count;
+ /* bits to write to registers */
+ unsigned int adc_fifo_bits;
+ unsigned int s5933_intcsr_bits;
+ unsigned int ao_control_bits;
+ /* fifo buffers */
+ unsigned short ai_buffer[AI_BUFFER_SIZE];
+ unsigned short ao_buffer[AO_BUFFER_SIZE];
+ /* divisors of master clock for analog output pacing */
+ unsigned int ao_divisor1;
+ unsigned int ao_divisor2;
+ /* number of analog output samples remaining */
+ unsigned int ao_count;
+ /* cached values for readback */
+ unsigned short ao_value[2];
+ unsigned int caldac_value[NUM_CHANNELS_8800];
+ unsigned int trimpot_value[NUM_CHANNELS_8402];
+ unsigned int dac08_value;
+ unsigned int calibration_source;
+};
+
+static inline unsigned int cal_enable_bits(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ return CAL_EN_BIT | CAL_SRC_BITS(devpriv->calibration_source);
+}
+
+static int cb_pcidas_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = inw(devpriv->control_status + ADCMUX_CONT);
+ if (status & EOC)
+ return 0;
+ return -EBUSY;
+}
+
+static int cb_pcidas_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned int bits;
+ int ret;
+ int n;
+
+ /* enable calibration input if appropriate */
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ outw(cal_enable_bits(dev),
+ devpriv->control_status + CALIBRATION_REG);
+ chan = 0;
+ } else {
+ outw(0, devpriv->control_status + CALIBRATION_REG);
+ }
+
+ /* set mux limits and gain */
+ bits = BEGIN_SCAN(chan) | END_SCAN(chan) | GAIN_BITS(range);
+ /* set unipolar/bipolar */
+ if (range & IS_UNIPOLAR)
+ bits |= UNIP;
+ /* set single-ended/differential */
+ if (aref != 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 */
+ ret = comedi_timeout(dev, s, insn, cb_pcidas_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ data[n] = inw(devpriv->adc_fifo + ADCDATA);
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ int id = data[0];
+ unsigned int source = data[1];
+
+ switch (id) {
+ case INSN_CONFIG_ALT_SOURCE:
+ if (source >= 8) {
+ dev_err(dev->class_dev,
+ "invalid calibration source: %i\n",
+ source);
+ return -EINVAL;
+ }
+ devpriv->calibration_source = source;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return insn->n;
+}
+
+/* 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long flags;
+
+ /* set channel and range */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->ao_control_bits &= (~DAC_MODE_UPDATE_BOTH &
+ ~DAC_RANGE_MASK(chan));
+ devpriv->ao_control_bits |= (DACEN | DAC_RANGE(chan, range));
+ outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* remember value for readback */
+ devpriv->ao_value[chan] = data[0];
+
+ /* send data */
+ outw(data[0], devpriv->ao_registers + DAC_DATA_REG(chan));
+
+ return insn->n;
+}
+
+/* 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long flags;
+
+ /* clear dac fifo */
+ outw(0, devpriv->ao_registers + DACFIFOCLR);
+
+ /* set channel and range */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->ao_control_bits &= (~DAC_CHAN_EN(0) & ~DAC_CHAN_EN(1) &
+ ~DAC_RANGE_MASK(chan) & ~DAC_PACER_MASK);
+ devpriv->ao_control_bits |= (DACEN | DAC_RANGE(chan, range) |
+ DAC_CHAN_EN(chan) | 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[chan] = data[0];
+
+ /* send data */
+ outw(data[0], devpriv->ao_registers + DACDATA);
+
+ return insn->n;
+}
+
+static int cb_pcidas_ao_readback_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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;
+}
+
+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 void write_calibration_bitstream(struct comedi_device *dev,
+ unsigned int register_bits,
+ unsigned int bitstream,
+ unsigned int bitstream_length)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ data[0] = devpriv->caldac_value[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+/* 1602/16 pregain offset */
+static void dac08_write(struct comedi_device *dev, unsigned int value)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long cal_reg;
+
+ if (devpriv->dac08_value != value) {
+ devpriv->dac08_value = value;
+
+ cal_reg = devpriv->control_status + CALIBRATION_REG;
+
+ value &= 0xff;
+ value |= cal_enable_bits(dev);
+
+ /* latch the new value into the caldac */
+ outw(value, cal_reg);
+ udelay(1);
+ outw(value | SELECT_DAC08_BIT, cal_reg);
+ udelay(1);
+ outw(value, cal_reg);
+ udelay(1);
+ }
+}
+
+static int dac08_write_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ dac08_write(dev, data[i]);
+
+ return insn->n;
+}
+
+static int dac08_read_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ data[0] = devpriv->dac08_value;
+
+ return 1;
+}
+
+static int trimpot_7376_write(struct comedi_device *dev, uint8_t value)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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 cb_pcidas_trimpot_write(struct comedi_device *dev,
+ unsigned int channel, unsigned int value)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ 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)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+
+ data[0] = devpriv->trimpot_value[channel];
+
+ return 1;
+}
+
+static int cb_pcidas_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int cb_pcidas_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->start_src == TRIG_EXT &&
+ (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT))
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ 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 |= -EINVAL;
+ }
+ if (!thisboard->is_1602 && (cmd->start_arg & CR_INVERT)) {
+ cmd->start_arg &= (CR_FLAGS_MASK & ~CR_INVERT);
+ err |= -EINVAL;
+ }
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ai_speed * cmd->chanlist_len);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void cb_pcidas_ai_load_counters(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long timer_base = devpriv->pacer_counter_dio + ADC8254;
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+}
+
+static int cb_pcidas_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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);
+
+ /* load counters */
+ if (cmd->scan_begin_src == TRIG_TIMER || cmd->convert_src == TRIG_TIMER)
+ cb_pcidas_ai_load_counters(dev);
+
+ /* 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) {
+ /* interrupt end of burst */
+ devpriv->adc_fifo_bits |= INT_EOS;
+ } else {
+ /* interrupt fifo not empty */
+ devpriv->adc_fifo_bits |= INT_FNE;
+ }
+ } else {
+ /* interrupt fifo half full */
+ devpriv->adc_fifo_bits |= INT_FHF;
+ }
+
+ /* 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 { /* TRIG_EXT */
+ bits |= EXT_TRIGGER | TGEN | XTRCL;
+ if (thisboard->is_1602) {
+ if (cmd->start_arg & CR_INVERT)
+ bits |= TGPOL;
+ if (cmd->start_arg & CR_EDGE)
+ bits |= TGSEL;
+ }
+ }
+ if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1)
+ bits |= BURSTE;
+ outw(bits, devpriv->control_status + TRIG_CONTSTAT);
+
+ return 0;
+}
+
+static int cb_pcidas_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+
+ if (cmd->chanlist_len > 1) {
+ unsigned int chan1 = CR_CHAN(cmd->chanlist[1]);
+
+ if (chan0 != 0 || chan1 != 1) {
+ dev_dbg(dev->class_dev,
+ "channels must be ordered channel 0, channel 1 in chanlist\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int cb_pcidas_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ao_scan_speed);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->ao_divisor1,
+ &devpriv->ao_divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/* cancel analog input command */
+static int cb_pcidas_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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;
+}
+
+static int cb_pcidas_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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 != cmd->start_arg)
+ 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;
+
+ /* 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);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ async->inttrig = NULL;
+
+ return 0;
+}
+
+static void cb_pcidas_ao_load_counters(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ unsigned long timer_base = devpriv->pacer_counter_dio + DAC8254;
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 1, devpriv->ao_divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->ao_divisor2);
+}
+
+static int cb_pcidas_ao_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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)
+ cb_pcidas_ao_load_counters(dev);
+
+ /* 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;
+}
+
+/* cancel analog output command */
+static int cb_pcidas_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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 handle_ao_interrupt(struct comedi_device *dev, unsigned int status)
+{
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ 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;
+
+ 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");
+ 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 (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);
+ }
+
+ cfc_handle_events(dev, s);
+}
+
+static irqreturn_t cb_pcidas_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = (struct comedi_device *)d;
+ const struct cb_pcidas_board *thisboard = comedi_board(dev);
+ struct cb_pcidas_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ 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)
+ return IRQ_NONE;
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ s5933_status = inl(devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ 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);
+
+ /* 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 (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 (cmd->stop_src == TRIG_COUNT && devpriv->count == 0)
+ async->events |= COMEDI_CB_EOA;
+ /* 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 (cmd->stop_src == TRIG_COUNT &&
+ --devpriv->count == 0) {
+ /* end of acquisition */
+ 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);
+ async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ }
+
+ cfc_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static int cb_pcidas_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct cb_pcidas_board *thisboard = NULL;
+ struct cb_pcidas_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(cb_pcidas_boards))
+ thisboard = &cb_pcidas_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->s5933_config = pci_resource_start(pcidev, 0);
+ devpriv->control_status = pci_resource_start(pcidev, 1);
+ devpriv->adc_fifo = pci_resource_start(pcidev, 2);
+ devpriv->pacer_counter_dio = pci_resource_start(pcidev, 3);
+ if (thisboard->ao_nchan)
+ devpriv->ao_registers = pci_resource_start(pcidev, 4);
+
+ /* disable and clear interrupts on amcc s5933 */
+ outl(INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+
+ if (request_irq(pcidev->irq, cb_pcidas_interrupt,
+ IRQF_SHARED, dev->driver->driver_name, dev)) {
+ dev_dbg(dev->class_dev, "unable to allocate irq %d\n",
+ pcidev->irq);
+ return -EINVAL;
+ }
+ dev->irq = pcidev->irq;
+
+ ret = comedi_alloc_subdevices(dev, 7);
+ if (ret)
+ return ret;
+
+ 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_nchan;
+ s->len_chanlist = thisboard->ai_nchan;
+ 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];
+ ret = subdev_8255_init(dev, s, NULL,
+ devpriv->pacer_counter_dio + DIO_8255);
+ if (ret)
+ return ret;
+
+ /* 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 0;
+}
+
+static void cb_pcidas_detach(struct comedi_device *dev)
+{
+ struct cb_pcidas_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->s5933_config) {
+ outl(INTCSR_INBOX_INTR_STATUS,
+ devpriv->s5933_config + AMCC_OP_REG_INTCSR);
+ }
+ }
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver cb_pcidas_driver = {
+ .driver_name = "cb_pcidas",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcidas_auto_attach,
+ .detach = cb_pcidas_detach,
+};
+
+static int cb_pcidas_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidas_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidas_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x0001), BOARD_PCIDAS1602_16 },
+ { PCI_VDEVICE(CB, 0x000f), BOARD_PCIDAS1200 },
+ { PCI_VDEVICE(CB, 0x0010), BOARD_PCIDAS1602_12 },
+ { PCI_VDEVICE(CB, 0x0019), BOARD_PCIDAS1200_JR },
+ { PCI_VDEVICE(CB, 0x001c), BOARD_PCIDAS1602_16_JR },
+ { PCI_VDEVICE(CB, 0x004c), BOARD_PCIDAS1000 },
+ { PCI_VDEVICE(CB, 0x001a), BOARD_PCIDAS1001 },
+ { PCI_VDEVICE(CB, 0x001b), BOARD_PCIDAS1002 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidas_pci_table);
+
+static struct pci_driver cb_pcidas_pci_driver = {
+ .name = "cb_pcidas",
+ .id_table = cb_pcidas_pci_table,
+ .probe = cb_pcidas_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidas_driver, cb_pcidas_pci_driver);
+
+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 00000000000..035c3a17600
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidas64.c
@@ -0,0 +1,4119 @@
+/*
+ comedi/drivers/cb_pcidas64.c
+ This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
+ 64xx, 60xx, and 4020 cards.
+
+ Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+ * Driver: cb_pcidas64
+ * Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series
+ * with the PLX 9080 PCI controller
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ * Updated: Fri, 02 Nov 2012 18:58:55 +0000
+ * 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:
+ * None.
+ *
+ * Manual attachment of PCI cards with the comedi_config utility is not
+ * supported by this driver; they are attached automatically.
+ *
+ * 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 let the maintainers know.
+ */
+
+/*
+
+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
+*/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+#include "8255.h"
+#include "plx9080.h"
+#include "comedi_fc.h"
+
+#define TIMER_BASE 25 /* 40MHz master clock */
+/* 100kHz 'prescaled' clock for slow acquisition,
+ * maybe I'll support this someday */
+#define PRESCALED_TIMER_BASE 10000
+#define DMA_BUFFER_SIZE 0x1000
+
+/* maximum value that can be loaded into board's 24-bit counters*/
+static const int max_counter_value = 0xffffff;
+
+/* PCI-DAS64xxx base addresses */
+
+/* devpriv->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,
+ /* lower 16 bits of adc sample interval counter */
+ ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16,
+ /* upper 8 bits of adc sample interval counter */
+ ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18,
+ /* lower 16 bits of delay interval counter */
+ ADC_DELAY_INTERVAL_LOWER_REG = 0x1a,
+ /* upper 8 bits of delay interval counter */
+ ADC_DELAY_INTERVAL_UPPER_REG = 0x1c,
+ /* lower 16 bits of hardware conversion/scan counter */
+ ADC_COUNT_LOWER_REG = 0x1e,
+ /* upper 8 bits of hardware conversion/scan counter */
+ ADC_COUNT_UPPER_REG = 0x20,
+ 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,
+ /* high channel for internal queue, use adc_chan_bits() inline above */
+ ADC_QUEUE_HIGH_REG = 0x2c,
+ DAC_CONTROL0_REG = 0x50, /* dac control register 0 */
+ DAC_CONTROL1_REG = 0x52, /* dac control register 0 */
+ /* lower 16 bits of dac sample interval counter */
+ DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54,
+ /* upper 8 bits of dac sample interval counter */
+ DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56,
+ 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 {
+ /* hardware status register,
+ * reading this apparently clears pending interrupts as well */
+ HW_STATUS_REG = 0x0,
+ 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 */
+ /* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */
+ ADC_QUEUE_FIFO_REG = 0x100,
+ ADC_FIFO_REG = 0x200, /* adc data fifo */
+ /* dac data fifo, has weird interactions with external channel queue */
+ DAC_FIFO_REG = 0x300,
+};
+
+/* devpriv->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, /* adc interrupt source mask */
+ ADC_INTR_QFULL_BITS = 0x0, /* interrupt fifo quarter 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 mask */
+ EN_ADC_INTR_SRC_BIT = 0x4, /* enable adc interrupt source */
+ EN_ADC_DONE_INTR_BIT = 0x8, /* enable adc acquisition done intr */
+ 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, /* master clock source mask for 4020 */
+ INTERNAL_CLOCK_4020_BITS = 0x1, /* use 40 MHz internal master clock */
+ 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 */
+ /* use 225 nanosec strobe when loading dac instead of 50 nanosec */
+ SLOW_DAC_BIT = 0x400,
+ /* bit with unknown function yet given as default value in pci-das64
+ * manual */
+ HW_CONFIG_DUMMY_BITS = 0x2000,
+ /* bit selects channels 1/0 for analog input/output, otherwise 0/1 */
+ DMA_CH_SELECT_BIT = 0x8000,
+ 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 {
+ /* use trig/ext clk bnc input for analog gate signal */
+ EXT_AGATE_BNC_BIT = 0x8000,
+ /* use trig/ext clk bnc input for external stop trigger signal */
+ EXT_STOP_TRIG_BNC_BIT = 0x4000,
+ /* use trig/ext clk bnc input for external start trigger signal */
+ EXT_START_TRIG_BNC_BIT = 0x2000,
+};
+
+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 */
+ /* external pacing uses falling edge */
+ ADC_EXT_CONV_FALLING_BIT = 0x800,
+ /* enable hardware scan counter */
+ ADC_SAMPLE_COUNTER_EN_BIT = 0x1000,
+ ADC_DMA_DISABLE_BIT = 0x4000, /* disables dma */
+ ADC_ENABLE_BIT = 0x8000, /* master adc enable */
+};
+
+enum adc_control1_contents {
+ /* should be set for boards with > 16 channels */
+ ADC_QUEUE_CONFIG_BIT = 0x1,
+ 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 */
+ /* non-referenced single-ended (common-mode input) */
+ ADC_COMMON_BIT = 0x2000,
+ 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 {
+ /* bits that set what source the adc converter measures */
+ ADC_SRC_4020_MASK = 0x70,
+ /* make bnc trig/ext clock threshold 0V instead of 2.5V */
+ BNC_TRIG_THRESHOLD_0V_BIT = 0x80,
+};
+
+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 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;
+};
+
+enum pcidas64_boardid {
+ BOARD_PCIDAS6402_16,
+ BOARD_PCIDAS6402_12,
+ BOARD_PCIDAS64_M1_16,
+ BOARD_PCIDAS64_M2_16,
+ BOARD_PCIDAS64_M3_16,
+ BOARD_PCIDAS6013,
+ BOARD_PCIDAS6014,
+ BOARD_PCIDAS6023,
+ BOARD_PCIDAS6025,
+ BOARD_PCIDAS6030,
+ BOARD_PCIDAS6031,
+ BOARD_PCIDAS6032,
+ BOARD_PCIDAS6033,
+ BOARD_PCIDAS6034,
+ BOARD_PCIDAS6035,
+ BOARD_PCIDAS6036,
+ BOARD_PCIDAS6040,
+ BOARD_PCIDAS6052,
+ BOARD_PCIDAS6070,
+ BOARD_PCIDAS6071,
+ BOARD_PCIDAS4020_12,
+ BOARD_PCIDAS6402_16_JR,
+ BOARD_PCIDAS64_M1_16_JR,
+ BOARD_PCIDAS64_M2_16_JR,
+ BOARD_PCIDAS64_M3_16_JR,
+ BOARD_PCIDAS64_M1_14,
+ BOARD_PCIDAS64_M2_14,
+ BOARD_PCIDAS64_M3_14,
+};
+
+struct pcidas64_board {
+ const char *name;
+ 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 scan speed */
+ const struct comedi_lrange *ao_range_table;
+ const int *ao_range_code;
+ const struct hw_fifo_info *const ai_fifo;
+ /* different board families have slightly different registers */
+ enum register_layout layout;
+ 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(const 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[] = {
+ [BOARD_PCIDAS6402_16] = {
+ .name = "pci-das6402/16",
+ .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,
+ },
+ [BOARD_PCIDAS6402_12] = {
+ .name = "pci-das6402/12", /* XXX check */
+ .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,
+ },
+ [BOARD_PCIDAS64_M1_16] = {
+ .name = "pci-das64/m1/16",
+ .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,
+ },
+ [BOARD_PCIDAS64_M2_16] = {
+ .name = "pci-das64/m2/16",
+ .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,
+ },
+ [BOARD_PCIDAS64_M3_16] = {
+ .name = "pci-das64/m3/16",
+ .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,
+ },
+ [BOARD_PCIDAS6013] = {
+ .name = "pci-das6013",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6014] = {
+ .name = "pci-das6014",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6023] = {
+ .name = "pci-das6023",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6025] = {
+ .name = "pci-das6025",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 1,
+ },
+ [BOARD_PCIDAS6030] = {
+ .name = "pci-das6030",
+ .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,
+ },
+ [BOARD_PCIDAS6031] = {
+ .name = "pci-das6031",
+ .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,
+ },
+ [BOARD_PCIDAS6032] = {
+ .name = "pci-das6032",
+ .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,
+ },
+ [BOARD_PCIDAS6033] = {
+ .name = "pci-das6033",
+ .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,
+ },
+ [BOARD_PCIDAS6034] = {
+ .name = "pci-das6034",
+ .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,
+ },
+ [BOARD_PCIDAS6035] = {
+ .name = "pci-das6035",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6036] = {
+ .name = "pci-das6036",
+ .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 = &range_bipolar10,
+ .ao_range_code = ao_range_code_60xx,
+ .ai_fifo = &ai_fifo_60xx,
+ .has_8255 = 0,
+ },
+ [BOARD_PCIDAS6040] = {
+ .name = "pci-das6040",
+ .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,
+ },
+ [BOARD_PCIDAS6052] = {
+ .name = "pci-das6052",
+ .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,
+ },
+ [BOARD_PCIDAS6070] = {
+ .name = "pci-das6070",
+ .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,
+ },
+ [BOARD_PCIDAS6071] = {
+ .name = "pci-das6071",
+ .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,
+ },
+ [BOARD_PCIDAS4020_12] = {
+ .name = "pci-das4020/12",
+ .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
+ /*
+ * The device id for these boards is unknown
+ */
+
+ [BOARD_PCIDAS6402_16_JR] = {
+ .name = "pci-das6402/16/jr",
+ .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,
+ },
+ [BOARD_PCIDAS64_M1_16_JR] = {
+ .name = "pci-das64/m1/16/jr",
+ .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,
+ },
+ [BOARD_PCIDAS64_M2_16_JR] = {
+ .name = "pci-das64/m2/16/jr",
+ .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,
+ },
+ [BOARD_PCIDAS64_M3_16_JR] = {
+ .name = "pci-das64/m3/16/jr",
+ .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,
+ },
+ [BOARD_PCIDAS64_M1_14] = {
+ .name = "pci-das64/m1/14",
+ .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,
+ },
+ [BOARD_PCIDAS64_M2_14] = {
+ .name = "pci-das64/m2/14",
+ .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,
+ },
+ [BOARD_PCIDAS64_M3_14] = {
+ .name = "pci-das64/m3/14",
+ .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 inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev,
+ int use_differential)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ if ((thisboard->layout == LAYOUT_64XX && !use_differential) ||
+ (thisboard->layout == LAYOUT_60XX && use_differential))
+ return ADC_SE_DIFF_BIT;
+ else
+ return 0;
+};
+
+struct ext_clock_info {
+ /* master clock divisor to use for scans with external master clock */
+ unsigned int divisor;
+ /* chanspec for master clock input when used as scan begin src */
+ unsigned int chanspec;
+};
+
+/* this structure is for data unique to this hardware driver. */
+struct pcidas64_private {
+ /* base addresses (physical) */
+ 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;
+ /* number of analog input samples remaining */
+ volatile unsigned int ai_count;
+ /* dma buffers for analog input */
+ uint16_t *ai_buffer[MAX_AI_DMA_RING_COUNT];
+ /* physical addresses of ai dma buffers */
+ dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT];
+ /* array of ai dma descriptors read by plx9080,
+ * allocated to get proper alignment */
+ struct plx_dma_desc *ai_dma_desc;
+ /* physical address of ai dma descriptor array */
+ dma_addr_t ai_dma_desc_bus_addr;
+ /* index of the ai dma descriptor/buffer
+ * that is currently being used */
+ volatile unsigned int ai_dma_index;
+ /* dma buffers for analog output */
+ uint16_t *ao_buffer[AO_DMA_RING_COUNT];
+ /* physical addresses of ao dma buffers */
+ dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT];
+ struct plx_dma_desc *ao_dma_desc;
+ dma_addr_t ao_dma_desc_bus_addr;
+ /* keeps track of buffer where the next ao sample should go */
+ volatile unsigned int ao_dma_index;
+ /* number of analog output samples remaining */
+ volatile unsigned long ao_count;
+ /* remember what the analog outputs are set to, to allow readback */
+ volatile unsigned int ao_value[2];
+ unsigned int hw_revision; /* stc chip hardware revision number */
+ /* last bits sent to INTR_ENABLE_REG register */
+ volatile unsigned int intr_enable_bits;
+ /* last bits sent to ADC_CONTROL1_REG register */
+ volatile uint16_t adc_control1_bits;
+ /* last bits sent to FIFO_SIZE_REG register */
+ volatile uint16_t fifo_size_bits;
+ /* last bits sent to HW_CONFIG_REG register */
+ volatile uint16_t hw_config_bits;
+ volatile uint16_t dac_control1_bits;
+ /* last bits written to plx9080 control register */
+ volatile uint32_t plx_control_bits;
+ /* last bits written to plx interrupt control and status register */
+ volatile uint32_t plx_intcsr_bits;
+ /* index of calibration source readable through ai ch0 */
+ volatile int calibration_source;
+ /* bits written to i2c calibration/range register */
+ volatile uint8_t i2c_cal_range_bits;
+ /* configure digital triggers to trigger on falling edge */
+ volatile unsigned int ext_trig_falling;
+ /* 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;
+ unsigned short ao_bounce_buffer[DAC_FIFO_SIZE];
+};
+
+static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev,
+ unsigned int range_index)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ const struct comedi_krange *range =
+ &thisboard->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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ if (thisboard->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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ unsigned int code = thisboard->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;
+}
+
+static void abort_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ /* spinlock for plx dma control/status reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ plx9080_abort_dma(devpriv->plx9080_iobase, channel);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void disable_plx_interrupts(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ devpriv->plx_intcsr_bits = 0;
+ writel(devpriv->plx_intcsr_bits,
+ devpriv->plx9080_iobase + PLX_INTRCS_REG);
+}
+
+static void disable_ai_interrupts(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->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(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static void enable_ai_interrupts(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ 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 (thisboard->layout != LAYOUT_4020)
+ bits |= ADC_INTR_EOSCAN_BITS | EN_ADC_INTR_SRC_BIT;
+ }
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->intr_enable_bits |= bits;
+ writew(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+/* initialize plx9080 chip */
+static void init_plx9080(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ uint32_t bits;
+ void __iomem *plx_iobase = devpriv->plx9080_iobase;
+
+ devpriv->plx_control_bits =
+ readl(devpriv->plx9080_iobase + PLX_CONTROL_REG);
+
+#ifdef __BIG_ENDIAN
+ bits = BIGEND_DMA0 | BIGEND_DMA1;
+#else
+ bits = 0;
+#endif
+ writel(bits, devpriv->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 (thisboard->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(thisboard))
+ writel(bits, plx_iobase + PLX_DMA0_MODE_REG);
+
+ /* enable interrupts on plx 9080 */
+ devpriv->plx_intcsr_bits |=
+ ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE |
+ ICS_DMA0_E | ICS_DMA1_E;
+ writel(devpriv->plx_intcsr_bits,
+ devpriv->plx9080_iobase + PLX_INTRCS_REG);
+}
+
+static void disable_ai_pacing(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ disable_ai_interrupts(dev);
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->adc_control1_bits &= ~ADC_SW_GATE_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->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,
+ devpriv->main_iobase + ADC_CONTROL0_REG);
+}
+
+static int set_ai_fifo_segment_length(struct comedi_device *dev,
+ unsigned int num_entries)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ static const int increment_size = 0x100;
+ const struct hw_fifo_info *const fifo = thisboard->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;
+ devpriv->fifo_size_bits &= ~fifo->fifo_size_reg_mask;
+ devpriv->fifo_size_bits |= bits;
+ writew(devpriv->fifo_size_bits,
+ devpriv->main_iobase + FIFO_SIZE_REG);
+
+ devpriv->ai_fifo_segment_length = num_increments * increment_size;
+
+ return devpriv->ai_fifo_segment_length;
+}
+
+/* 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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ unsigned int num_fifo_entries;
+ int retval;
+ const struct hw_fifo_info *const fifo = thisboard->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;
+
+ return num_samples;
+}
+
+/* query length of fifo */
+static unsigned int ai_fifo_size(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+
+ return devpriv->ai_fifo_segment_length *
+ thisboard->ai_fifo->num_segments *
+ thisboard->ai_fifo->sample_packing_ratio;
+}
+
+static void init_stc_registers(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ 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)
+ devpriv->adc_control1_bits |= ADC_QUEUE_CONFIG_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+
+ /* 6402/16 manual says this register must be initialized to 0xff? */
+ writew(0xff, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+
+ bits = SLOW_DAC_BIT | DMA_CH_SELECT_BIT;
+ if (thisboard->layout == LAYOUT_4020)
+ bits |= INTERNAL_CLOCK_4020_BITS;
+ devpriv->hw_config_bits |= bits;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+
+ writew(0, devpriv->main_iobase + DAQ_SYNC_REG);
+ writew(0, devpriv->main_iobase + CALIBRATION_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* set fifos to maximum size */
+ devpriv->fifo_size_bits |= DAC_FIFO_BITS;
+ set_ai_fifo_segment_length(dev,
+ thisboard->ai_fifo->max_segment_length);
+
+ devpriv->dac_control1_bits = DAC_OUTPUT_ENABLE_BIT;
+ devpriv->intr_enable_bits =
+ /* EN_DAC_INTR_SRC_BIT | DAC_INTR_QEMPTY_BITS | */
+ EN_DAC_DONE_INTR_BIT | EN_DAC_UNDERRUN_BIT;
+ writew(devpriv->intr_enable_bits,
+ devpriv->main_iobase + INTR_ENABLE_REG);
+
+ disable_ai_pacing(dev);
+};
+
+static int alloc_and_init_dma_members(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ int i;
+
+ /* allocate pci dma buffers */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ devpriv->ai_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->ai_buffer_bus_addr[i]);
+ if (devpriv->ai_buffer[i] == NULL)
+ return -ENOMEM;
+
+ }
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ if (ao_cmd_is_supported(thisboard)) {
+ devpriv->ao_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->
+ ao_buffer_bus_addr[i]);
+ if (devpriv->ao_buffer[i] == NULL)
+ return -ENOMEM;
+
+ }
+ }
+ /* allocate dma descriptors */
+ devpriv->ai_dma_desc =
+ pci_alloc_consistent(pcidev, sizeof(struct plx_dma_desc) *
+ ai_dma_ring_count(thisboard),
+ &devpriv->ai_dma_desc_bus_addr);
+ if (devpriv->ai_dma_desc == NULL)
+ return -ENOMEM;
+
+ if (ao_cmd_is_supported(thisboard)) {
+ devpriv->ao_dma_desc =
+ pci_alloc_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ AO_DMA_RING_COUNT,
+ &devpriv->ao_dma_desc_bus_addr);
+ if (devpriv->ao_dma_desc == NULL)
+ return -ENOMEM;
+ }
+ /* initialize dma descriptors */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ devpriv->ai_dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->ai_buffer_bus_addr[i]);
+ if (thisboard->layout == LAYOUT_4020)
+ devpriv->ai_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local1_iobase +
+ ADC_FIFO_REG);
+ else
+ devpriv->ai_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local0_iobase +
+ ADC_FIFO_REG);
+ devpriv->ai_dma_desc[i].transfer_size = cpu_to_le32(0);
+ devpriv->ai_dma_desc[i].next =
+ cpu_to_le32((devpriv->ai_dma_desc_bus_addr +
+ ((i + 1) % ai_dma_ring_count(thisboard)) *
+ sizeof(devpriv->ai_dma_desc[0])) |
+ PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT |
+ PLX_XFER_LOCAL_TO_PCI);
+ }
+ if (ao_cmd_is_supported(thisboard)) {
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ devpriv->ao_dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->ao_buffer_bus_addr[i]);
+ devpriv->ao_dma_desc[i].local_start_addr =
+ cpu_to_le32(devpriv->local0_iobase +
+ DAC_FIFO_REG);
+ devpriv->ao_dma_desc[i].transfer_size = cpu_to_le32(0);
+ devpriv->ao_dma_desc[i].next =
+ cpu_to_le32((devpriv->ao_dma_desc_bus_addr +
+ ((i + 1) % (AO_DMA_RING_COUNT)) *
+ sizeof(devpriv->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)");
+}
+
+/* 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int data_bit = CTL_EE_W;
+ void __iomem *plx_control_addr = devpriv->plx9080_iobase +
+ PLX_CONTROL_REG;
+
+ if (state) {
+ /* set data line high */
+ devpriv->plx_control_bits &= ~data_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_high_udelay);
+ } else { /* set data line low */
+
+ devpriv->plx_control_bits |= data_bit;
+ writel(devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ static const int clock_bit = CTL_USERO;
+ void __iomem *plx_control_addr = devpriv->plx9080_iobase +
+ PLX_CONTROL_REG;
+
+ if (state) {
+ /* set clock line high */
+ devpriv->plx_control_bits &= ~clock_bit;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(i2c_high_udelay);
+ } else { /* set clock line low */
+
+ devpriv->plx_control_bits |= clock_bit;
+ writel(devpriv->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;
+
+ 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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 */
+ devpriv->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);
+}
+
+static int cb_pcidas64_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readw(devpriv->main_iobase + HW_STATUS_REG);
+ if (thisboard->layout == LAYOUT_4020) {
+ status = readw(devpriv->main_iobase + ADC_WRITE_PNTR_REG);
+ if (status)
+ return 0;
+ } else {
+ if (pipe_full_bits(status))
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int bits = 0, n;
+ unsigned int channel, range, aref;
+ unsigned long flags;
+ int ret;
+
+ 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)
+ devpriv->adc_control1_bits |= ADC_DITHER_BIT;
+ else
+ devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ if (thisboard->layout != LAYOUT_4020) {
+ /* use internal queue */
+ devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+
+ /* ALT_SOURCE is internal calibration reference */
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ unsigned int cal_en_bit;
+
+ if (thisboard->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(devpriv->calibration_source),
+ devpriv->main_iobase + CALIBRATION_REG);
+ } else {
+ /* make sure internal calibration source
+ * is turned off */
+ writew(0, devpriv->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),
+ devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
+ /* set start channel, and rest of settings */
+ writew(bits, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ } else {
+ uint8_t old_cal_range_bits = devpriv->i2c_cal_range_bits;
+
+ devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
+ if (insn->chanspec & CR_ALT_SOURCE) {
+ devpriv->i2c_cal_range_bits |=
+ adc_src_4020_bits(devpriv->calibration_source);
+ } else { /* select BNC inputs */
+ devpriv->i2c_cal_range_bits |= adc_src_4020_bits(4);
+ }
+ /* select range */
+ if (range == 0)
+ devpriv->i2c_cal_range_bits |= attenuate_bit(channel);
+ else
+ devpriv->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 != devpriv->i2c_cal_range_bits) {
+ uint8_t i2c_data = devpriv->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, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+ writew(2, devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
+ }
+
+ for (n = 0; n < insn->n; n++) {
+
+ /* clear adc buffer (inside loop for 4020 sake) */
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+ /* trigger conversion, bits sent only matter for 4020 */
+ writew(adc_convert_chan_4020_bits(CR_CHAN(insn->chanspec)),
+ devpriv->main_iobase + ADC_CONVERT_REG);
+
+ /* wait for data */
+ ret = comedi_timeout(dev, s, insn, cb_pcidas64_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ if (thisboard->layout == LAYOUT_4020)
+ data[n] = readl(devpriv->dio_counter_iobase +
+ ADC_FIFO_REG) & 0xffff;
+ else
+ data[n] = readw(devpriv->main_iobase + PIPE1_READ_REG);
+ }
+
+ return n;
+}
+
+static int ai_config_calibration_source(struct comedi_device *dev,
+ unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int source = data[1];
+ int num_calibration_sources;
+
+ if (thisboard->layout == LAYOUT_60XX)
+ num_calibration_sources = 16;
+ else
+ num_calibration_sources = 8;
+ if (source >= num_calibration_sources) {
+ dev_dbg(dev->class_dev, "invalid calibration source: %i\n",
+ source);
+ return -EINVAL;
+ }
+
+ devpriv->calibration_source = source;
+
+ return 2;
+}
+
+static int ai_config_block_size(struct comedi_device *dev, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ int fifo_size;
+ const struct hw_fifo_info *const fifo = thisboard->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int divisor = data[4];
+ int retval = 0;
+
+ if (divisor < 2) {
+ divisor = 2;
+ retval = -EAGAIN;
+ }
+
+ switch (data[1]) {
+ case COMEDI_EV_SCAN_BEGIN:
+ devpriv->ext_clock.divisor = divisor;
+ devpriv->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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ switch (thisboard->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;
+}
+
+/* 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;
+}
+
+/* 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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ 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 (thisboard->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;
+}
+
+static int cb_pcidas64_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *board = comedi_board(dev);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "all elements in chanlist must use the same analog reference\n");
+ return -EINVAL;
+ }
+ }
+
+ if (board->layout == LAYOUT_4020) {
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "chanlist must use consecutive channels\n");
+ return -EINVAL;
+ }
+ }
+ if (cmd->chanlist_len == 3) {
+ dev_dbg(dev->class_dev,
+ "chanlist cannot be 3 channels long, use 1, 2, or 4 channels\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ int err = 0;
+ unsigned int tmp_arg, tmp_arg2;
+ unsigned int triggers;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+
+ triggers = TRIG_TIMER;
+ if (thisboard->layout == LAYOUT_4020)
+ triggers |= TRIG_OTHER;
+ else
+ triggers |= TRIG_FOLLOW;
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, triggers);
+
+ triggers = TRIG_TIMER;
+ if (thisboard->layout == LAYOUT_4020)
+ triggers |= TRIG_NOW;
+ else
+ triggers |= TRIG_EXT;
+ err |= cfc_check_trigger_src(&cmd->convert_src, triggers);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /*
+ * start_arg is the CR_CHAN | CR_INVERT of the
+ * external trigger.
+ */
+ break;
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (thisboard->layout == LAYOUT_4020) {
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ } else {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+ /* if scans are timed faster than conversion rate allows */
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(
+ &cmd->scan_begin_arg,
+ cmd->convert_arg *
+ cmd->chanlist_len);
+ }
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_EXT:
+ break;
+ case TRIG_COUNT:
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ 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;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas64_ai_check_chanlist(dev, s, cmd);
+
+ 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* set software count */
+ devpriv->ai_count = cmd->stop_arg * cmd->chanlist_len;
+ }
+ /* load hardware conversion counter */
+ if (use_hw_sample_counter(cmd)) {
+ writew(cmd->stop_arg & 0xffff,
+ devpriv->main_iobase + ADC_COUNT_LOWER_REG);
+ writew((cmd->stop_arg >> 16) & 0xff,
+ devpriv->main_iobase + ADC_COUNT_UPPER_REG);
+ } else {
+ writew(1, devpriv->main_iobase + ADC_COUNT_LOWER_REG);
+ }
+}
+
+static inline unsigned int dma_transfer_size(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_samples;
+
+ num_samples = devpriv->ai_fifo_segment_length *
+ thisboard->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 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int divisor;
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ divisor = cmd->scan_begin_arg / TIMER_BASE;
+ break;
+ case TRIG_OTHER:
+ divisor = devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ /* select internal/external master clock */
+ devpriv->hw_config_bits &= ~MASTER_CLOCK_4020_MASK;
+ if (cmd->scan_begin_src == TRIG_OTHER) {
+ int chanspec = devpriv->ext_clock.chanspec;
+
+ if (CR_CHAN(chanspec))
+ devpriv->hw_config_bits |= BNC_CLOCK_4020_BITS;
+ else
+ devpriv->hw_config_bits |= EXT_CLOCK_4020_BITS;
+ } else {
+ devpriv->hw_config_bits |= INTERNAL_CLOCK_4020_BITS;
+ }
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+}
+
+static void select_master_clock(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ switch (thisboard->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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,
+ devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+ else
+ writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT |
+ PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ uint32_t convert_counter = 0, scan_counter = 0;
+
+ check_adc_timing(dev, cmd);
+
+ select_master_clock(dev, cmd);
+
+ if (thisboard->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,
+ devpriv->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
+ /* load upper 8 bits of convert interval */
+ writew((convert_counter >> 16) & 0xff,
+ devpriv->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
+ /* load lower 16 bits of scan delay */
+ writew(scan_counter & 0xffff,
+ devpriv->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
+ /* load upper 8 bits of scan delay */
+ writew((scan_counter >> 16) & 0xff,
+ devpriv->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
+}
+
+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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned short bits;
+ int i;
+
+ if (thisboard->layout != LAYOUT_4020) {
+ if (use_internal_queue_6xxx(cmd)) {
+ devpriv->hw_config_bits &= ~EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->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])),
+ devpriv->main_iobase + ADC_QUEUE_HIGH_REG);
+ /* set start channel, and rest of settings */
+ writew(bits,
+ devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ } else {
+ /* use external queue */
+ if (dev->write_subdev && dev->write_subdev->busy) {
+ warn_external_queue(dev);
+ return -EBUSY;
+ }
+ devpriv->hw_config_bits |= EXT_QUEUE_BIT;
+ writew(devpriv->hw_config_bits,
+ devpriv->main_iobase + HW_CONFIG_REG);
+ /* clear DAC buffer to prevent weird interactions */
+ writew(0,
+ devpriv->main_iobase + DAC_BUFFER_CLEAR_REG);
+ /* clear queue pointer */
+ writew(0, devpriv->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,
+ devpriv->main_iobase +
+ ADC_QUEUE_FIFO_REG);
+ }
+ /* doing a queue clear is not specified in board docs,
+ * but required for reliable operation */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+ /* prime queue holding register */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_LOAD_REG);
+ }
+ } else {
+ unsigned short old_cal_range_bits = devpriv->i2c_cal_range_bits;
+
+ devpriv->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
+ /* select BNC inputs */
+ devpriv->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)
+ devpriv->i2c_cal_range_bits |=
+ attenuate_bit(channel);
+ else
+ devpriv->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 != devpriv->i2c_cal_range_bits) {
+ uint8_t i2c_data = devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ /* 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,
+ devpriv->plx9080_iobase + PLX_DMA1_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG);
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA1_LOCAL_ADDRESS_REG);
+ writel(descriptor_bits,
+ devpriv->plx9080_iobase + PLX_DMA1_DESCRIPTOR_REG);
+ } else {
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
+ writel(0,
+ devpriv->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG);
+ writel(descriptor_bits,
+ devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
+ }
+}
+
+static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ 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, devpriv->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 */
+ devpriv->adc_control1_bits |= ADC_SW_GATE_BIT;
+ devpriv->adc_control1_bits &= ~ADC_DITHER_BIT;
+ if (thisboard->layout != LAYOUT_4020) {
+ devpriv->adc_control1_bits &= ~ADC_MODE_MASK;
+ if (cmd->convert_src == TRIG_EXT)
+ /* good old mode 13 */
+ devpriv->adc_control1_bits |= adc_mode_bits(13);
+ else
+ /* mode 8. What else could you need? */
+ devpriv->adc_control1_bits |= adc_mode_bits(8);
+ } else {
+ devpriv->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK;
+ if (cmd->chanlist_len == 4)
+ devpriv->adc_control1_bits |= FOUR_CHANNEL_4020_BITS;
+ else if (cmd->chanlist_len == 2)
+ devpriv->adc_control1_bits |= TWO_CHANNEL_4020_BITS;
+ devpriv->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK;
+ devpriv->adc_control1_bits |=
+ adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0]));
+ devpriv->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK;
+ devpriv->adc_control1_bits |=
+ adc_hi_chan_4020_bits(CR_CHAN(cmd->chanlist
+ [cmd->chanlist_len - 1]));
+ }
+ writew(devpriv->adc_control1_bits,
+ devpriv->main_iobase + ADC_CONTROL1_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* clear adc buffer */
+ writew(0, devpriv->main_iobase + ADC_BUFFER_CLEAR_REG);
+
+ if ((cmd->flags & TRIG_WAKE_EOS) == 0 ||
+ thisboard->layout == LAYOUT_4020) {
+ devpriv->ai_dma_index = 0;
+
+ /* set dma transfer size */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++)
+ devpriv->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,
+ devpriv->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 (thisboard->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, devpriv->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, devpriv->main_iobase + ADC_CONTROL0_REG);
+
+ devpriv->ai_cmd_running = 1;
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* start acquisition */
+ if (cmd->start_src == TRIG_NOW)
+ writew(0, devpriv->main_iobase + ADC_START_REG);
+
+ return 0;
+}
+
+/* read num_samples from 16 bit wide ai fifo */
+static void pio_drain_ai_fifo_16(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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(devpriv->main_iobase + ADC_READ_PNTR_REG) &
+ 0x7fff;
+ write_index = readw(devpriv->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(devpriv->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);
+
+ if (read_segment != write_segment)
+ num_samples =
+ devpriv->ai_fifo_segment_length - read_index;
+ else
+ num_samples = write_index - read_index;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->ai_count == 0)
+ break;
+ if (num_samples > devpriv->ai_count)
+ num_samples = devpriv->ai_count;
+
+ devpriv->ai_count -= num_samples;
+ }
+
+ if (num_samples < 0) {
+ dev_err(dev->class_dev,
+ "cb_pcidas64: bug! num_samples < 0\n");
+ break;
+ }
+
+ for (i = 0; i < num_samples; i++) {
+ cfc_write_to_buffer(s,
+ readw(devpriv->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 pcidas64_private *devpriv = dev->private;
+ 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(devpriv->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff;
+ int read_code =
+ readw(devpriv->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (max_transfer > devpriv->ai_count)
+ max_transfer = devpriv->ai_count;
+
+ }
+ for (i = 0; read_code != write_code && i < max_transfer;) {
+ fifo_data = readl(devpriv->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(devpriv->main_iobase + ADC_READ_PNTR_REG) &
+ 0x7fff;
+ }
+ devpriv->ai_count -= i;
+}
+
+/* empty fifo */
+static void pio_drain_ai_fifo(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ if (thisboard->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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_async *async = dev->read_subdev->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ uint32_t next_transfer_addr;
+ int j;
+ int num_samples = 0;
+ void __iomem *pci_addr_reg;
+
+ if (channel)
+ pci_addr_reg =
+ devpriv->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG;
+ else
+ pci_addr_reg =
+ devpriv->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 <
+ devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] ||
+ next_transfer_addr >=
+ devpriv->ai_buffer_bus_addr[devpriv->ai_dma_index] +
+ DMA_BUFFER_SIZE) && j < ai_dma_ring_count(thisboard); j++) {
+ /* transfer data from dma buffer to comedi buffer */
+ num_samples = dma_transfer_size(dev);
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (num_samples > devpriv->ai_count)
+ num_samples = devpriv->ai_count;
+ devpriv->ai_count -= num_samples;
+ }
+ cfc_write_array_to_buffer(dev->read_subdev,
+ devpriv->ai_buffer[devpriv->
+ ai_dma_index],
+ num_samples * sizeof(uint16_t));
+ devpriv->ai_dma_index = (devpriv->ai_dma_index + 1) %
+ ai_dma_ring_count(thisboard);
+ }
+ /* 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)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ 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(devpriv->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,
+ devpriv->plx9080_iobase + PLX_DMA1_CS_REG);
+
+ if (dma1_status & PLX_DMA_EN_BIT)
+ drain_dma_buffers(dev, 1);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* drain fifo with pio */
+ if ((status & ADC_DONE_BIT) ||
+ ((cmd->flags & TRIG_WAKE_EOS) &&
+ (status & ADC_INTR_PENDING_BIT) &&
+ (thisboard->layout != LAYOUT_4020))) {
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->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)devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int buffer_index;
+
+ if (devpriv->ao_dma_index == 0)
+ buffer_index = AO_DMA_RING_COUNT - 1;
+ else
+ buffer_index = devpriv->ao_dma_index - 1;
+ return buffer_index;
+}
+
+static int last_ao_dma_load_completed(struct comedi_device *dev)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int buffer_index;
+ unsigned int transfer_address;
+ unsigned short dma_status;
+
+ buffer_index = prev_ao_dma_index(dev);
+ dma_status = readb(devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ if ((dma_status & PLX_DMA_DONE_BIT) == 0)
+ return 0;
+
+ transfer_address =
+ readl(devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
+ if (transfer_address != devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (cmd->stop_src == TRIG_NONE)
+ return 1;
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int dma_desc_bits;
+
+ dma_desc_bits =
+ readl(devpriv->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
+ dma_desc_bits &= ~PLX_END_OF_CHAIN_BIT;
+ load_first_dma_descriptor(dev, 0, dma_desc_bits);
+
+ dma_start_sync(dev, 0);
+}
+
+static unsigned int load_ao_dma_buffer(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_bytes, buffer_index, prev_buffer_index;
+ unsigned int next_bits;
+
+ buffer_index = devpriv->ao_dma_index;
+ prev_buffer_index = prev_ao_dma_index(dev);
+
+ num_bytes = comedi_buf_read_n_available(dev->write_subdev);
+ if (num_bytes > DMA_BUFFER_SIZE)
+ num_bytes = DMA_BUFFER_SIZE;
+ if (cmd->stop_src == TRIG_COUNT && num_bytes > devpriv->ao_count)
+ num_bytes = devpriv->ao_count;
+ num_bytes -= num_bytes % bytes_in_sample;
+
+ if (num_bytes == 0)
+ return 0;
+
+ num_bytes = cfc_read_array_from_buffer(dev->write_subdev,
+ devpriv->
+ ao_buffer[buffer_index],
+ num_bytes);
+ devpriv->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(devpriv->ao_dma_desc[buffer_index].next);
+ next_bits |= PLX_END_OF_CHAIN_BIT;
+ devpriv->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(devpriv->ao_dma_desc[prev_buffer_index].next);
+ next_bits &= ~PLX_END_OF_CHAIN_BIT;
+ devpriv->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits);
+
+ devpriv->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT;
+ devpriv->ao_count -= num_bytes;
+
+ return num_bytes;
+}
+
+static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_bytes;
+ unsigned int next_transfer_addr;
+ void __iomem *pci_addr_reg =
+ devpriv->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
+ unsigned int buffer_index;
+
+ do {
+ buffer_index = devpriv->ao_dma_index;
+ /* don't overwrite data that hasn't been transferred yet */
+ next_transfer_addr = readl(pci_addr_reg);
+ if (next_transfer_addr >=
+ devpriv->ao_buffer_bus_addr[buffer_index] &&
+ next_transfer_addr <
+ devpriv->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 void handle_ao_interrupt(struct comedi_device *dev,
+ unsigned short status, unsigned int plx_status)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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(devpriv->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,
+ devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ else
+ writeb(PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_iobase + PLX_DMA0_CS_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ 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);
+ }
+ } 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;
+ }
+ cfc_handle_events(dev, s);
+}
+
+static irqreturn_t handle_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned short status;
+ uint32_t plx_status;
+ uint32_t plx_bits;
+
+ plx_status = readl(devpriv->plx9080_iobase + PLX_INTRCS_REG);
+ status = readw(devpriv->main_iobase + HW_STATUS_REG);
+
+ /* an interrupt before all the postconfig stuff gets done could
+ * cause a NULL dereference if we continue through the
+ * interrupt handler */
+ if (!dev->attached)
+ 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(devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+ writel(plx_bits, devpriv->plx9080_iobase + PLX_DBR_OUT_REG);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ if (devpriv->ai_cmd_running == 0) {
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ return 0;
+ }
+ devpriv->ai_cmd_running = 0;
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ disable_ai_pacing(dev);
+
+ abort_dma(dev, 1);
+
+ return 0;
+}
+
+static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+
+ /* do some initializing */
+ writew(0, devpriv->main_iobase + DAC_CONTROL0_REG);
+
+ /* set range */
+ set_dac_range_bits(dev, &devpriv->dac_control1_bits, chan, range);
+ writew(devpriv->dac_control1_bits,
+ devpriv->main_iobase + DAC_CONTROL1_REG);
+
+ /* write to channel */
+ if (thisboard->layout == LAYOUT_4020) {
+ writew(data[0] & 0xff,
+ devpriv->main_iobase + dac_lsb_4020_reg(chan));
+ writew((data[0] >> 8) & 0xf,
+ devpriv->main_iobase + dac_msb_4020_reg(chan));
+ } else {
+ writew(data[0], devpriv->main_iobase + dac_convert_reg(chan));
+ }
+
+ /* remember output value */
+ devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+static void set_dac_control0_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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, devpriv->main_iobase + DAC_CONTROL0_REG);
+}
+
+static void set_dac_control1_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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, &devpriv->dac_control1_bits, channel,
+ range);
+ }
+ devpriv->dac_control1_bits |= DAC_SW_GATE_BIT;
+ writew(devpriv->dac_control1_bits,
+ devpriv->main_iobase + DAC_CONTROL1_REG);
+}
+
+static void set_dac_select_reg(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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, devpriv->main_iobase + DAC_SELECT_REG);
+}
+
+static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags)
+{
+ return get_divisor(ns, flags) - 2;
+}
+
+static void set_dac_interval_regs(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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,
+ devpriv->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG);
+ writew((divisor >> 16) & 0xff,
+ devpriv->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG);
+}
+
+static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int num_bytes;
+ int i;
+
+ /* clear queue pointer too, since external queue has
+ * weird interactions with ao fifo */
+ writew(0, devpriv->main_iobase + ADC_QUEUE_CLEAR_REG);
+ writew(0, devpriv->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 > devpriv->ao_count)
+ num_bytes = devpriv->ao_count * bytes_in_sample;
+ num_bytes = cfc_read_array_from_buffer(dev->write_subdev,
+ devpriv->ao_bounce_buffer,
+ num_bytes);
+ for (i = 0; i < num_bytes / bytes_in_sample; i++) {
+ writew(devpriv->ao_bounce_buffer[i],
+ devpriv->main_iobase + DAC_FIFO_REG);
+ }
+ devpriv->ao_count -= num_bytes / bytes_in_sample;
+ if (cmd->stop_src == TRIG_COUNT && devpriv->ao_count == 0)
+ return 0;
+ num_bytes = load_ao_dma_buffer(dev, cmd);
+ if (num_bytes == 0)
+ return -1;
+ load_ao_dma(dev, cmd);
+
+ dma_start_sync(dev, 0);
+
+ return 0;
+}
+
+static inline int external_ai_queue_in_use(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+
+ if (s->busy)
+ return 0;
+ if (thisboard->layout == LAYOUT_4020)
+ return 0;
+ else if (use_internal_queue_6xxx(cmd))
+ return 0;
+ return 1;
+}
+
+static int ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int retval;
+
+ if (trig_num != cmd->start_arg)
+ 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, devpriv->main_iobase + DAC_START_REG);
+
+ s->async->inttrig = NULL;
+
+ return 0;
+}
+
+static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (external_ai_queue_in_use(dev, s, cmd)) {
+ warn_external_queue(dev);
+ return -EBUSY;
+ }
+ /* disable analog output system during setup */
+ writew(0x0, devpriv->main_iobase + DAC_CONTROL0_REG);
+
+ devpriv->ao_dma_index = 0;
+ devpriv->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, devpriv->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 cb_pcidas64_ao_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "chanlist must use consecutive channels\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ int err = 0;
+ unsigned int tmp_arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
+ err |= -EINVAL;
+ if (cmd->stop_src != TRIG_COUNT &&
+ cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ thisboard->ao_scan_speed);
+ if (get_ao_divisor(cmd->scan_begin_arg, cmd->flags) >
+ max_counter_value) {
+ cmd->scan_begin_arg = (max_counter_value + 2) *
+ TIMER_BASE;
+ err |= -EINVAL;
+ }
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ 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;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= cb_pcidas64_ao_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ writew(0x0, devpriv->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);
+ 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int bits;
+
+ bits = readb(devpriv->dio_counter_iobase + DI_REG);
+ bits &= 0xf;
+ data[1] = bits;
+ data[0] = 0;
+
+ return insn->n;
+}
+
+static int do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ writeb(s->state, devpriv->dio_counter_iobase + DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int dio_60xx_config_insn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writeb(s->io_bits,
+ devpriv->dio_counter_iobase + DIO_DIRECTION_60XX_REG);
+
+ return insn->n;
+}
+
+static int dio_60xx_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcidas64_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data)) {
+ writeb(s->state,
+ devpriv->dio_counter_iobase + DIO_DATA_60XX_REG);
+ }
+
+ data[1] = readb(devpriv->dio_counter_iobase + DIO_DATA_60XX_REG);
+
+ return insn->n;
+}
+
+/* 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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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, devpriv->main_iobase + CALIBRATION_REG);
+ register_bits |= SERIAL_CLOCK_BIT;
+ udelay(caldac_8800_udelay);
+ writew(register_bits, devpriv->main_iobase + CALIBRATION_REG);
+ }
+ udelay(caldac_8800_udelay);
+ writew(SELECT_8800_BIT, devpriv->main_iobase + CALIBRATION_REG);
+ udelay(caldac_8800_udelay);
+ writew(0, devpriv->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;
+}
+
+static void caldac_write(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+
+ devpriv->caldac_state[channel] = value;
+
+ switch (thisboard->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+
+ /* return immediately if setting hasn't changed, since
+ * programming these things is slow */
+ if (devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+
+ data[0] = devpriv->caldac_state[channel];
+
+ return 1;
+}
+
+static void ad8402_write(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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;
+
+ devpriv->ad8402_state[channel] = value;
+
+ register_bits = SELECT_8402_64XX_BIT;
+ udelay(ad8402_udelay);
+ writew(register_bits, devpriv->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, devpriv->main_iobase + CALIBRATION_REG);
+ udelay(ad8402_udelay);
+ writew(register_bits | SERIAL_CLOCK_BIT,
+ devpriv->main_iobase + CALIBRATION_REG);
+ }
+
+ udelay(ad8402_udelay);
+ writew(0, devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ int channel = CR_CHAN(insn->chanspec);
+
+ /* return immediately if setting hasn't changed, since
+ * programming these things is slow */
+ if (devpriv->ad8402_state[channel] == data[0])
+ return 1;
+
+ devpriv->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)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+
+ data[0] = devpriv->ad8402_state[channel];
+
+ return 1;
+}
+
+static uint16_t read_eeprom(struct comedi_device *dev, uint8_t address)
+{
+ struct pcidas64_private *devpriv = dev->private;
+ 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 =
+ devpriv->plx9080_iobase + PLX_CONTROL_REG;
+ uint16_t value;
+ static const int value_length = 16;
+ static const int eeprom_udelay = 1;
+
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK & ~CTL_EE_CS;
+ /* make sure we don't send anything to the i2c bus on 4020 */
+ devpriv->plx_control_bits |= CTL_USERO;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ /* activate serial eeprom */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits |= CTL_EE_CS;
+ writel(devpriv->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)
+ devpriv->plx_control_bits |= CTL_EE_W;
+ else
+ devpriv->plx_control_bits &= ~CTL_EE_W;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ /* clock in bit */
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits |= CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK;
+ writel(devpriv->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);
+ devpriv->plx_control_bits |= CTL_EE_CLK;
+ writel(devpriv->plx_control_bits, plx_control_addr);
+ udelay(eeprom_udelay);
+ devpriv->plx_control_bits &= ~CTL_EE_CLK;
+ writel(devpriv->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);
+ devpriv->plx_control_bits &= ~CTL_EE_CS;
+ writel(devpriv->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;
+}
+
+/* Allocate and initialize the subdevice structures.
+ */
+static int setup_subdevices(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ void __iomem *dio_8255_iobase;
+ int i;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 10);
+ if (ret)
+ return ret;
+
+ 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 (thisboard->layout == LAYOUT_60XX)
+ s->subdev_flags |= SDF_COMMON | SDF_DIFF;
+ else if (thisboard->layout == LAYOUT_64XX)
+ s->subdev_flags |= SDF_DIFF;
+ /* XXX Number of inputs in differential mode is ignored */
+ s->n_chan = thisboard->ai_se_chans;
+ s->len_chanlist = 0x2000;
+ s->maxdata = (1 << thisboard->ai_bits) - 1;
+ s->range_table = thisboard->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 (thisboard->layout == LAYOUT_4020) {
+ uint8_t data;
+ /* set adc to read from inputs
+ * (not internal calibration sources) */
+ devpriv->i2c_cal_range_bits = adc_src_4020_bits(4);
+ /* set channels to +-5 volt input ranges */
+ for (i = 0; i < s->n_chan; i++)
+ devpriv->i2c_cal_range_bits |= attenuate_bit(i);
+ data = devpriv->i2c_cal_range_bits;
+ i2c_write(dev, RANGE_CAL_I2C_ADDR, &data, sizeof(data));
+ }
+
+ /* 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 | SDF_CMD_WRITE;
+ s->n_chan = thisboard->ao_nchan;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->range_table = thisboard->ao_range_table;
+ s->insn_read = ao_readback_insn;
+ s->insn_write = ao_winsn;
+ if (ao_cmd_is_supported(thisboard)) {
+ dev->write_subdev = s;
+ s->do_cmdtest = ao_cmdtest;
+ s->do_cmd = ao_cmd;
+ s->len_chanlist = thisboard->ao_nchan;
+ s->cancel = ao_cancel;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* digital input */
+ s = &dev->subdevices[2];
+ if (thisboard->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 (thisboard->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 (thisboard->has_8255) {
+ if (thisboard->layout == LAYOUT_4020) {
+ dio_8255_iobase = devpriv->main_iobase + I8255_4020_REG;
+ ret = subdev_8255_init(dev, s, dio_callback_4020,
+ (unsigned long)dio_8255_iobase);
+ } else {
+ dio_8255_iobase =
+ devpriv->dio_counter_iobase + DIO_8255_OFFSET;
+ ret = subdev_8255_init(dev, s, dio_callback,
+ (unsigned long)dio_8255_iobase);
+ }
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* 8 channel dio for 60xx */
+ s = &dev->subdevices[5];
+ if (thisboard->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 (thisboard->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 (thisboard->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(devpriv->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 int auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct pcidas64_board *thisboard = NULL;
+ struct pcidas64_private *devpriv;
+ uint32_t local_range, local_decode;
+ int retval;
+
+ if (context < ARRAY_SIZE(pcidas64_boards))
+ thisboard = &pcidas64_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ retval = comedi_pci_enable(dev);
+ if (retval)
+ return retval;
+ pci_set_master(pcidev);
+
+ /* Initialize dev->board_name */
+ dev->board_name = thisboard->name;
+
+ devpriv->main_phys_iobase = pci_resource_start(pcidev, 2);
+ devpriv->dio_counter_phys_iobase = pci_resource_start(pcidev, 3);
+
+ devpriv->plx9080_iobase = pci_ioremap_bar(pcidev, 0);
+ devpriv->main_iobase = pci_ioremap_bar(pcidev, 2);
+ devpriv->dio_counter_iobase = pci_ioremap_bar(pcidev, 3);
+
+ if (!devpriv->plx9080_iobase || !devpriv->main_iobase
+ || !devpriv->dio_counter_iobase) {
+ dev_warn(dev->class_dev, "failed to remap io memory\n");
+ return -ENOMEM;
+ }
+
+ /* figure out what local addresses are */
+ local_range = readl(devpriv->plx9080_iobase + PLX_LAS0RNG_REG) &
+ LRNG_MEM_MASK;
+ local_decode = readl(devpriv->plx9080_iobase + PLX_LAS0MAP_REG) &
+ local_range & LMAP_MEM_MASK;
+ devpriv->local0_iobase = ((uint32_t)devpriv->main_phys_iobase &
+ ~local_range) | local_decode;
+ local_range = readl(devpriv->plx9080_iobase + PLX_LAS1RNG_REG) &
+ LRNG_MEM_MASK;
+ local_decode = readl(devpriv->plx9080_iobase + PLX_LAS1MAP_REG) &
+ local_range & LMAP_MEM_MASK;
+ devpriv->local1_iobase = ((uint32_t)devpriv->dio_counter_phys_iobase &
+ ~local_range) | local_decode;
+
+ retval = alloc_and_init_dma_members(dev);
+ if (retval < 0)
+ return retval;
+
+ devpriv->hw_revision =
+ hw_revision(dev, readw(devpriv->main_iobase + HW_STATUS_REG));
+ dev_dbg(dev->class_dev, "stc hardware revision %i\n",
+ devpriv->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->class_dev, "unable to allocate irq %u\n",
+ pcidev->irq);
+ return -EINVAL;
+ }
+ dev->irq = pcidev->irq;
+ dev_dbg(dev->class_dev, "irq %u\n", dev->irq);
+
+ retval = setup_subdevices(dev);
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+static void detach(struct comedi_device *dev)
+{
+ const struct pcidas64_board *thisboard = comedi_board(dev);
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct pcidas64_private *devpriv = dev->private;
+ unsigned int i;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (pcidev) {
+ if (devpriv->plx9080_iobase) {
+ disable_plx_interrupts(dev);
+ iounmap(devpriv->plx9080_iobase);
+ }
+ if (devpriv->main_iobase)
+ iounmap(devpriv->main_iobase);
+ if (devpriv->dio_counter_iobase)
+ iounmap(devpriv->dio_counter_iobase);
+ /* free pci dma buffers */
+ for (i = 0; i < ai_dma_ring_count(thisboard); i++) {
+ if (devpriv->ai_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->ai_buffer[i],
+ devpriv->ai_buffer_bus_addr[i]);
+ }
+ for (i = 0; i < AO_DMA_RING_COUNT; i++) {
+ if (devpriv->ao_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->ao_buffer[i],
+ devpriv->ao_buffer_bus_addr[i]);
+ }
+ /* free dma descriptors */
+ if (devpriv->ai_dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ ai_dma_ring_count(thisboard),
+ devpriv->ai_dma_desc,
+ devpriv->ai_dma_desc_bus_addr);
+ if (devpriv->ao_dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ AO_DMA_RING_COUNT,
+ devpriv->ao_dma_desc,
+ devpriv->ao_dma_desc_bus_addr);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver cb_pcidas64_driver = {
+ .driver_name = "cb_pcidas64",
+ .module = THIS_MODULE,
+ .auto_attach = auto_attach,
+ .detach = detach,
+};
+
+static int cb_pcidas64_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidas64_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidas64_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x001d), BOARD_PCIDAS6402_16 },
+ { PCI_VDEVICE(CB, 0x001e), BOARD_PCIDAS6402_12 },
+ { PCI_VDEVICE(CB, 0x0035), BOARD_PCIDAS64_M1_16 },
+ { PCI_VDEVICE(CB, 0x0036), BOARD_PCIDAS64_M2_16 },
+ { PCI_VDEVICE(CB, 0x0037), BOARD_PCIDAS64_M3_16 },
+ { PCI_VDEVICE(CB, 0x0052), BOARD_PCIDAS4020_12 },
+ { PCI_VDEVICE(CB, 0x005d), BOARD_PCIDAS6023 },
+ { PCI_VDEVICE(CB, 0x005e), BOARD_PCIDAS6025 },
+ { PCI_VDEVICE(CB, 0x005f), BOARD_PCIDAS6030 },
+ { PCI_VDEVICE(CB, 0x0060), BOARD_PCIDAS6031 },
+ { PCI_VDEVICE(CB, 0x0061), BOARD_PCIDAS6032 },
+ { PCI_VDEVICE(CB, 0x0062), BOARD_PCIDAS6033 },
+ { PCI_VDEVICE(CB, 0x0063), BOARD_PCIDAS6034 },
+ { PCI_VDEVICE(CB, 0x0064), BOARD_PCIDAS6035 },
+ { PCI_VDEVICE(CB, 0x0065), BOARD_PCIDAS6040 },
+ { PCI_VDEVICE(CB, 0x0066), BOARD_PCIDAS6052 },
+ { PCI_VDEVICE(CB, 0x0067), BOARD_PCIDAS6070 },
+ { PCI_VDEVICE(CB, 0x0068), BOARD_PCIDAS6071 },
+ { PCI_VDEVICE(CB, 0x006f), BOARD_PCIDAS6036 },
+ { PCI_VDEVICE(CB, 0x0078), BOARD_PCIDAS6013 },
+ { PCI_VDEVICE(CB, 0x0079), BOARD_PCIDAS6014 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidas64_pci_table);
+
+static struct pci_driver cb_pcidas64_pci_driver = {
+ .name = "cb_pcidas64",
+ .id_table = cb_pcidas64_pci_table,
+ .probe = cb_pcidas64_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidas64_driver, cb_pcidas64_pci_driver);
+
+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 00000000000..901dc5d1bb7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcidda.c
@@ -0,0 +1,429 @@
+/*
+ * comedi/drivers/cb_pcidda.c
+ * Driver for the ComputerBoards / MeasurementComputing PCI-DDA series.
+ *
+ * Copyright (C) 2001 Ivan Martinez <ivanmr@altavista.com>
+ * Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: cb_pcidda
+ * Description: MeasurementComputing PCI-DDA series
+ * Devices: (Measurement Computing) PCI-DDA08/12 [pci-dda08/12]
+ * (Measurement Computing) PCI-DDA04/12 [pci-dda04/12]
+ * (Measurement Computing) PCI-DDA02/12 [pci-dda02/12]
+ * (Measurement Computing) PCI-DDA08/16 [pci-dda08/16]
+ * (Measurement Computing) PCI-DDA04/16 [pci-dda04/16]
+ * (Measurement Computing) PCI-DDA02/16 [pci-dda02/16]
+ * Author: Ivan Martinez <ivanmr@altavista.com>
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ *
+ * Only simple analog output writing is supported.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8255.h"
+
+#define EEPROM_SIZE 128 /* number of entries in eeprom */
+/* maximum number of ao channels for supported boards */
+#define MAX_AO_CHANNELS 8
+
+/* Digital I/O registers */
+#define CB_DDA_DIO0_8255_BASE 0x00
+#define CB_DDA_DIO1_8255_BASE 0x04
+
+/* DAC registers */
+#define CB_DDA_DA_CTRL_REG 0x00 /* D/A Control Register */
+#define CB_DDA_DA_CTRL_SU (1 << 0) /* Simultaneous update */
+#define CB_DDA_DA_CTRL_EN (1 << 1) /* Enable specified DAC */
+#define CB_DDA_DA_CTRL_DAC(x) ((x) << 2) /* Specify DAC channel */
+#define CB_DDA_DA_CTRL_RANGE2V5 (0 << 6) /* 2.5V range */
+#define CB_DDA_DA_CTRL_RANGE5V (2 << 6) /* 5V range */
+#define CB_DDA_DA_CTRL_RANGE10V (3 << 6) /* 10V range */
+#define CB_DDA_DA_CTRL_UNIP (1 << 8) /* Unipolar range */
+
+#define DACALIBRATION1 4 /* D/A CALIBRATION REGISTER 1 */
+/* write bits */
+/* serial data input for eeprom, caldacs, reference dac */
+#define SERIAL_IN_BIT 0x1
+#define CAL_CHANNEL_MASK (0x7 << 1)
+#define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK)
+/* read bits */
+#define CAL_COUNTER_MASK 0x1f
+/* calibration counter overflow status bit */
+#define CAL_COUNTER_OVERFLOW_BIT 0x20
+/* analog output is less than reference dac voltage */
+#define AO_BELOW_REF_BIT 0x40
+#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 */
+/* don't send serial data to MAX542 reference dac */
+#define DESELECT_REF_DAC_BIT 0x2
+/* don't send serial data to caldac n */
+#define DESELECT_CALDAC_BIT(n) (0x4 << (n))
+/* manual says to set this bit with no explanation */
+#define DUMMY_BIT 0x40
+
+#define CB_DDA_DA_DATA_REG(x) (0x08 + ((x) * 2))
+
+/* Offsets for the caldac channels */
+#define CB_DDA_CALDAC_FINE_GAIN 0
+#define CB_DDA_CALDAC_COURSE_GAIN 1
+#define CB_DDA_CALDAC_COURSE_OFFSET 2
+#define CB_DDA_CALDAC_FINE_OFFSET 3
+
+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)
+ }
+};
+
+enum cb_pcidda_boardid {
+ BOARD_DDA02_12,
+ BOARD_DDA04_12,
+ BOARD_DDA08_12,
+ BOARD_DDA02_16,
+ BOARD_DDA04_16,
+ BOARD_DDA08_16,
+};
+
+struct cb_pcidda_board {
+ const char *name;
+ int ao_chans;
+ int ao_bits;
+};
+
+static const struct cb_pcidda_board cb_pcidda_boards[] = {
+ [BOARD_DDA02_12] = {
+ .name = "pci-dda02/12",
+ .ao_chans = 2,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA04_12] = {
+ .name = "pci-dda04/12",
+ .ao_chans = 4,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA08_12] = {
+ .name = "pci-dda08/12",
+ .ao_chans = 8,
+ .ao_bits = 12,
+ },
+ [BOARD_DDA02_16] = {
+ .name = "pci-dda02/16",
+ .ao_chans = 2,
+ .ao_bits = 16,
+ },
+ [BOARD_DDA04_16] = {
+ .name = "pci-dda04/16",
+ .ao_chans = 4,
+ .ao_bits = 16,
+ },
+ [BOARD_DDA08_16] = {
+ .name = "pci-dda08/16",
+ .ao_chans = 8,
+ .ao_bits = 16,
+ },
+};
+
+struct cb_pcidda_private {
+ /* bits last written to da calibration register 1 */
+ unsigned int dac_cal1_bits;
+ /* current range settings for output channels */
+ unsigned int ao_range[MAX_AO_CHANNELS];
+ u16 eeprom_data[EEPROM_SIZE]; /* software copy of board's eeprom */
+};
+
+/* 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(dev->iobase + 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)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ 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, dev->iobase + 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;
+ /* one caldac for every two dac channels */
+ const int max_num_caldacs = 4;
+ /* bits to send to tell eeprom we want to read */
+ const int read_instruction = 0x6;
+ 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, dev->iobase + 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, dev->iobase + 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;
+ /* caldacs use 3 bit channel specification */
+ const int num_channel_bits = 3;
+ const int num_caldac_bits = 8; /* 8 bit calibration dacs */
+ /* one caldac for every two dac channels */
+ const int max_num_caldacs = 4;
+
+ /* 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, dev->iobase + DACALIBRATION2);
+ /* deactivate caldac */
+ cal2_bits |= DESELECT_CALDAC_BIT(caldac);
+ outw_p(cal2_bits, dev->iobase + DACALIBRATION2);
+}
+
+/* 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)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int caldac = channel / 2; /* two caldacs per channel */
+ unsigned int chan = 4 * (channel % 2); /* caldac channel base */
+ unsigned int index = 2 * range + 12 * channel;
+ unsigned int offset;
+ unsigned int gain;
+
+ /* save range so we can tell when we need to readjust calibration */
+ devpriv->ao_range[channel] = range;
+
+ /* get values from eeprom data */
+ offset = devpriv->eeprom_data[0x7 + index];
+ gain = devpriv->eeprom_data[0x8 + index];
+
+ /* set caldacs */
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_OFFSET,
+ (offset >> 8) & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_OFFSET,
+ offset & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_COURSE_GAIN,
+ (gain >> 8) & 0xff);
+ cb_pcidda_write_caldac(dev, caldac, chan + CB_DDA_CALDAC_FINE_GAIN,
+ gain & 0xff);
+}
+
+static int cb_pcidda_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcidda_private *devpriv = dev->private;
+ unsigned int channel = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int ctrl;
+
+ if (range != devpriv->ao_range[channel])
+ cb_pcidda_calibrate(dev, channel, range);
+
+ ctrl = CB_DDA_DA_CTRL_EN | CB_DDA_DA_CTRL_DAC(channel);
+
+ switch (range) {
+ case 0:
+ case 3:
+ ctrl |= CB_DDA_DA_CTRL_RANGE10V;
+ break;
+ case 1:
+ case 4:
+ ctrl |= CB_DDA_DA_CTRL_RANGE5V;
+ break;
+ case 2:
+ case 5:
+ ctrl |= CB_DDA_DA_CTRL_RANGE2V5;
+ break;
+ }
+
+ if (range > 2)
+ ctrl |= CB_DDA_DA_CTRL_UNIP;
+
+ outw(ctrl, dev->iobase + CB_DDA_DA_CTRL_REG);
+
+ outw(data[0], dev->iobase + CB_DDA_DA_DATA_REG(channel));
+
+ return insn->n;
+}
+
+static int cb_pcidda_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct cb_pcidda_board *thisboard = NULL;
+ struct cb_pcidda_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned long iobase_8255;
+ int i;
+ int ret;
+
+ if (context < ARRAY_SIZE(cb_pcidda_boards))
+ thisboard = &cb_pcidda_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 3);
+ iobase_8255 = pci_resource_start(pcidev, 2);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ 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 = &cb_pcidda_ranges;
+ s->insn_write = cb_pcidda_ao_insn_write;
+
+ /* two 8255 digital io subdevices */
+ for (i = 0; i < 2; i++) {
+ s = &dev->subdevices[1 + i];
+ ret = subdev_8255_init(dev, s, NULL, iobase_8255 + (i * 4));
+ if (ret)
+ return ret;
+ }
+
+ /* Read the caldac eeprom data */
+ for (i = 0; i < EEPROM_SIZE; i++)
+ devpriv->eeprom_data[i] = cb_pcidda_read_eeprom(dev, i);
+
+ /* set calibrations dacs */
+ for (i = 0; i < thisboard->ao_chans; i++)
+ cb_pcidda_calibrate(dev, i, devpriv->ao_range[i]);
+
+ return 0;
+}
+
+static struct comedi_driver cb_pcidda_driver = {
+ .driver_name = "cb_pcidda",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcidda_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int cb_pcidda_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcidda_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcidda_pci_table[] = {
+ { PCI_VDEVICE(CB, 0x0020), BOARD_DDA02_12 },
+ { PCI_VDEVICE(CB, 0x0021), BOARD_DDA04_12 },
+ { PCI_VDEVICE(CB, 0x0022), BOARD_DDA08_12 },
+ { PCI_VDEVICE(CB, 0x0023), BOARD_DDA02_16 },
+ { PCI_VDEVICE(CB, 0x0024), BOARD_DDA04_16 },
+ { PCI_VDEVICE(CB, 0x0025), BOARD_DDA08_16 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table);
+
+static struct pci_driver cb_pcidda_pci_driver = {
+ .name = "cb_pcidda",
+ .id_table = cb_pcidda_pci_table,
+ .probe = cb_pcidda_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcidda_driver, cb_pcidda_pci_driver);
+
+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 00000000000..50e522e6e69
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcimdas.c
@@ -0,0 +1,302 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "plx9052.h"
+#include "8255.h"
+
+/* Registers for the PCIM-DAS1602/16 */
+
+/* sizes of io regions (bytes) */
+#define BADR3_SIZE 16
+
+/* 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
+
+/*
+ * 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 {
+ /* base addresses */
+ unsigned long BADR3;
+
+ /* Used for AO readback */
+ unsigned int ao_readback[2];
+};
+
+static int cb_pcimdas_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = inb(devpriv->BADR3 + 2);
+ if ((status & 0x80) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int cb_pcimdas_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ int n;
+ unsigned int d;
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned short chanlims;
+ int maxchans;
+ int ret;
+
+ /* only support sw initiated reads from a single channel */
+
+ /* check channel number */
+ if ((inb(devpriv->BADR3 + 2) & 0x20) == 0) /* differential mode */
+ maxchans = s->n_chan / 2;
+ else
+ maxchans = s->n_chan;
+
+ 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);
+ }
+
+ /* set bursting off, conversions on */
+ outb(0x01, devpriv->BADR3 + 6);
+
+ /* set range to 10V. UP/BP is controlled by a switch on the board */
+ outb(0x00, devpriv->BADR3 + 7);
+
+ /*
+ * 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, dev->iobase + 0);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, cb_pcimdas_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ data[n] = inw(dev->iobase + 0);
+ }
+
+ /* 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)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ 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, dev->iobase + DAC0_OFFSET);
+ break;
+ case 1:
+ outw(data[i] & 0x0FFF, dev->iobase + 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)
+{
+ struct cb_pcimdas_private *devpriv = dev->private;
+ 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 cb_pcimdas_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct cb_pcimdas_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned long iobase_8255;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev->iobase = pci_resource_start(pcidev, 2);
+ devpriv->BADR3 = pci_resource_start(pcidev, 3);
+ iobase_8255 = pci_resource_start(pcidev, 4);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ 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 = 16;
+ s->maxdata = 0xffff;
+ 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 = 2;
+ s->maxdata = 0xfff;
+ /* ranges are hardware settable, but not software readable. */
+ s->range_table = &range_unknown;
+ s->insn_write = &cb_pcimdas_ao_winsn;
+ s->insn_read = &cb_pcimdas_ao_rinsn;
+
+ s = &dev->subdevices[2];
+ /* digital i/o subdevice */
+ ret = subdev_8255_init(dev, s, NULL, iobase_8255);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void cb_pcimdas_detach(struct comedi_device *dev)
+{
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver cb_pcimdas_driver = {
+ .driver_name = "cb_pcimdas",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcimdas_auto_attach,
+ .detach = cb_pcimdas_detach,
+};
+
+static int cb_pcimdas_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcimdas_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcimdas_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0056) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table);
+
+static struct pci_driver cb_pcimdas_pci_driver = {
+ .name = "cb_pcimdas",
+ .id_table = cb_pcimdas_pci_table,
+ .probe = cb_pcimdas_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcimdas_driver, cb_pcimdas_pci_driver);
+
+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 00000000000..4a2b200de01
--- /dev/null
+++ b/drivers/staging/comedi/drivers/cb_pcimdda.c
@@ -0,0 +1,225 @@
+/*
+ comedi/drivers/cb_pcimdda.c
+ Computer Boards PCIM-DDA06-16 Comedi driver
+ Author: Calin Culianu <calin@ajvar.org>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: cb_pcimdda
+Description: Measurement Computing PCIM-DDA06-16
+Devices: [Measurement Computing] PCIM-DDA06-16 (cb_pcimdda)
+Author: Calin Culianu <calin@ajvar.org>
+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: not applicable, uses PCI auto config
+*/
+
+/*
+ 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 <Timothy.Curry@rdec.redstone.army.mil>
+ for lending me a board so that I could write this driver.
+
+ -Calin Culianu <calin@ajvar.org>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+
+/* device ids of the cards we support -- currently only 1 card supported */
+#define PCI_ID_PCIM_DDA06_16 0x0053
+
+/*
+ * Register map, 8-bit access only
+ */
+#define PCIMDDA_DA_CHAN(x) (0x00 + (x) * 2)
+#define PCIMDDA_8255_BASE_REG 0x0c
+
+#define MAX_AO_READBACK_CHANNELS 6
+
+struct cb_pcimdda_private {
+ unsigned int ao_readback[MAX_AO_READBACK_CHANNELS];
+};
+
+static int cb_pcimdda_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdda_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan);
+ unsigned int val = 0;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /*
+ * Write the LSB then MSB.
+ *
+ * If the simultaneous xfer mode is selected by the
+ * jumper on the card, a read instruction is needed
+ * in order to initiate the simultaneous transfer.
+ * Otherwise, the DAC will be updated when the MSB
+ * is written.
+ */
+ outb(val & 0x00ff, offset);
+ outb((val >> 8) & 0x00ff, offset + 1);
+ }
+
+ /* Cache the last value for readback */
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int cb_pcimdda_ao_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct cb_pcimdda_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Initiate the simultaneous transfer */
+ inw(offset);
+
+ data[i] = devpriv->ao_readback[chan];
+ }
+
+ return insn->n;
+}
+
+static int cb_pcimdda_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct cb_pcimdda_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 3);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 6;
+ s->maxdata = 0xffff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = cb_pcimdda_ao_winsn;
+ s->insn_read = cb_pcimdda_ao_rinsn;
+
+ s = &dev->subdevices[1];
+ /* digital i/o subdevice */
+ ret = subdev_8255_init(dev, s, NULL,
+ dev->iobase + PCIMDDA_8255_BASE_REG);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver cb_pcimdda_driver = {
+ .driver_name = "cb_pcimdda",
+ .module = THIS_MODULE,
+ .auto_attach = cb_pcimdda_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int cb_pcimdda_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &cb_pcimdda_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id cb_pcimdda_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, PCI_ID_PCIM_DDA06_16) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, cb_pcimdda_pci_table);
+
+static struct pci_driver cb_pcimdda_driver_pci_driver = {
+ .name = "cb_pcimdda",
+ .id_table = cb_pcimdda_pci_table,
+ .probe = cb_pcimdda_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(cb_pcimdda_driver, cb_pcimdda_driver_pci_driver);
+
+MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>");
+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");
diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c
new file mode 100644
index 00000000000..8450c99af8b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_bond.c
@@ -0,0 +1,358 @@
+/*
+ * 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 <ds@schleef.org>
+ * Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * 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 <linux/module.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "../comedi.h"
+#include "../comedilib.h"
+#include "../comedidev.h"
+
+struct bonded_device {
+ struct comedi_device *dev;
+ unsigned minor;
+ unsigned subdev;
+ unsigned nchans;
+};
+
+struct comedi_bond_private {
+# define MAX_BOARD_NAME 256
+ char name[MAX_BOARD_NAME];
+ struct bonded_device **devs;
+ unsigned ndevs;
+ unsigned nchans;
+};
+
+static int bonding_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ unsigned int n_left, n_done, base_chan;
+ unsigned int write_mask, data_bits;
+ struct bonded_device **devs;
+
+ write_mask = data[0];
+ data_bits = data[1];
+ base_chan = CR_CHAN(insn->chanspec);
+ /* do a maximum of 32 channels, starting from base_chan. */
+ n_left = devpriv->nchans - base_chan;
+ if (n_left > 32)
+ n_left = 32;
+
+ n_done = 0;
+ devs = devpriv->devs;
+ do {
+ struct bonded_device *bdev = *devs++;
+
+ if (base_chan < bdev->nchans) {
+ /* base channel falls within bonded device */
+ unsigned int b_chans, b_mask, b_write_mask, b_data_bits;
+ int ret;
+
+ /*
+ * Get num channels to do for bonded device and set
+ * up mask and data bits for bonded device.
+ */
+ b_chans = bdev->nchans - base_chan;
+ if (b_chans > n_left)
+ b_chans = n_left;
+ b_mask = (1U << b_chans) - 1;
+ b_write_mask = (write_mask >> n_done) & b_mask;
+ b_data_bits = (data_bits >> n_done) & b_mask;
+ /* Read/Write the new digital lines. */
+ ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev,
+ b_write_mask, &b_data_bits,
+ base_chan);
+ if (ret < 0)
+ return ret;
+ /* Place read bits into data[1]. */
+ data[1] &= ~(b_mask << n_done);
+ data[1] |= (b_data_bits & b_mask) << n_done;
+ /*
+ * Set up for following bonded device (if still have
+ * channels to read/write).
+ */
+ base_chan = 0;
+ n_done += b_chans;
+ n_left -= b_chans;
+ } else {
+ /* Skip bonded devices before base channel. */
+ base_chan -= bdev->nchans;
+ }
+ } while (n_left);
+
+ return insn->n;
+}
+
+static int bonding_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ struct bonded_device *bdev;
+ struct bonded_device **devs;
+
+ /*
+ * Locate bonded subdevice and adjust channel.
+ */
+ devs = devpriv->devs;
+ for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++)
+ chan -= bdev->nchans;
+
+ /*
+ * 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
+ * configuration instruction INSN_CONFIG_DIO_OUTPUT,
+ * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY.
+ *
+ * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT,
+ * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT. This is deliberate ;)
+ */
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ case INSN_CONFIG_DIO_INPUT:
+ ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]);
+ break;
+ case INSN_CONFIG_DIO_QUERY:
+ ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan,
+ &data[1]);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ if (ret >= 0)
+ ret = insn->n;
+ return ret;
+}
+
+static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+ DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
+ int i;
+
+ 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[sizeof("/dev/comediXXXXXX")];
+ int minor = it->options[i];
+ struct comedi_device *d;
+ int sdev = -1, nchans;
+ struct bonded_device *bdev;
+ struct bonded_device **devs;
+
+ if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
+ dev_err(dev->class_dev,
+ "Minor %d is invalid!\n", minor);
+ return -EINVAL;
+ }
+ if (minor == dev->minor) {
+ dev_err(dev->class_dev,
+ "Cannot bond this driver to itself!\n");
+ return -EINVAL;
+ }
+ if (test_and_set_bit(minor, devs_opened)) {
+ dev_err(dev->class_dev,
+ "Minor %d specified more than once!\n", minor);
+ return -EINVAL;
+ }
+
+ snprintf(file, sizeof(file), "/dev/comedi%d", minor);
+ file[sizeof(file) - 1] = 0;
+
+ d = comedi_open(file);
+
+ if (!d) {
+ dev_err(dev->class_dev,
+ "Minor %u could not be opened\n", minor);
+ return -ENODEV;
+ }
+
+ /* 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) {
+ dev_err(dev->class_dev,
+ "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
+ nchans, minor, sdev);
+ return -EINVAL;
+ }
+ bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
+ if (!bdev)
+ return -ENOMEM;
+
+ bdev->dev = d;
+ bdev->minor = minor;
+ bdev->subdev = sdev;
+ bdev->nchans = nchans;
+ devpriv->nchans += nchans;
+
+ /*
+ * Now put bdev pointer at end of devpriv->devs array
+ * list..
+ */
+
+ /* ergh.. ugly.. we need to realloc :( */
+ devs = krealloc(devpriv->devs,
+ (devpriv->ndevs + 1) * sizeof(*devs),
+ GFP_KERNEL);
+ if (!devs) {
+ dev_err(dev->class_dev,
+ "Could not allocate memory. Out of memory?\n");
+ kfree(bdev);
+ return -ENOMEM;
+ }
+ devpriv->devs = devs;
+ devpriv->devs[devpriv->ndevs++] = bdev;
+ {
+ /* Append dev:subdev to devpriv->name */
+ char buf[20];
+ int left =
+ MAX_BOARD_NAME - strlen(devpriv->name) - 1;
+ snprintf(buf, sizeof(buf), "%u:%u ",
+ bdev->minor, bdev->subdev);
+ buf[sizeof(buf) - 1] = 0;
+ strncat(devpriv->name, buf, left);
+ }
+
+ }
+ }
+
+ if (!devpriv->nchans) {
+ dev_err(dev->class_dev, "No channels found!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bonding_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_bond_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /*
+ * Setup our bonding from config params.. sets up our private struct..
+ */
+ ret = do_dev_config(dev, it);
+ if (ret)
+ return ret;
+
+ dev->board_name = devpriv->name;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ 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;
+
+ dev_info(dev->class_dev,
+ "%s: %s attached, %u channels from %u devices\n",
+ dev->driver->driver_name, dev->board_name,
+ devpriv->nchans, devpriv->ndevs);
+
+ return 0;
+}
+
+static void bonding_detach(struct comedi_device *dev)
+{
+ struct comedi_bond_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->devs) {
+ DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
+
+ memset(&devs_closed, 0, sizeof(devs_closed));
+ while (devpriv->ndevs--) {
+ struct bonded_device *bdev;
+
+ bdev = devpriv->devs[devpriv->ndevs];
+ if (!bdev)
+ continue;
+ if (!test_and_set_bit(bdev->minor, devs_closed))
+ comedi_close(bdev->dev);
+ kfree(bdev);
+ }
+ kfree(devpriv->devs);
+ devpriv->devs = NULL;
+ }
+}
+
+static struct comedi_driver bonding_driver = {
+ .driver_name = "comedi_bond",
+ .module = THIS_MODULE,
+ .attach = bonding_attach,
+ .detach = bonding_detach,
+};
+module_comedi_driver(bonding_driver);
+
+MODULE_AUTHOR("Calin A. Culianu");
+MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_fc.c b/drivers/staging/comedi/drivers/comedi_fc.c
new file mode 100644
index 00000000000..c33c3e5680a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_fc.c
@@ -0,0 +1,132 @@
+/*
+ * 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 <fmhess@users.sourceforge.net>
+ * 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.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+unsigned int cfc_bytes_per_scan(struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int num_samples;
+ unsigned int bits_per_sample;
+
+ switch (s->type) {
+ case COMEDI_SUBD_DI:
+ case COMEDI_SUBD_DO:
+ case COMEDI_SUBD_DIO:
+ bits_per_sample = 8 * bytes_per_sample(s);
+ num_samples = (cmd->chanlist_len + bits_per_sample - 1) /
+ bits_per_sample;
+ break;
+ default:
+ num_samples = cmd->chanlist_len;
+ break;
+ }
+ return num_samples * bytes_per_sample(s);
+}
+EXPORT_SYMBOL_GPL(cfc_bytes_per_scan);
+
+void cfc_inc_scan_progress(struct comedi_subdevice *s, unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int scan_length = cfc_bytes_per_scan(s);
+
+ async->scan_progress += num_bytes;
+ if (async->scan_progress >= scan_length) {
+ async->scan_progress %= scan_length;
+ async->events |= COMEDI_CB_EOS;
+ }
+}
+EXPORT_SYMBOL_GPL(cfc_inc_scan_progress);
+
+/* Writes an array of data points to comedi's buffer */
+unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes)
+{
+ struct comedi_async *async = s->async;
+ unsigned int retval;
+
+ if (num_bytes == 0)
+ return 0;
+
+ retval = comedi_buf_write_alloc(s, num_bytes);
+ if (retval != num_bytes) {
+ dev_warn(s->device->class_dev, "buffer overrun\n");
+ async->events |= COMEDI_CB_OVERFLOW;
+ return 0;
+ }
+
+ comedi_buf_memcpy_to(s, 0, data, num_bytes);
+ comedi_buf_write_free(s, num_bytes);
+ cfc_inc_scan_progress(s, num_bytes);
+ async->events |= COMEDI_CB_BLOCK;
+
+ return num_bytes;
+}
+EXPORT_SYMBOL_GPL(cfc_write_array_to_buffer);
+
+unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *s,
+ void *data, unsigned int num_bytes)
+{
+ if (num_bytes == 0)
+ return 0;
+
+ num_bytes = comedi_buf_read_alloc(s, num_bytes);
+ comedi_buf_memcpy_from(s, 0, data, num_bytes);
+ comedi_buf_read_free(s, num_bytes);
+ cfc_inc_scan_progress(s, num_bytes);
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ return num_bytes;
+}
+EXPORT_SYMBOL_GPL(cfc_read_array_from_buffer);
+
+unsigned int cfc_handle_events(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int events = s->async->events;
+
+ if (events == 0)
+ return events;
+
+ if (events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW))
+ s->cancel(dev, s);
+
+ comedi_event(dev, s);
+
+ return events;
+}
+EXPORT_SYMBOL_GPL(cfc_handle_events);
+
+static int __init comedi_fc_init_module(void)
+{
+ return 0;
+}
+module_init(comedi_fc_init_module);
+
+static void __exit comedi_fc_cleanup_module(void)
+{
+}
+module_exit(comedi_fc_cleanup_module);
+
+MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>");
+MODULE_DESCRIPTION("Shared functions for Comedi low-level drivers");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_fc.h b/drivers/staging/comedi/drivers/comedi_fc.h
new file mode 100644
index 00000000000..541b9371d3d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_fc.h
@@ -0,0 +1,127 @@
+/*
+ * 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 <fmhess@users.sourceforge.net>
+ * 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.
+ */
+
+#ifndef _COMEDI_FC_H
+#define _COMEDI_FC_H
+
+#include "../comedidev.h"
+
+unsigned int cfc_bytes_per_scan(struct comedi_subdevice *);
+void cfc_inc_scan_progress(struct comedi_subdevice *, unsigned int num_bytes);
+
+/* Writes an array of data points to comedi's buffer */
+unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *,
+ void *data, unsigned int num_bytes);
+
+static inline unsigned int cfc_write_to_buffer(struct comedi_subdevice *s,
+ unsigned short data)
+{
+ return cfc_write_array_to_buffer(s, &data, sizeof(data));
+};
+
+static inline unsigned int cfc_write_long_to_buffer(struct comedi_subdevice *s,
+ unsigned int data)
+{
+ return cfc_write_array_to_buffer(s, &data, sizeof(data));
+};
+
+unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *,
+ void *data, unsigned int num_bytes);
+
+unsigned int cfc_handle_events(struct comedi_device *,
+ struct comedi_subdevice *);
+
+/**
+ * cfc_check_trigger_src() - trivially validate a comedi_cmd trigger source
+ * @src: pointer to the trigger source to validate
+ * @flags: bitmask of valid TRIG_* for the trigger
+ *
+ * This is used in "step 1" of the do_cmdtest functions of comedi drivers
+ * to vaildate the comedi_cmd triggers. The mask of the @src against the
+ * @flags allows the userspace comedilib to pass all the comedi_cmd
+ * triggers as TRIG_ANY and get back a bitmask of the valid trigger sources.
+ */
+static inline int cfc_check_trigger_src(unsigned int *src, unsigned int flags)
+{
+ unsigned int orig_src = *src;
+
+ *src = orig_src & flags;
+ if (*src == TRIG_INVALID || *src != orig_src)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * cfc_check_trigger_is_unique() - make sure a trigger source is unique
+ * @src: the trigger source to check
+ */
+static inline int cfc_check_trigger_is_unique(unsigned int src)
+{
+ /* this test is true if more than one _src bit is set */
+ if ((src & (src - 1)) != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * cfc_check_trigger_arg_is() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the value the argument should be
+ */
+static inline int cfc_check_trigger_arg_is(unsigned int *arg, unsigned int val)
+{
+ if (*arg != val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * cfc_check_trigger_arg_min() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the minimum value the argument should be
+ */
+static inline int cfc_check_trigger_arg_min(unsigned int *arg,
+ unsigned int val)
+{
+ if (*arg < val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * cfc_check_trigger_arg_max() - trivially validate a trigger argument
+ * @arg: pointer to the trigger arg to validate
+ * @val: the maximum value the argument should be
+ */
+static inline int cfc_check_trigger_arg_max(unsigned int *arg,
+ unsigned int val)
+{
+ if (*arg > val) {
+ *arg = val;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#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 00000000000..a4274869235
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_parport.c
@@ -0,0 +1,320 @@
+/*
+ * comedi_parport.c
+ * Comedi driver for standard parallel port
+ *
+ * For more information see:
+ * http://retired.beyondlogic.org/spp/parallel.htm
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998,2001 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * 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 (optional)
+ *
+ * Parallel Port Lines:
+ *
+ * pin subdev chan type name
+ * ----- ------ ---- ---- --------------
+ * 1 2 0 DO strobe
+ * 2 0 0 DIO data 0
+ * 3 0 1 DIO data 1
+ * 4 0 2 DIO data 2
+ * 5 0 3 DIO data 3
+ * 6 0 4 DIO data 4
+ * 7 0 5 DIO data 5
+ * 8 0 6 DIO data 6
+ * 9 0 7 DIO data 7
+ * 10 1 3 DI ack
+ * 11 1 4 DI busy
+ * 12 1 2 DI paper out
+ * 13 1 1 DI select in
+ * 14 2 1 DO auto LF
+ * 15 1 0 DI error
+ * 16 2 2 DO init
+ * 17 2 3 DO select printer
+ * 18-25 ground
+ *
+ * When an IRQ is configured 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 trigger, which can be used to wake up tasks.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/*
+ * Register map
+ */
+#define PARPORT_DATA_REG 0x00
+#define PARPORT_STATUS_REG 0x01
+#define PARPORT_CTRL_REG 0x02
+#define PARPORT_CTRL_IRQ_ENA (1 << 4)
+#define PARPORT_CTRL_BIDIR_ENA (1 << 5)
+
+static int parport_data_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + PARPORT_DATA_REG);
+
+ data[1] = inb(dev->iobase + PARPORT_DATA_REG);
+
+ return insn->n;
+}
+
+static int parport_data_reg_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int ctrl;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xff);
+ if (ret)
+ return ret;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ if (s->io_bits)
+ ctrl &= ~PARPORT_CTRL_BIDIR_ENA;
+ else
+ ctrl |= PARPORT_CTRL_BIDIR_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return insn->n;
+}
+
+static int parport_status_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PARPORT_STATUS_REG) >> 3;
+
+ return insn->n;
+}
+
+static int parport_ctrl_reg_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int ctrl;
+
+ if (comedi_dio_update_state(s, data)) {
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl &= (PARPORT_CTRL_IRQ_ENA | PARPORT_CTRL_BIDIR_ENA);
+ ctrl |= s->state;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int parport_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int parport_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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)
+{
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl |= PARPORT_CTRL_IRQ_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static int parport_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ ctrl &= ~PARPORT_CTRL_IRQ_ENA;
+ outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static irqreturn_t parport_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int ctrl;
+
+ ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
+ if (!(ctrl & PARPORT_CTRL_IRQ_ENA))
+ return IRQ_NONE;
+
+ comedi_buf_put(s, 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)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x03);
+ if (ret)
+ return ret;
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], parport_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, dev->irq ? 4 : 3);
+ if (ret)
+ return ret;
+
+ /* Digial I/O subdevice - Parallel port DATA register */
+ 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_data_reg_insn_bits;
+ s->insn_config = parport_data_reg_insn_config;
+
+ /* Digial Input subdevice - Parallel port STATUS register */
+ 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_status_reg_insn_bits;
+
+ /* Digial Output subdevice - Parallel port CONTROL register */
+ 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_ctrl_reg_insn_bits;
+
+ if (dev->irq) {
+ /* Digial Input subdevice - Interrupt support */
+ 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->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = parport_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = parport_intr_cmdtest;
+ s->do_cmd = parport_intr_cmd;
+ s->cancel = parport_intr_cancel;
+ }
+
+ outb(0, dev->iobase + PARPORT_DATA_REG);
+ outb(0, dev->iobase + PARPORT_CTRL_REG);
+
+ return 0;
+}
+
+static struct comedi_driver parport_driver = {
+ .driver_name = "comedi_parport",
+ .module = THIS_MODULE,
+ .attach = parport_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(parport_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi: Standard parallel port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c
new file mode 100644
index 00000000000..67a09aa6b72
--- /dev/null
+++ b/drivers/staging/comedi/drivers/comedi_test.c
@@ -0,0 +1,450 @@
+/*
+ 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 <Joachim.Wuttke@icn.siemens.de>
+ Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: comedi_test
+Description: generates fake waveforms
+Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
+ <fmhess@users.sourceforge.net>, 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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <asm/div64.h>
+
+#include "comedi_fc.h"
+#include <linux/timer.h>
+
+#define N_CHANS 8
+
+/* Data unique to this driver */
+struct waveform_private {
+ struct timer_list timer;
+ struct timeval last; /* time 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 (mod 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 int ao_loopbacks[N_CHANS];
+};
+
+/* 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)
+ }
+};
+
+static unsigned short fake_sawtooth(struct comedi_device *dev,
+ unsigned int range_index,
+ unsigned long current_time)
+{
+ struct waveform_private *devpriv = dev->private;
+ 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 unsigned short fake_squarewave(struct comedi_device *dev,
+ unsigned int range_index,
+ unsigned long current_time)
+{
+ struct waveform_private *devpriv = dev->private;
+ 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 unsigned 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 unsigned 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);
+}
+
+/*
+ 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 waveform_private *devpriv = dev->private;
+ 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;
+ bool stopping = false;
+
+ 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;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ unsigned int remaining = cmd->stop_arg - devpriv->ai_count;
+
+ if (num_scans >= remaining) {
+ /* about to finish */
+ num_scans = remaining;
+ stopping = true;
+ }
+ }
+
+ for (i = 0; i < num_scans; i++) {
+ for (j = 0; j < cmd->chanlist_len; j++) {
+ unsigned short sample;
+
+ sample = fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
+ CR_RANGE(cmd->chanlist[j]),
+ devpriv->usec_current +
+ i * devpriv->scan_period +
+ j * devpriv->convert_period);
+ cfc_write_to_buffer(dev->read_subdev, sample);
+ }
+ }
+
+ devpriv->ai_count += i;
+ devpriv->usec_current += elapsed_time;
+ devpriv->usec_current %= devpriv->usec_period;
+
+ if (stopping)
+ async->events |= COMEDI_CB_EOA;
+ else
+ mod_timer(&devpriv->timer, jiffies + 1);
+
+ comedi_event(dev, dev->read_subdev);
+}
+
+static int waveform_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW | TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_NOW)
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ nano_per_micro);
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ cmd->convert_arg * cmd->chanlist_len);
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ /* round to nearest microsec */
+ arg = nano_per_micro *
+ ((arg + (nano_per_micro / 2)) / nano_per_micro);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ /* round to nearest microsec */
+ arg = nano_per_micro *
+ ((arg + (nano_per_micro / 2)) / nano_per_micro);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int waveform_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct waveform_private *devpriv = dev->private;
+ 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->ai_count = 0;
+ devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
+
+ if (cmd->convert_src == TRIG_NOW)
+ devpriv->convert_period = 0;
+ else /* TRIG_TIMER */
+ devpriv->convert_period = cmd->convert_arg / nano_per_micro;
+
+ 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)
+{
+ struct waveform_private *devpriv = dev->private;
+
+ del_timer_sync(&devpriv->timer);
+ return 0;
+}
+
+static int waveform_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct waveform_private *devpriv = dev->private;
+ 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)
+{
+ struct waveform_private *devpriv = dev->private;
+ int i, chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++)
+ devpriv->ao_loopbacks[chan] = data[i];
+
+ return insn->n;
+}
+
+static int waveform_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct waveform_private *devpriv;
+ struct comedi_subdevice *s;
+ int amplitude = it->options[0];
+ int period = it->options[1];
+ int i;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ 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;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ 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 = N_CHANS;
+ s->maxdata = 0xffff;
+ 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 = N_CHANS;
+ s->maxdata = 0xffff;
+ s->range_table = &waveform_ai_ranges;
+ s->insn_write = waveform_ao_insn_write;
+
+ /* 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;
+
+ dev_info(dev->class_dev,
+ "%s: %i microvolt, %li microsecond waveform attached\n",
+ dev->board_name,
+ devpriv->uvolt_amplitude, devpriv->usec_period);
+
+ return 0;
+}
+
+static void waveform_detach(struct comedi_device *dev)
+{
+ struct waveform_private *devpriv = dev->private;
+
+ if (devpriv)
+ waveform_ai_cancel(dev, dev->read_subdev);
+}
+
+static struct comedi_driver waveform_driver = {
+ .driver_name = "comedi_test",
+ .module = THIS_MODULE,
+ .attach = waveform_attach,
+ .detach = waveform_detach,
+};
+module_comedi_driver(waveform_driver);
+
+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 00000000000..0a9c32e9db4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/contec_pci_dio.c
@@ -0,0 +1,128 @@
+/*
+ comedi/drivers/contec_pci_dio.c
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: contec_pci_dio
+Description: Contec PIO1616L digital I/O board
+Devices: [Contec] PIO1616L (contec_pci_dio)
+Author: Stefano Rivoir <s.rivoir@gts.it>
+Updated: Wed, 27 Jun 2007 13:00:06 +0100
+Status: works
+
+Configuration Options: not applicable, uses comedi PCI auto config
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#define PCI_DEVICE_ID_PIO1616L 0x8172
+
+/*
+ * Register map
+ */
+#define PIO1616L_DI_REG 0x00
+#define PIO1616L_DO_REG 0x02
+
+static int contec_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + PIO1616L_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int contec_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = inw(dev->iobase + PIO1616L_DI_REG);
+
+ return insn->n;
+}
+
+static int contec_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ 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;
+
+ return 0;
+}
+
+static struct comedi_driver contec_pci_dio_driver = {
+ .driver_name = "contec_pci_dio",
+ .module = THIS_MODULE,
+ .auto_attach = contec_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int contec_pci_dio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &contec_pci_dio_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id contec_pci_dio_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CONTEC, PCI_DEVICE_ID_PIO1616L) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, contec_pci_dio_pci_table);
+
+static struct pci_driver contec_pci_dio_pci_driver = {
+ .name = "contec_pci_dio",
+ .id_table = contec_pci_dio_pci_table,
+ .probe = contec_pci_dio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(contec_pci_dio_driver, contec_pci_dio_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/dac02.c b/drivers/staging/comedi/drivers/dac02.c
new file mode 100644
index 00000000000..df46e0a5bad
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dac02.c
@@ -0,0 +1,172 @@
+/*
+ * dac02.c
+ * Comedi driver for DAC02 compatible boards
+ * Copyright (C) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Based on the poc driver
+ * Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2001 David A. Schleef <ds@schleef.org>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: dac02
+ * Description: Comedi driver for DAC02 compatible boards
+ * Devices: (Keithley Metrabyte) DAC-02 [dac02]
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Tue, 11 Mar 2014 11:27:19 -0700
+ * Status: unknown
+ *
+ * Configuration options:
+ * [0] - I/O port base
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+/*
+ * The output range is selected by jumpering pins on the I/O connector.
+ *
+ * Range Chan # Jumper pins Output
+ * ------------- ------ ------------- -----------------
+ * 0 to 5V 0 21 to 22 24
+ * 1 15 to 16 18
+ * 0 to 10V 0 20 to 22 24
+ * 1 14 to 16 18
+ * +/-5V 0 21 to 22 23
+ * 1 15 to 16 17
+ * +/-10V 0 20 to 22 23
+ * 1 14 to 16 17
+ * 4 to 20mA 0 21 to 22 25
+ * 1 15 to 16 19
+ * AC reference 0 In on pin 22 24 (2-quadrant)
+ * In on pin 22 23 (4-quadrant)
+ * 1 In on pin 16 18 (2-quadrant)
+ * In on pin 16 17 (4-quadrant)
+ */
+static const struct comedi_lrange das02_ao_ranges = {
+ 6, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ RANGE_mA(4, 20),
+ RANGE_ext(0, 1)
+ }
+};
+
+struct dac02_private {
+ unsigned int ao_readback[2];
+};
+
+/*
+ * Register I/O map
+ */
+#define DAC02_AO_LSB(x) (0x00 + ((x) * 2))
+#define DAC02_AO_MSB(x) (0x01 + ((x) * 2))
+
+static int dac02_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dac02_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ devpriv->ao_readback[chan] = val;
+
+ /*
+ * Unipolar outputs are true binary encoding.
+ * Bipolar outputs are complementary offset binary
+ * (that is, 0 = +full scale, maxdata = -full scale).
+ */
+ if (comedi_range_is_bipolar(s, range))
+ val = s->maxdata - val;
+
+ /*
+ * DACs are double-buffered.
+ * Write LSB then MSB to latch output.
+ */
+ outb((val << 4) & 0xf0, dev->iobase + DAC02_AO_LSB(chan));
+ outb((val >> 4) & 0xff, dev->iobase + DAC02_AO_MSB(chan));
+ }
+
+ return insn->n;
+}
+
+static int dac02_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dac02_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int dac02_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct dac02_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x08);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &das02_ao_ranges;
+ s->insn_write = dac02_ao_insn_write;
+ s->insn_read = dac02_ao_insn_read;
+
+ return 0;
+}
+
+static struct comedi_driver dac02_driver = {
+ .driver_name = "dac02",
+ .module = THIS_MODULE,
+ .attach = dac02_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dac02_driver);
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi driver for DAC02 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/daqboard2000.c b/drivers/staging/comedi/drivers/daqboard2000.c
new file mode 100644
index 00000000000..a8f6036ad82
--- /dev/null
+++ b/drivers/staging/comedi/drivers/daqboard2000.c
@@ -0,0 +1,815 @@
+/*
+ comedi/drivers/daqboard2000.c
+ hardware driver for IOtech DAQboard/2000
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+Driver: daqboard2000
+Description: IOTech DAQBoard/2000
+Author: Anders Blomdell <anders.blomdell@control.lth.se>
+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 fimware, which is available from
+http://www.comedi.org in the comedi_nonfree_firmware tarball.
+
+Configuration options: not applicable, uses PCI auto config
+*/
+/*
+ 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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+
+#define DAQBOARD2000_FIRMWARE "daqboard2000_firmware.bin"
+
+#define DAQBOARD2000_SUBSYSTEM_IDS2 0x0002 /* Daqboard/2000 - 2 Dacs */
+#define DAQBOARD2000_SUBSYSTEM_IDS4 0x0004 /* Daqboard/2000 - 4 Dacs */
+
+/* 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
+
+static const struct comedi_lrange range_daqboard2000_ai = {
+ 13, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125),
+ BIP_RANGE(0.156),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625),
+ UNI_RANGE(0.3125)
+ }
+};
+
+/*
+ * Register Memory Map
+ */
+#define acqControl 0x00 /* u16 */
+#define acqScanListFIFO 0x02 /* u16 */
+#define acqPacerClockDivLow 0x04 /* u32 */
+#define acqScanCounter 0x08 /* u16 */
+#define acqPacerClockDivHigh 0x0a /* u16 */
+#define acqTriggerCount 0x0c /* u16 */
+#define acqResultsFIFO 0x10 /* u16 */
+#define acqResultsShadow 0x14 /* u16 */
+#define acqAdcResult 0x18 /* u16 */
+#define dacScanCounter 0x1c /* u16 */
+#define dacControl 0x20 /* u16 */
+#define dacFIFO 0x24 /* s16 */
+#define dacPacerClockDiv 0x2a /* u16 */
+#define refDacs 0x2c /* u16 */
+#define dioControl 0x30 /* u16 */
+#define dioP3hsioData 0x32 /* s16 */
+#define dioP3Control 0x34 /* u16 */
+#define calEepromControl 0x36 /* u16 */
+#define dacSetting(x) (0x38 + (x)*2) /* s16 */
+#define dioP2ExpansionIO8Bit 0x40 /* s16 */
+#define ctrTmrControl 0x80 /* u16 */
+#define ctrInput(x) (0x88 + (x)*2) /* s16 */
+#define timerDivisor(x) (0xa0 + (x)*2) /* u16 */
+#define dmaControl 0xb0 /* u16 */
+#define trigControl 0xb2 /* u16 */
+#define calEeprom 0xb8 /* u16 */
+#define acqDigitalMark 0xba /* u16 */
+#define trigDacs 0xbc /* u16 */
+#define dioP2ExpansionIO16Bit(x) (0xc0 + (x)*2) /* s16 */
+
+/* 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
+
+struct daq200_boardtype {
+ const char *name;
+ int id;
+};
+static const struct daq200_boardtype boardtypes[] = {
+ {"ids2", DAQBOARD2000_SUBSYSTEM_IDS2},
+ {"ids4", DAQBOARD2000_SUBSYSTEM_IDS4},
+};
+
+struct daqboard2000_private {
+ enum {
+ card_daqboard_2000
+ } card;
+ void __iomem *daq;
+ void __iomem *plx;
+ unsigned int ao_readback[2];
+};
+
+static void writeAcqScanListEntry(struct comedi_device *dev, u16 entry)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ /* udelay(4); */
+ writew(entry & 0x00ff, devpriv->daq + acqScanListFIFO);
+ /* udelay(4); */
+ writew((entry >> 8) & 0x00ff, devpriv->daq + acqScanListFIFO);
+}
+
+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;
+ writeAcqScanListEntry(dev, word0);
+ writeAcqScanListEntry(dev, word1);
+ writeAcqScanListEntry(dev, word2);
+ writeAcqScanListEntry(dev, word3);
+}
+
+static int daqboard2000_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readw(devpriv->daq + acqControl);
+ if (status & context)
+ return 0;
+ return -EBUSY;
+}
+
+static int daqboard2000_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ int gain, chan;
+ int ret;
+ int i;
+
+ writew(DAQBOARD2000_AcqResetScanListFifo |
+ DAQBOARD2000_AcqResetResultsFifo |
+ DAQBOARD2000_AcqResetConfigPipe, devpriv->daq + acqControl);
+
+ /*
+ * If pacer clock is not set to some high value (> 10 us), we
+ * risk multiple samples to be put into the result FIFO.
+ */
+ /* 1 second, should be long enough */
+ writel(1000000, devpriv->daq + acqPacerClockDivLow);
+ writew(0, devpriv->daq + acqPacerClockDivHigh);
+
+ 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 */
+ writew(DAQBOARD2000_SeqStartScanList,
+ devpriv->daq + acqControl);
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqConfigPipeFull);
+ if (ret)
+ return ret;
+
+ writew(DAQBOARD2000_AdcPacerEnable, devpriv->daq + acqControl);
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqLogicScanning);
+ if (ret)
+ return ret;
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status,
+ DAQBOARD2000_AcqResultsFIFOHasValidData);
+ if (ret)
+ return ret;
+
+ data[i] = readw(devpriv->daq + acqResultsFIFO);
+ writew(DAQBOARD2000_AdcPacerDisable, devpriv->daq + acqControl);
+ writew(DAQBOARD2000_SeqStopScanList, devpriv->daq + acqControl);
+ }
+
+ return i;
+}
+
+static int daqboard2000_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ 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 daqboard2000_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int status;
+
+ status = readw(devpriv->daq + dacControl);
+ if ((status & ((chan + 1) * 0x0010)) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int daqboard2000_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+#if 0
+ /*
+ * OK, since it works OK without enabling the DAC's,
+ * let's keep it as simple as possible...
+ */
+ writew((chan + 2) * 0x0010 | 0x0001,
+ devpriv->daq + dacControl);
+ udelay(1000);
+#endif
+ writew(data[i], devpriv->daq + dacSetting(chan));
+
+ ret = comedi_timeout(dev, s, insn, daqboard2000_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ devpriv->ao_readback[chan] = data[i];
+#if 0
+ /*
+ * Since we never enabled the DAC's, we don't need
+ * to disable it...
+ */
+ writew((chan + 2) * 0x0010 | 0x0000,
+ devpriv->daq + dacControl);
+ udelay(1000);
+#endif
+ }
+
+ return i;
+}
+
+static void daqboard2000_resetLocalBus(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRLocalBusHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRLocalBusLo, devpriv->plx + 0x6c);
+ mdelay(10);
+}
+
+static void daqboard2000_reloadPLX(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRReloadHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c);
+ mdelay(10);
+}
+
+static void daqboard2000_pulseProgPin(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ writel(DAQBOARD2000_SECRProgPinHi, devpriv->plx + 0x6c);
+ mdelay(10);
+ writel(DAQBOARD2000_SECRProgPinLo, devpriv->plx + 0x6c);
+ mdelay(10); /* Not in the original code, but I like symmetry... */
+}
+
+static int daqboard2000_pollCPLD(struct comedi_device *dev, int mask)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ 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)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ 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,
+ const u8 *cpld_array, size_t len,
+ unsigned long context)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ int result = -EIO;
+ /* Read the serial EEPROM control register */
+ int secr;
+ int retry;
+ size_t i;
+
+ /* Check to make sure the serial eeprom is present on the board */
+ secr = readl(devpriv->plx + 0x6c);
+ if (!(secr & DAQBOARD2000_EEPROM_PRESENT))
+ return -EIO;
+
+ for (retry = 0; retry < 3; retry++) {
+ 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)
+ 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) {
+ daqboard2000_resetLocalBus(dev);
+ daqboard2000_reloadPLX(dev);
+ result = 0;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static void daqboard2000_adcStopDmaTransfer(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_adcDisarm(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ /* Disable hardware triggers */
+ udelay(2);
+ writew(DAQBOARD2000_TrigAnalog | DAQBOARD2000_TrigDisable,
+ devpriv->daq + trigControl);
+ udelay(2);
+ writew(DAQBOARD2000_TrigTTL | DAQBOARD2000_TrigDisable,
+ devpriv->daq + trigControl);
+
+ /* Stop the scan list FIFO from loading the configuration pipe */
+ udelay(2);
+ writew(DAQBOARD2000_SeqStopScanList, devpriv->daq + acqControl);
+
+ /* Stop the pacer clock */
+ udelay(2);
+ writew(DAQBOARD2000_AdcPacerDisable, devpriv->daq + acqControl);
+
+ /* Stop the input dma (abort channel 1) */
+ daqboard2000_adcStopDmaTransfer(dev);
+}
+
+static void daqboard2000_activateReferenceDacs(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+ unsigned int val;
+ int timeout;
+
+ /* Set the + reference dac value in the FPGA */
+ writew(0x80 | DAQBOARD2000_PosRefDacSelect, devpriv->daq + refDacs);
+ for (timeout = 0; timeout < 20; timeout++) {
+ val = readw(devpriv->daq + dacControl);
+ if ((val & DAQBOARD2000_RefBusy) == 0)
+ break;
+ udelay(2);
+ }
+
+ /* Set the - reference dac value in the FPGA */
+ writew(0x80 | DAQBOARD2000_NegRefDacSelect, devpriv->daq + refDacs);
+ for (timeout = 0; timeout < 20; timeout++) {
+ val = readw(devpriv->daq + dacControl);
+ if ((val & DAQBOARD2000_RefBusy) == 0)
+ break;
+ udelay(2);
+ }
+}
+
+static void daqboard2000_initializeCtrs(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_initializeTmrs(struct comedi_device *dev)
+{
+}
+
+static void daqboard2000_dacDisarm(struct comedi_device *dev)
+{
+}
+
+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);
+}
+
+static int daqboard2000_8255_cb(int dir, int port, int data,
+ unsigned long ioaddr)
+{
+ void __iomem *mmio_base = (void __iomem *)ioaddr;
+
+ if (dir) {
+ writew(data, mmio_base + port * 2);
+ return 0;
+ } else {
+ return readw(mmio_base + port * 2);
+ }
+}
+
+static const void *daqboard2000_find_boardinfo(struct comedi_device *dev,
+ struct pci_dev *pcidev)
+{
+ const struct daq200_boardtype *board;
+ int i;
+
+ if (pcidev->subsystem_device != PCI_VENDOR_ID_IOTECH)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(boardtypes); i++) {
+ board = &boardtypes[i];
+ if (pcidev->subsystem_device == board->id)
+ return board;
+ }
+ return NULL;
+}
+
+static int daqboard2000_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct daq200_boardtype *board;
+ struct daqboard2000_private *devpriv;
+ struct comedi_subdevice *s;
+ int result;
+
+ board = daqboard2000_find_boardinfo(dev, pcidev);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ result = comedi_pci_enable(dev);
+ if (result)
+ return result;
+
+ devpriv->plx = pci_ioremap_bar(pcidev, 0);
+ devpriv->daq = pci_ioremap_bar(pcidev, 2);
+ if (!devpriv->plx || !devpriv->daq)
+ return -ENOMEM;
+
+ result = comedi_alloc_subdevices(dev, 3);
+ if (result)
+ return result;
+
+ readl(devpriv->plx + 0x6c);
+
+ result = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ DAQBOARD2000_FIRMWARE,
+ initialize_daqboard2000, 0);
+ if (result < 0)
+ return result;
+
+ daqboard2000_initializeAdc(dev);
+ daqboard2000_initializeDac(dev);
+
+ 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_bipolar10;
+
+ s = &dev->subdevices[2];
+ result = subdev_8255_init(dev, s, daqboard2000_8255_cb,
+ (unsigned long)(devpriv->daq + dioP2ExpansionIO8Bit));
+ if (result)
+ return result;
+
+ return 0;
+}
+
+static void daqboard2000_detach(struct comedi_device *dev)
+{
+ struct daqboard2000_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->daq)
+ iounmap(devpriv->daq);
+ if (devpriv->plx)
+ iounmap(devpriv->plx);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver daqboard2000_driver = {
+ .driver_name = "daqboard2000",
+ .module = THIS_MODULE,
+ .auto_attach = daqboard2000_auto_attach,
+ .detach = daqboard2000_detach,
+};
+
+static int daqboard2000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &daqboard2000_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id daqboard2000_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_IOTECH, 0x0409) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, daqboard2000_pci_table);
+
+static struct pci_driver daqboard2000_pci_driver = {
+ .name = "daqboard2000",
+ .id_table = daqboard2000_pci_table,
+ .probe = daqboard2000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(daqboard2000_driver, daqboard2000_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(DAQBOARD2000_FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/das08.c b/drivers/staging/comedi/drivers/das08.c
new file mode 100644
index 00000000000..c5e352fb555
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08.c
@@ -0,0 +1,580 @@
+/*
+ * comedi/drivers/das08.c
+ * comedi driver for common DAS08 support (used by ISA/PCI/PCMCIA drivers)
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: das08
+ * Description: DAS-08 compatible boards
+ * Devices: various, see das08_isa, das08_cs, and das08_pci drivers
+ * Author: Warren Jasper, ds, Frank Hess
+ * Updated: Fri, 31 Aug 2012 19:19:06 +0100
+ * Status: works
+ *
+ * This driver is used by the das08_isa, das08_cs, and das08_pci
+ * drivers to provide the common support for the DAS-08 hardware.
+ *
+ * The driver doesn't support asynchronous commands, since the
+ * cheap das08 hardware doesn't really support them.
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "8253.h"
+#include "das08.h"
+
+/*
+ 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 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 int das08_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS08_STATUS);
+ if ((status & DAS08_EOC) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ struct das08_private_struct *devpriv = dev->private;
+ int n;
+ int chan;
+ int range;
+ int lsb, msb;
+ int ret;
+
+ 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)
+ dev_info(dev->class_dev, "over-range\n");
+
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS08_TRIG_12BIT);
+
+ ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ 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 insn->n;
+}
+
+static int das08_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das08_private_struct *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data)) {
+ /* 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(s->state);
+ outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
+ spin_unlock(&dev->spinlock);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+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 insn->n;
+}
+
+static int das08jr_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS08JR_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das08_ao_set_data(struct comedi_device *dev,
+ unsigned int chan, unsigned int data)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ struct das08_private_struct *devpriv = dev->private;
+ unsigned char lsb;
+ unsigned char msb;
+
+ lsb = data & 0xff;
+ msb = (data >> 8) & 0xff;
+ if (thisboard->is_jr) {
+ outb(lsb, dev->iobase + DAS08JR_AO_LSB(chan));
+ outb(msb, dev->iobase + DAS08JR_AO_MSB(chan));
+ /* load DACs */
+ inb(dev->iobase + DAS08JR_DIO);
+ } else {
+ outb(lsb, dev->iobase + DAS08AO_AO_LSB(chan));
+ outb(msb, dev->iobase + DAS08AO_AO_MSB(chan));
+ /* load DACs */
+ inb(dev->iobase + DAS08AO_AO_UPDATE);
+ }
+ devpriv->ao_readback[chan] = data;
+}
+
+static void das08_ao_initialize(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ int n;
+ unsigned int data;
+
+ data = s->maxdata / 2; /* should be about 0 volts */
+ for (n = 0; n < s->n_chan; n++)
+ das08_ao_set_data(dev, n, data);
+}
+
+static int das08_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ unsigned int n;
+ unsigned int chan;
+
+ chan = CR_CHAN(insn->chanspec);
+
+ for (n = 0; n < insn->n; n++)
+ das08_ao_set_data(dev, chan, *data);
+
+ return n;
+}
+
+static int das08_ao_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das08_private_struct *devpriv = dev->private;
+ unsigned int n;
+ unsigned int chan;
+
+ chan = CR_CHAN(insn->chanspec);
+
+ for (n = 0; n < insn->n; n++)
+ data[n] = devpriv->ao_readback[chan];
+
+ return n;
+}
+
+static void i8254_initialize(struct comedi_device *dev)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
+ unsigned int mode = I8254_MODE0 | I8254_BINARY;
+ int i;
+
+ for (i = 0; i < 3; ++i)
+ i8254_set_mode(i8254_iobase, 0, i, mode);
+}
+
+static int das08_counter_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
+ int chan = insn->chanspec;
+
+ data[0] = i8254_read(i8254_iobase, 0, chan);
+ return 1;
+}
+
+static int das08_counter_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
+ int chan = insn->chanspec;
+
+ i8254_write(i8254_iobase, 0, 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)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
+ int chan = insn->chanspec;
+
+ switch (data[0]) {
+ case INSN_CONFIG_SET_COUNTER_MODE:
+ i8254_set_mode(i8254_iobase, 0, chan, data[1]);
+ break;
+ case INSN_CONFIG_8254_READ_STATUS:
+ data[1] = i8254_status(i8254_iobase, 0, chan);
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return 2;
+}
+
+int das08_common_attach(struct comedi_device *dev, unsigned long iobase)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ struct das08_private_struct *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ dev->iobase = iobase;
+
+ dev->board_name = thisboard->name;
+
+ ret = comedi_alloc_subdevices(dev, 6);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai */
+ if (thisboard->ai_nbits) {
+ 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 = das08_ai_rinsn;
+ devpriv->pg_gainlist = das08_gainlists[thisboard->ai_pg];
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ /* ao */
+ if (thisboard->ao_nbits) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = (1 << thisboard->ao_nbits) - 1;
+ s->range_table = &range_bipolar5;
+ s->insn_write = das08_ao_winsn;
+ s->insn_read = das08_ao_rinsn;
+ das08_ao_initialize(dev, s);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* di */
+ if (thisboard->di_nchan) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = thisboard->di_nchan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits =
+ thisboard->is_jr ? das08jr_di_rbits : das08_di_rbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[3];
+ /* do */
+ if (thisboard->do_nchan) {
+ 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->is_jr ? das08jr_do_wbits : das08_do_wbits;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[4];
+ /* 8255 */
+ if (thisboard->i8255_offset != 0) {
+ ret = subdev_8255_init(dev, s, NULL,
+ dev->iobase + thisboard->i8255_offset);
+ if (ret)
+ return ret;
+ } 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;
+ i8254_initialize(dev);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(das08_common_attach);
+
+static int __init das08_init(void)
+{
+ return 0;
+}
+module_init(das08_init);
+
+static void __exit das08_exit(void)
+{
+}
+module_exit(das08_exit);
+
+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 00000000000..18cc170facd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08.h
@@ -0,0 +1,52 @@
+/*
+ das08.h
+
+ Header for das08.c and das08_cs.c
+
+ Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _DAS08_H
+#define _DAS08_H
+
+/* 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;
+ bool is_jr; /* true for 'JR' boards */
+ unsigned int ai_nbits;
+ enum das08_lrange ai_pg;
+ enum das08_ai_encoding ai_encoding;
+ unsigned int ao_nbits;
+ unsigned int di_nchan;
+ unsigned int do_nchan;
+ unsigned int i8255_offset;
+ unsigned int i8254_offset;
+ unsigned int iosize; /* number of ioports used */
+};
+
+struct das08_private_struct {
+ unsigned int do_mux_bits; /* bits for do/mux register on boards
+ * without separate do register
+ */
+ const unsigned int *pg_gainlist;
+ unsigned int ao_readback[2]; /* assume 2 AO channels */
+};
+
+int das08_common_attach(struct comedi_device *dev, unsigned long iobase);
+
+#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 00000000000..f3ccc2ce6d4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_cs.c
@@ -0,0 +1,117 @@
+/*
+ comedi/drivers/das08_cs.c
+ DAS08 driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+
+ PCMCIA support code for this driver 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
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+*/
+/*
+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 <linux/module.h>
+
+#include "../comedidev.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include "das08.h"
+
+static const struct das08_board_struct das08_cs_boards[] = {
+ {
+ .name = "pcm-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_bipolar5,
+ .ai_encoding = das08_pcm_encode12,
+ .di_nchan = 3,
+ .do_nchan = 3,
+ .iosize = 16,
+ },
+};
+
+static int das08_cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct das08_private_struct *devpriv;
+ unsigned long iobase;
+ int ret;
+
+ /* The das08 driver needs the board_ptr */
+ dev->board_ptr = &das08_cs_boards[0];
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ iobase = link->resource[0]->start;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ return das08_common_attach(dev, iobase);
+}
+
+static struct comedi_driver driver_das08_cs = {
+ .driver_name = "das08_cs",
+ .module = THIS_MODULE,
+ .auto_attach = das08_cs_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int das08_pcmcia_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_das08_cs);
+}
+
+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);
+
+static struct pcmcia_driver das08_cs_driver = {
+ .name = "pcm-das08",
+ .owner = THIS_MODULE,
+ .id_table = das08_cs_id_table,
+ .probe = das08_pcmcia_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_das08_cs, das08_cs_driver);
+
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>, "
+ "Frank Mori Hess <fmhess@users.sourceforge.net>");
+MODULE_DESCRIPTION("Comedi driver for ComputerBoards DAS-08 PCMCIA boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das08_isa.c b/drivers/staging/comedi/drivers/das08_isa.c
new file mode 100644
index 00000000000..4fb03d3852d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_isa.c
@@ -0,0 +1,205 @@
+/*
+ * das08_isa.c
+ * comedi driver for DAS08 ISA/PC-104 boards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: das08_isa
+ * Description: DAS-08 ISA/PC-104 compatible boards
+ * Devices: (Keithley Metrabyte) DAS08 [isa-das08],
+ * (ComputerBoards) DAS08 [isa-das08]
+ * (ComputerBoards) DAS08-PGM [das08-pgm]
+ * (ComputerBoards) DAS08-PGH [das08-pgh]
+ * (ComputerBoards) DAS08-PGL [das08-pgl]
+ * (ComputerBoards) DAS08-AOH [das08-aoh]
+ * (ComputerBoards) DAS08-AOL [das08-aol]
+ * (ComputerBoards) DAS08-AOM [das08-aom]
+ * (ComputerBoards) DAS08/JR-AO [das08/jr-ao]
+ * (ComputerBoards) DAS08/JR-16-AO [das08jr-16-ao]
+ * (ComputerBoards) PC104-DAS08 [pc104-das08]
+ * (ComputerBoards) DAS08/JR/16 [das08jr/16]
+ * Author: Warren Jasper, ds, Frank Hess
+ * Updated: Fri, 31 Aug 2012 19:19:06 +0100
+ * Status: works
+ *
+ * This is the ISA/PC-104-specific support split off from the das08 driver.
+ *
+ * Configuration Options:
+ * [0] - base io address
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "das08.h"
+
+static const struct das08_board_struct das08_isa_boards[] = {
+ {
+ /* cio-das08.pdf */
+ .name = "isa-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 8,
+ .i8254_offset = 4,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgm",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgm,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgh",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgh,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08pgx.pdf */
+ .name = "das08-pgl",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgl,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aoh",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgh,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aol",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgl,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08_aox.pdf */
+ .name = "das08-aom",
+ .ai_nbits = 12,
+ .ai_pg = das08_pgm,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8255_offset = 0x0c,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08-jr-ao.pdf */
+ .name = "das08/jr-ao",
+ .is_jr = true,
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .ao_nbits = 12,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .iosize = 16, /* unchecked */
+ }, {
+ /* cio-das08jr-16-ao.pdf */
+ .name = "das08jr-16-ao",
+ .is_jr = true,
+ .ai_nbits = 16,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode16,
+ .ao_nbits = 16,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .i8254_offset = 0x04,
+ .iosize = 16, /* unchecked */
+ }, {
+ .name = "pc104-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 4,
+ .iosize = 16, /* unchecked */
+ }, {
+ .name = "das08jr/16",
+ .is_jr = true,
+ .ai_nbits = 16,
+ .ai_pg = das08_pg_none,
+ .ai_encoding = das08_encode16,
+ .di_nchan = 8,
+ .do_nchan = 8,
+ .iosize = 16, /* unchecked */
+ },
+};
+
+static int das08_isa_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das08_board_struct *thisboard = comedi_board(dev);
+ struct das08_private_struct *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], thisboard->iosize);
+ if (ret)
+ return ret;
+
+ return das08_common_attach(dev, dev->iobase);
+}
+
+static struct comedi_driver das08_isa_driver = {
+ .driver_name = "isa-das08",
+ .module = THIS_MODULE,
+ .attach = das08_isa_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &das08_isa_boards[0].name,
+ .num_names = ARRAY_SIZE(das08_isa_boards),
+ .offset = sizeof(das08_isa_boards[0]),
+};
+module_comedi_driver(das08_isa_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das08_pci.c b/drivers/staging/comedi/drivers/das08_pci.c
new file mode 100644
index 00000000000..d94af09151b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das08_pci.c
@@ -0,0 +1,108 @@
+/*
+ * das08_pci.c
+ * comedi driver for DAS08 PCI boards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: das08_pci
+ * Description: DAS-08 PCI compatible boards
+ * Devices: (ComputerBoards) PCI-DAS08 [pci-das08]
+ * Author: Warren Jasper, ds, Frank Hess
+ * Updated: Fri, 31 Aug 2012 19:19:06 +0100
+ * Status: works
+ *
+ * This is the PCI-specific support split off from the das08 driver.
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "das08.h"
+
+#define PCI_DEVICE_ID_PCIDAS08 0x0029
+
+static const struct das08_board_struct das08_pci_boards[] = {
+ {
+ .name = "pci-das08",
+ .ai_nbits = 12,
+ .ai_pg = das08_bipolar5,
+ .ai_encoding = das08_encode12,
+ .di_nchan = 3,
+ .do_nchan = 4,
+ .i8254_offset = 4,
+ .iosize = 8,
+ },
+};
+
+static int das08_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pdev = comedi_to_pci_dev(dev);
+ struct das08_private_struct *devpriv;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* The das08 driver needs the board_ptr */
+ dev->board_ptr = &das08_pci_boards[0];
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pdev, 2);
+
+ return das08_common_attach(dev, dev->iobase);
+}
+
+static struct comedi_driver das08_pci_comedi_driver = {
+ .driver_name = "pci-das08",
+ .module = THIS_MODULE,
+ .auto_attach = das08_pci_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int das08_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &das08_pci_comedi_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id das08_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CB, PCI_DEVICE_ID_PCIDAS08) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, das08_pci_table);
+
+static struct pci_driver das08_pci_driver = {
+ .name = "pci-das08",
+ .id_table = das08_pci_table,
+ .probe = das08_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(das08_pci_comedi_driver, das08_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das16.c b/drivers/staging/comedi/drivers/das16.c
new file mode 100644
index 00000000000..2feecf199f2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das16.c
@@ -0,0 +1,1259 @@
+/*
+ * das16.c
+ * DAS16 driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com>
+ * Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * 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]
+ * (Keithley Metrabyte) DAS-16G [das-16g]
+ * (Keithley Metrabyte) DAS-16F [das-16f]
+ * (Keithley Metrabyte) DAS-1201 [das-1201]
+ * (Keithley Metrabyte) DAS-1202 [das-1202]
+ * (Keithley Metrabyte) DAS-1401 [das-1401]
+ * (Keithley Metrabyte) DAS-1402 [das-1402]
+ * (Keithley Metrabyte) DAS-1601 [das-1601]
+ * (Keithley Metrabyte) DAS-1602 [das-1602]
+ * (ComputerBoards) PC104-DAS16/JR [pc104-das16jr]
+ * (ComputerBoards) PC104-DAS16JR/16 [pc104-das16jr/16]
+ * (ComputerBoards) CIO-DAS16 [cio-das16]
+ * (ComputerBoards) CIO-DAS16F [cio-das16/f]
+ * (ComputerBoards) CIO-DAS16/JR [cio-das16/jr]
+ * (ComputerBoards) CIO-DAS16JR/16 [cio-das16jr/16]
+ * (ComputerBoards) CIO-DAS1401/12 [cio-das1401/12]
+ * (ComputerBoards) CIO-DAS1402/12 [cio-das1402/12]
+ * (ComputerBoards) CIO-DAS1402/16 [cio-das1402/16]
+ * (ComputerBoards) CIO-DAS1601/12 [cio-das1601/12]
+ * (ComputerBoards) CIO-DAS1602/12 [cio-das1602/12]
+ * (ComputerBoards) CIO-DAS1602/16 [cio-das1602/16]
+ * (ComputerBoards) 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 channel (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)
+ *
+ * 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 <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include <asm/dma.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+#include "8255.h"
+#include "comedi_fc.h"
+
+#define DAS16_DMA_SIZE 0xff00 /* size in bytes of allocated dma buffer */
+
+/*
+ * Register I/O map
+ */
+#define DAS16_TRIG_REG 0x00
+#define DAS16_AI_LSB_REG 0x00
+#define DAS16_AI_MSB_REG 0x01
+#define DAS16_MUX_REG 0x02
+#define DAS16_DIO_REG 0x03
+#define DAS16_AO_LSB_REG(x) ((x) ? 0x06 : 0x04)
+#define DAS16_AO_MSB_REG(x) ((x) ? 0x07 : 0x05)
+#define DAS16_STATUS_REG 0x08
+#define DAS16_STATUS_BUSY (1 << 7)
+#define DAS16_STATUS_UNIPOLAR (1 << 6)
+#define DAS16_STATUS_MUXBIT (1 << 5)
+#define DAS16_STATUS_INT (1 << 4)
+#define DAS16_CTRL_REG 0x09
+#define DAS16_CTRL_INTE (1 << 7)
+#define DAS16_CTRL_IRQ(x) (((x) & 0x7) << 4)
+#define DAS16_CTRL_DMAE (1 << 2)
+#define DAS16_CTRL_PACING_MASK (3 << 0)
+#define DAS16_CTRL_INT_PACER (3 << 0)
+#define DAS16_CTRL_EXT_PACER (2 << 0)
+#define DAS16_CTRL_SOFT_PACER (0 << 0)
+#define DAS16_PACER_REG 0x0a
+#define DAS16_PACER_BURST_LEN(x) (((x) & 0xf) << 4)
+#define DAS16_PACER_CTR0 (1 << 1)
+#define DAS16_PACER_TRIG0 (1 << 0)
+#define DAS16_GAIN_REG 0x0b
+#define DAS16_TIMER_BASE_REG 0x0c /* to 0x0f */
+
+#define DAS1600_CONV_REG 0x404
+#define DAS1600_CONV_DISABLE (1 << 6)
+#define DAS1600_BURST_REG 0x405
+#define DAS1600_BURST_VAL (1 << 6)
+#define DAS1600_ENABLE_REG 0x406
+#define DAS1600_ENABLE_VAL (1 << 6)
+#define DAS1600_STATUS_REG 0x407
+#define DAS1600_STATUS_BME (1 << 6)
+#define DAS1600_STATUS_ME (1 << 5)
+#define DAS1600_STATUS_CD (1 << 4)
+#define DAS1600_STATUS_WS (1 << 1)
+#define DAS1600_STATUS_CLK_10MHZ (1 << 0)
+
+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, {
+ 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 das16_board {
+ const char *name;
+ unsigned int ai_maxdata;
+ unsigned int ai_speed; /* max conversion speed in nanosec */
+ unsigned int ai_pg;
+ unsigned int has_ao:1;
+ unsigned int has_8255:1;
+
+ unsigned int i8255_offset;
+
+ unsigned int size;
+ unsigned int id;
+};
+
+static const struct das16_board das16_boards[] = {
+ {
+ .name = "das-16",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 15000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "das-16g",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 15000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "das-16f",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 8500,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x00,
+ }, {
+ .name = "cio-das16",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 20000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x80,
+ }, {
+ .name = "cio-das16/f",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_none,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x10,
+ .size = 0x14,
+ .id = 0x80,
+ }, {
+ .name = "cio-das16/jr",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 7692,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "pc104-das16jr",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 3300,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "cio-das16jr/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_16jr_16,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "pc104-das16jr/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_16jr_16,
+ .size = 0x10,
+ .id = 0x00,
+ }, {
+ .name = "das-1201",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 20000,
+ .ai_pg = das16_pg_none,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0x20,
+ }, {
+ .name = "das-1202",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_none,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0x20,
+ }, {
+ .name = "das-1401",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1601,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1402",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1601",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1601,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "das-1602",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1401/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1601,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1402/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1402/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1601/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 6250,
+ .ai_pg = das16_pg_1601,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1602/12",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das1602/16",
+ .ai_maxdata = 0xffff,
+ .ai_speed = 10000,
+ .ai_pg = das16_pg_1602,
+ .has_ao = 1,
+ .has_8255 = 1,
+ .i8255_offset = 0x400,
+ .size = 0x408,
+ .id = 0xc0,
+ }, {
+ .name = "cio-das16/330",
+ .ai_maxdata = 0x0fff,
+ .ai_speed = 3030,
+ .ai_pg = das16_pg_16jr,
+ .size = 0x14,
+ .id = 0xf0,
+ },
+};
+
+/* 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 clockbase;
+ unsigned int ctrl_reg;
+ unsigned long adc_byte_count;
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int dma_chan;
+ uint16_t *dma_buffer[2];
+ dma_addr_t dma_buffer_addr[2];
+ unsigned int current_buffer;
+ unsigned int dma_transfer_size;
+ struct comedi_lrange *user_ai_range_table;
+ struct comedi_lrange *user_ao_range_table;
+ struct timer_list timer;
+ short timer_running;
+ unsigned long extra_iobase;
+ unsigned int can_burst:1;
+};
+
+static void das16_ai_enable(struct comedi_device *dev,
+ unsigned int mode, unsigned int src)
+{
+ struct das16_private_struct *devpriv = dev->private;
+
+ devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE |
+ DAS16_CTRL_DMAE |
+ DAS16_CTRL_PACING_MASK);
+ devpriv->ctrl_reg |= mode;
+
+ if (src == TRIG_EXT)
+ devpriv->ctrl_reg |= DAS16_CTRL_EXT_PACER;
+ else
+ devpriv->ctrl_reg |= DAS16_CTRL_INT_PACER;
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+}
+
+static void das16_ai_disable(struct comedi_device *dev)
+{
+ struct das16_private_struct *devpriv = dev->private;
+
+ /* disable interrupts, dma and pacer clocked conversions */
+ devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE |
+ DAS16_CTRL_DMAE |
+ DAS16_CTRL_PACING_MASK);
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+}
+
+/* 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)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ 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) {
+ dev_err(dev->class_dev,
+ "failed to get an even dma transfer, could be trouble\n");
+ }
+ return residue;
+}
+
+static void das16_interrupt(struct comedi_device *dev)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long spin_flags;
+ unsigned long dma_flags;
+ int num_bytes, residue;
+ int buffer_index;
+
+ spin_lock_irqsave(&dev->spinlock, spin_flags);
+ if (!(devpriv->ctrl_reg & DAS16_CTRL_DMAE)) {
+ spin_unlock_irqrestore(&dev->spinlock, spin_flags);
+ 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) {
+ dev_err(dev->class_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;
+
+ /* 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);
+ }
+ 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 void das16_timer_interrupt(unsigned long arg)
+{
+ struct comedi_device *dev = (struct comedi_device *)arg;
+ struct das16_private_struct *devpriv = dev->private;
+
+ das16_interrupt(dev);
+
+ if (devpriv->timer_running)
+ mod_timer(&devpriv->timer, jiffies + timer_period());
+}
+
+static int das16_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != ((chan0 + i) % s->n_chan)) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das16_board *board = comedi_board(dev);
+ struct das16_private_struct *devpriv = dev->private;
+ int err = 0;
+ unsigned int trig_mask;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+
+ trig_mask = TRIG_FOLLOW;
+ if (devpriv->can_burst)
+ trig_mask |= TRIG_TIMER | TRIG_EXT;
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, trig_mask);
+
+ trig_mask = TRIG_TIMER | TRIG_EXT;
+ if (devpriv->can_burst)
+ trig_mask |= TRIG_NOW;
+ err |= cfc_check_trigger_src(&cmd->convert_src, trig_mask);
+
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* make sure scan_begin_src and convert_src dont conflict */
+ if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW)
+ err |= -EINVAL;
+ if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ /* check against maximum frequency */
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ai_speed * cmd->chanlist_len);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up arguments */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(devpriv->clockbase,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(devpriv->clockbase,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das16_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns,
+ int rounding_flags)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + DAS16_TIMER_BASE_REG;
+
+ i8253_cascade_ns_to_timer(devpriv->clockbase,
+ &devpriv->divisor1, &devpriv->divisor2,
+ &ns, rounding_flags);
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+
+ return ns;
+}
+
+static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct das16_board *board = comedi_board(dev);
+ struct das16_private_struct *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int byte;
+ unsigned long flags;
+ int range;
+
+ if (cmd->flags & TRIG_RT) {
+ dev_err(dev->class_dev,
+ "isa dma transfers cannot be performed with TRIG_RT, aborting\n");
+ return -1;
+ }
+
+ devpriv->adc_byte_count = cmd->stop_arg * cfc_bytes_per_scan(s);
+
+ if (devpriv->can_burst)
+ outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV_REG);
+
+ /* 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_REG);
+
+ /* set gain (this is also burst rate register but according to
+ * computer boards manual, burst rate does nothing, even on
+ * keithley cards) */
+ if (board->ai_pg != das16_pg_none) {
+ range = CR_RANGE(cmd->chanlist[0]);
+ outb((das16_gainlists[board->ai_pg])[range],
+ dev->iobase + DAS16_GAIN_REG);
+ }
+
+ /* set counter mode and counts */
+ cmd->convert_arg =
+ das16_set_pacer(dev, cmd->convert_arg,
+ cmd->flags & TRIG_ROUND_MASK);
+
+ /* enable counters */
+ byte = 0;
+ if (devpriv->can_burst) {
+ if (cmd->convert_src == TRIG_NOW) {
+ outb(DAS1600_BURST_VAL,
+ dev->iobase + DAS1600_BURST_REG);
+ /* set burst length */
+ byte |= DAS16_PACER_BURST_LEN(cmd->chanlist_len - 1);
+ } else {
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+ }
+ }
+ outb(byte, dev->iobase + DAS16_PACER_REG);
+
+ /* 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]);
+ devpriv->dma_transfer_size = DAS16_DMA_SIZE;
+ set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size);
+ enable_dma(devpriv->dma_chan);
+ release_dma_lock(flags);
+
+ /* set up interrupt */
+ devpriv->timer_running = 1;
+ devpriv->timer.expires = jiffies + timer_period();
+ add_timer(&devpriv->timer);
+
+ das16_ai_enable(dev, DAS16_CTRL_DMAE, cmd->convert_src);
+
+ if (devpriv->can_burst)
+ outb(0, dev->iobase + DAS1600_CONV_REG);
+
+ return 0;
+}
+
+static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das16_private_struct *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ das16_ai_disable(dev);
+ disable_dma(devpriv->dma_chan);
+
+ /* disable SW timer */
+ if (devpriv->timer_running) {
+ devpriv->timer_running = 0;
+ del_timer(&devpriv->timer);
+ }
+
+ if (devpriv->can_burst)
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+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);
+ unsigned short *data = array;
+
+ for (i = 0; i < num_samples; i++) {
+ data[i] = le16_to_cpu(data[i]);
+ if (s->maxdata == 0x0fff)
+ data[i] >>= 4;
+ data[i] &= s->maxdata;
+ }
+}
+
+static int das16_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS16_STATUS_REG);
+ if ((status & DAS16_STATUS_BUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct das16_board *board = comedi_board(dev);
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ das16_ai_disable(dev);
+
+ /* set multiplexer */
+ outb(chan | (chan << 4), dev->iobase + DAS16_MUX_REG);
+
+ /* set gain */
+ if (board->ai_pg != das16_pg_none) {
+ outb((das16_gainlists[board->ai_pg])[range],
+ dev->iobase + DAS16_GAIN_REG);
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS16_TRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, das16_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(dev->iobase + DAS16_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + DAS16_AI_LSB_REG);
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int das16_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 val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ val <<= 4;
+
+ outb(val & 0xff, dev->iobase + DAS16_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + DAS16_AO_MSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int das16_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DAS16_DIO_REG) & 0xf;
+
+ return insn->n;
+}
+
+static int das16_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS16_DIO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das16_board *board = comedi_board(dev);
+ int diobits;
+
+ /* diobits indicates boards */
+ diobits = inb(dev->iobase + DAS16_DIO_REG) & 0xf0;
+ if (board->id != diobits) {
+ dev_err(dev->class_dev,
+ "requested board's id bits are incorrect (0x%x != 0x%x)\n",
+ board->id, diobits);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void das16_reset(struct comedi_device *dev)
+{
+ outb(0, dev->iobase + DAS16_STATUS_REG);
+ outb(0, dev->iobase + DAS16_CTRL_REG);
+ outb(0, dev->iobase + DAS16_PACER_REG);
+ outb(0, dev->iobase + DAS16_TIMER_BASE_REG + i8254_control_reg);
+}
+
+static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das16_board *board = comedi_board(dev);
+ struct das16_private_struct *devpriv;
+ struct comedi_subdevice *s;
+ struct comedi_lrange *lrange;
+ struct comedi_krange *krange;
+ unsigned int dma_chan = it->options[2];
+ unsigned int status;
+ int ret;
+
+ /* check that clock setting is valid */
+ if (it->options[3]) {
+ if (it->options[3] != 0 &&
+ it->options[3] != 1 && it->options[3] != 10) {
+ dev_err(dev->class_dev,
+ "Invalid option. Master clock must be set to 1 or 10 (MHz)\n");
+ return -EINVAL;
+ }
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ if (board->size < 0x400) {
+ ret = comedi_request_region(dev, it->options[0], board->size);
+ if (ret)
+ return ret;
+ } else {
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+ /* Request an additional region for the 8255 */
+ ret = __comedi_request_region(dev, dev->iobase + 0x400,
+ board->size & 0x3ff);
+ if (ret)
+ return ret;
+ devpriv->extra_iobase = dev->iobase + 0x400;
+ devpriv->can_burst = 1;
+ }
+
+ /* probe id bits to make sure they are consistent */
+ if (das16_probe(dev, it))
+ return -EINVAL;
+
+ /* get master clock speed */
+ if (devpriv->can_burst) {
+ status = inb(dev->iobase + DAS1600_STATUS_REG);
+
+ if (status & DAS1600_STATUS_CLK_10MHZ)
+ devpriv->clockbase = I8254_OSC_BASE_10MHZ;
+ else
+ devpriv->clockbase = I8254_OSC_BASE_1MHZ;
+ } else {
+ if (it->options[3])
+ devpriv->clockbase = I8254_OSC_BASE_1MHZ /
+ it->options[3];
+ else
+ devpriv->clockbase = I8254_OSC_BASE_1MHZ;
+ }
+
+ /* initialize dma */
+ if (dma_chan == 1 || dma_chan == 3) {
+ unsigned long flags;
+ int i;
+
+ if (request_dma(dma_chan, dev->board_name)) {
+ dev_err(dev->class_dev,
+ "failed to request dma channel %i\n",
+ dma_chan);
+ return -EINVAL;
+ }
+ devpriv->dma_chan = dma_chan;
+
+ /* allocate dma buffers */
+ for (i = 0; i < 2; i++) {
+ void *p;
+
+ p = pci_alloc_consistent(NULL, DAS16_DMA_SIZE,
+ &devpriv->dma_buffer_addr[i]);
+ if (!p)
+ return -ENOMEM;
+ devpriv->dma_buffer[i] = p;
+ }
+
+ flags = claim_dma_lock();
+ disable_dma(devpriv->dma_chan);
+ set_dma_mode(devpriv->dma_chan, DMA_MODE_READ);
+ release_dma_lock(flags);
+
+ init_timer(&devpriv->timer);
+ devpriv->timer.function = das16_timer_interrupt;
+ devpriv->timer.data = (unsigned long)dev;
+ }
+
+ /* get any user-defined input range */
+ if (board->ai_pg == das16_pg_none &&
+ (it->options[4] || it->options[5])) {
+ /* allocate single-range range table */
+ lrange = kzalloc(sizeof(*lrange) + sizeof(*krange), GFP_KERNEL);
+ if (!lrange)
+ return -ENOMEM;
+
+ /* initialize ai range */
+ devpriv->user_ai_range_table = lrange;
+ lrange->length = 1;
+ krange = devpriv->user_ai_range_table->range;
+ krange->min = it->options[4];
+ krange->max = it->options[5];
+ krange->flags = UNIT_volt;
+ }
+
+ /* get any user-defined output range */
+ if (it->options[6] || it->options[7]) {
+ /* allocate single-range range table */
+ lrange = kzalloc(sizeof(*lrange) + sizeof(*krange), GFP_KERNEL);
+ if (!lrange)
+ return -ENOMEM;
+
+ /* initialize ao range */
+ devpriv->user_ao_range_table = lrange;
+ lrange->length = 1;
+ krange = devpriv->user_ao_range_table->range;
+ krange->min = it->options[6];
+ krange->max = it->options[7];
+ krange->flags = UNIT_volt;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4 + board->has_8255);
+ if (ret)
+ return ret;
+
+ status = inb(dev->iobase + DAS16_STATUS_REG);
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (status & DAS16_STATUS_MUXBIT) {
+ s->subdev_flags |= SDF_GROUND;
+ s->n_chan = 16;
+ } else {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = 8;
+ }
+ s->len_chanlist = s->n_chan;
+ s->maxdata = board->ai_maxdata;
+ if (devpriv->user_ai_range_table) { /* user defined ai range */
+ s->range_table = devpriv->user_ai_range_table;
+ } else if (status & DAS16_STATUS_UNIPOLAR) {
+ s->range_table = das16_ai_uni_lranges[board->ai_pg];
+ } else {
+ s->range_table = das16_ai_bip_lranges[board->ai_pg];
+ }
+ s->insn_read = das16_ai_insn_read;
+ if (devpriv->dma_chan) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmdtest = das16_cmd_test;
+ s->do_cmd = das16_cmd_exec;
+ s->cancel = das16_cancel;
+ s->munge = das16_ai_munge;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = devpriv->user_ao_range_table;
+ s->insn_write = das16_ao_insn_write;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital Input subdevice */
+ 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 = das16_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ 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 = das16_do_insn_bits;
+
+ /* initialize digital output lines */
+ outb(s->state, dev->iobase + DAS16_DIO_REG);
+
+ /* 8255 Digital I/O subdevice */
+ if (board->has_8255) {
+ s = &dev->subdevices[4];
+ ret = subdev_8255_init(dev, s, NULL,
+ dev->iobase + board->i8255_offset);
+ if (ret)
+ return ret;
+ }
+
+ das16_reset(dev);
+ /* set the interrupt level */
+ devpriv->ctrl_reg = DAS16_CTRL_IRQ(dev->irq);
+ outb(devpriv->ctrl_reg, dev->iobase + DAS16_CTRL_REG);
+
+ if (devpriv->can_burst) {
+ outb(DAS1600_ENABLE_VAL, dev->iobase + DAS1600_ENABLE_REG);
+ outb(0, dev->iobase + DAS1600_CONV_REG);
+ outb(0, dev->iobase + DAS1600_BURST_REG);
+ }
+
+ return 0;
+}
+
+static void das16_detach(struct comedi_device *dev)
+{
+ const struct das16_board *board = comedi_board(dev);
+ struct das16_private_struct *devpriv = dev->private;
+ int i;
+
+ if (devpriv) {
+ if (dev->iobase)
+ das16_reset(dev);
+
+ 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 (devpriv->extra_iobase)
+ release_region(devpriv->extra_iobase,
+ board->size & 0x3ff);
+ }
+
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das16_driver = {
+ .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]),
+};
+module_comedi_driver(das16_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for DAS16 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das16m1.c b/drivers/staging/comedi/drivers/das16m1.c
new file mode 100644
index 00000000000..ec039fbff0f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das16m1.c
@@ -0,0 +1,648 @@
+/*
+ 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 <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: das16m1
+Description: CIO-DAS16/M1
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
+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 <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+#include "8253.h"
+#include "comedi_fc.h"
+
+#define DAS16M1_SIZE 16
+#define DAS16M1_SIZE2 8
+
+#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)
+ }
+};
+
+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;
+ unsigned short ai_buffer[FIFO_SIZE];
+ unsigned int divisor1; /* divides master clock to obtain conversion speed */
+ unsigned int divisor2; /* divides master clock to obtain conversion speed */
+ unsigned long extra_iobase;
+};
+
+static inline unsigned short munge_sample(unsigned short data)
+{
+ return (data >> 4) & 0xfff;
+}
+
+static void munge_sample_array(unsigned short *array, unsigned int num_elements)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_elements; i++)
+ array[i] = munge_sample(array[i]);
+}
+
+static int das16m1_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ if (cmd->chanlist_len == 1)
+ return 0;
+
+ if ((cmd->chanlist_len % 2) != 0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be of even length or length 1\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if ((i % 2) != (chan % 2)) {
+ dev_dbg(dev->class_dev,
+ "even/odd channels must go have even/odd chanlist indices\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das16m1_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 1000);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das16m1_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static void das16m1_set_pacer(struct comedi_device *dev)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + DAS16M1_8254_SECOND;
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+}
+
+static int das16m1_cmd_exec(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long timer_base = dev->iobase + DAS16M1_8254_FIRST;
+ unsigned int byte, i;
+
+ /* 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_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_write(timer_base, 0, 1, 0);
+ /* remember current reading of counter so we know when counter has
+ * actually been loaded */
+ devpriv->initial_hw_count = i8254_read(timer_base, 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);
+ }
+
+ /* enable interrupts and set internal pacer counter mode and counts */
+ devpriv->control_state &= ~PACER_MASK;
+ if (cmd->convert_src == TRIG_TIMER) {
+ das16m1_set_pacer(dev);
+ devpriv->control_state |= INT_PACER;
+ } else { /* TRIG_EXT */
+ devpriv->control_state |= EXT_PACER;
+ }
+
+ /* 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);
+
+ 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)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+
+ devpriv->control_state &= ~INTE & ~PACER_MASK;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static int das16m1_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS16M1_CS);
+ if (status & IRQDATA)
+ return 0;
+ return -EBUSY;
+}
+
+static int das16m1_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ int ret;
+ int n;
+ int byte;
+
+ /* 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);
+
+ ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ 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 insn->n;
+}
+
+static int das16m1_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS16M1_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das16m1_handler(struct comedi_device *dev, unsigned int status)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+ 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;
+ 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 */
+ 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) {
+ async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ comedi_error(dev, "fifo overflow");
+ }
+
+ cfc_handle_events(dev, s);
+}
+
+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) {
+ 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 int das16m1_irq_bits(unsigned int irq)
+{
+ switch (irq) {
+ case 10:
+ return 0x0;
+ case 11:
+ return 0x1;
+ case 12:
+ return 0x2;
+ case 15:
+ return 0x3;
+ case 2:
+ return 0x4;
+ case 3:
+ return 0x5;
+ case 5:
+ return 0x6;
+ case 7:
+ return 0x7;
+ default:
+ return 0x0;
+ }
+}
+
+/*
+ * Options list:
+ * 0 I/O base
+ * 1 IRQ
+ */
+static int das16m1_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct das16m1_private_struct *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], DAS16M1_SIZE);
+ if (ret)
+ return ret;
+ /* Request an additional region for the 8255 */
+ ret = __comedi_request_region(dev, dev->iobase + DAS16M1_82C55,
+ DAS16M1_SIZE2);
+ if (ret)
+ return ret;
+ devpriv->extra_iobase = dev->iobase + DAS16M1_82C55;
+
+ /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */
+ if ((1 << it->options[1]) & 0xdcfc) {
+ ret = request_irq(it->options[1], das16m1_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 8;
+ s->maxdata = (1 << 12) - 1;
+ s->range_table = &range_das16m1;
+ s->insn_read = das16m1_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 256;
+ 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 */
+ ret = subdev_8255_init(dev, s, NULL, devpriv->extra_iobase);
+ if (ret)
+ return ret;
+
+ /* 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(0, dev->iobase + DAS16M1_DIO);
+
+ /* set the interrupt level */
+ devpriv->control_state = das16m1_irq_bits(dev->irq) << 4;
+ outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
+
+ return 0;
+}
+
+static void das16m1_detach(struct comedi_device *dev)
+{
+ struct das16m1_private_struct *devpriv = dev->private;
+
+ if (devpriv && devpriv->extra_iobase)
+ release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das16m1_driver = {
+ .driver_name = "das16m1",
+ .module = THIS_MODULE,
+ .attach = das16m1_attach,
+ .detach = das16m1_detach,
+};
+module_comedi_driver(das16m1_driver);
+
+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 00000000000..859519026c4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das1800.c
@@ -0,0 +1,1608 @@
+/*
+ comedi/drivers/das1800.c
+ Driver for Keitley das1700/das1800 series boards
+ Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: das1800
+Description: Keithley Metrabyte DAS1800 (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+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 <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include "../comedidev.h"
+
+#include <asm/dma.h>
+
+#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 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
+};
+
+/* analog input ranges */
+static const struct comedi_lrange range_ai_das1801 = {
+ 8, {
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02),
+ UNI_RANGE(5),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_ai_das1802 = {
+ 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 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,
+ },
+};
+
+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 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 */
+ unsigned short ao_update_bits; /* remembers the last write to the
+ * 'update' dac */
+};
+
+/* analog out range for 'ao' boards */
+/*
+static const struct comedi_lrange range_ao_2 = {
+ 2, {
+ BIP_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+*/
+
+static inline uint16_t munge_bipolar_sample(const struct comedi_device *dev,
+ uint16_t sample)
+{
+ const struct das1800_board *thisboard = comedi_board(dev);
+
+ 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]);
+ }
+}
+
+static void das1800_handle_fifo_half_full(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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)
+{
+ struct das1800_private *devpriv = dev->private;
+ unsigned 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;
+}
+
+/* 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)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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 (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)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned int status)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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 int das1800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das1800_private *devpriv = dev->private;
+
+ 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;
+}
+
+/* the guts of the interrupt handler, that is shared with das1800_ai_poll */
+static void das1800_ai_handler(struct comedi_device *dev)
+{
+ struct das1800_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int status = inb(dev->iobase + DAS1800_STATUS);
+
+ /* 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");
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(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);
+ async->events |= COMEDI_CB_EOA;
+ } else if (cmd->stop_src == TRIG_COUNT && devpriv->count == 0) { /* stop_src TRIG_COUNT */
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ cfc_handle_events(dev, s);
+}
+
+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) {
+ 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;
+}
+
+/* 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 flags)
+{
+ 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 (flags & TRIG_ROUND_MASK) {
+ 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;
+}
+
+static int das1800_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int unipolar0 = CR_RANGE(cmd->chanlist[0]) & UNIPOLAR;
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int unipolar = CR_RANGE(cmd->chanlist[i]) & UNIPOLAR;
+
+ if (unipolar != unipolar0) {
+ dev_dbg(dev->class_dev,
+ "unipolar and bipolar ranges cannot be mixed in the chanlist\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* test analog input cmd */
+static int das1800_ai_do_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das1800_board *thisboard = comedi_board(dev);
+ struct das1800_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src,
+ TRIG_COUNT | TRIG_EXT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->scan_begin_src != TRIG_FOLLOW &&
+ cmd->convert_src != TRIG_TIMER)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW &&
+ cmd->convert_src == TRIG_TIMER) {
+ /* we are not in burst mode */
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_5MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &cmd->convert_arg, cmd->flags);
+ if (arg != cmd->convert_arg)
+ err++;
+ } else if (cmd->convert_src == TRIG_TIMER) {
+ /* we are in burst mode */
+ arg = cmd->convert_arg;
+ cmd->convert_arg = burst_convert_arg(cmd->convert_arg,
+ cmd->flags);
+ if (arg != cmd->convert_arg)
+ err++;
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->chanlist_len;
+ if (arg > cmd->scan_begin_arg) {
+ cmd->scan_begin_arg = arg;
+ err++;
+ }
+
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_5MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &cmd->scan_begin_arg,
+ cmd->flags);
+ if (arg != cmd->scan_begin_arg)
+ err++;
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das1800_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/* returns appropriate bits for control register a, depending on command */
+static int control_a_bits(const 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(const 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;
+}
+
+static void das1800_setup_counters(struct comedi_device *dev,
+ const struct comedi_cmd *cmd)
+{
+ struct das1800_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + DAS1800_COUNTER;
+
+ /* setup cascaded counters for conversion/scan frequency */
+ if ((cmd->scan_begin_src == TRIG_FOLLOW ||
+ cmd->scan_begin_src == TRIG_TIMER) &&
+ cmd->convert_src == TRIG_TIMER) {
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+ }
+
+ /* setup counter 0 for 'about triggering' */
+ if (cmd->stop_src == TRIG_EXT) {
+ i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 0, 1);
+ }
+}
+
+/* utility function that suggests a dma transfer size based on the conversion period 'ns' */
+static unsigned int suggest_transfer_size(const 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;
+}
+
+/* sets up dma */
+static void setup_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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,
+ const 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)
+{
+ struct das1800_private *devpriv = dev->private;
+ int control_a, control_c;
+ struct comedi_async *async = s->async;
+ const struct comedi_cmd *cmd = &async->cmd;
+
+ /* 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);
+ das1800_setup_counters(dev, cmd);
+ 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)
+{
+ const struct das1800_board *thisboard = comedi_board(dev);
+ int i, n;
+ int chan, range, aref, chan_range;
+ int timeout = 1000;
+ unsigned 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)
+{
+ const struct das1800_board *thisboard = comedi_board(dev);
+ struct das1800_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+/* int range = CR_RANGE(insn->chanspec); */
+ int update_chan = thisboard->ao_n_chan - 1;
+ unsigned 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 insn->n;
+}
+
+static int das1800_do_wbits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS1800_DIGITAL);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das1800_init_dma(struct comedi_device *dev, unsigned int dma0,
+ unsigned int dma1)
+{
+ struct das1800_private *devpriv = dev->private;
+ 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->class_dev,
+ "only supports dma channels 5 through 7\n");
+ dev_err(dev->class_dev,
+ "Dual dma only allows the following combinations:\n");
+ dev_err(dev->class_dev,
+ "dma 5,6 / 6,7 / or 7,5\n");
+ return -EINVAL;
+ break;
+ }
+ if (request_dma(dma0, dev->driver->driver_name)) {
+ dev_err(dev->class_dev,
+ "failed to allocate dma channel %i\n", dma0);
+ return -EINVAL;
+ }
+ devpriv->dma0 = dma0;
+ devpriv->dma_current = dma0;
+ if (dma1) {
+ if (request_dma(dma1, dev->driver->driver_name)) {
+ dev_err(dev->class_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_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->class_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->class_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->class_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->class_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->class_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->class_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_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das1800_board *thisboard;
+ struct das1800_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq = it->options[1];
+ unsigned int dma0 = it->options[2];
+ unsigned int dma1 = it->options[3];
+ int board;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], DAS1800_SIZE);
+ if (ret)
+ return ret;
+
+ board = das1800_probe(dev);
+ if (board < 0) {
+ dev_err(dev->class_dev, "unable to determine board type\n");
+ return -ENODEV;
+ }
+
+ dev->board_ptr = das1800_boards + board;
+ thisboard = comedi_board(dev);
+ 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) {
+ unsigned long iobase2 = dev->iobase + IOBASE2;
+
+ ret = __comedi_request_region(dev, iobase2, DAS1800_SIZE);
+ if (ret)
+ return ret;
+ devpriv->iobase2 = iobase2;
+ }
+
+ if (irq == 3 || irq == 5 || irq == 7 || irq == 10 || irq == 11 ||
+ irq == 15) {
+ ret = request_irq(irq, das1800_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = irq;
+
+ switch (irq) {
+ 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;
+ }
+ }
+ }
+
+ ret = das1800_init_dma(dev, dma0, dma1);
+ if (ret < 0)
+ return ret;
+
+ if (devpriv->ai_buf0 == NULL) {
+ devpriv->ai_buf0 =
+ kmalloc(FIFO_SIZE * sizeof(uint16_t), GFP_KERNEL);
+ if (devpriv->ai_buf0 == NULL)
+ return -ENOMEM;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND;
+ if (thisboard->common)
+ s->subdev_flags |= SDF_COMMON;
+ s->n_chan = thisboard->qram_len;
+ s->maxdata = (1 << thisboard->resolution) - 1;
+ s->range_table = thisboard->range_ai;
+ s->insn_read = das1800_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmd = das1800_ai_do_cmd;
+ s->do_cmdtest = das1800_ai_do_cmdtest;
+ 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_bipolar10;
+ 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(0, 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 void das1800_detach(struct comedi_device *dev)
+{
+ struct das1800_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->dma0)
+ free_dma(devpriv->dma0);
+ if (devpriv->dma1)
+ free_dma(devpriv->dma1);
+ kfree(devpriv->ai_buf0);
+ kfree(devpriv->ai_buf1);
+ if (devpriv->iobase2)
+ release_region(devpriv->iobase2, DAS1800_SIZE);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver das1800_driver = {
+ .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),
+};
+module_comedi_driver(das1800_driver);
+
+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 00000000000..d18eea6c01a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das6402.c
@@ -0,0 +1,547 @@
+/*
+ * das6402.c
+ * Comedi driver for DAS6402 compatible boards
+ * Copyright(c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
+ *
+ * Rewrite of an experimental driver by:
+ * Copyright (C) 1999 Oystein Svendsen <svendsen@pvv.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: das6402
+ * Description: Keithley Metrabyte DAS6402 (& compatibles)
+ * Devices: (Keithley Metrabyte) DAS6402-12 (das6402-12)
+ * (Keithley Metrabyte) DAS6402-16 (das6402-16)
+ * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Updated: Fri, 14 Mar 2014 10:18:43 -0700
+ * Status: unknown
+ *
+ * Configuration Options:
+ * [0] - I/O base address
+ * [1] - IRQ (optional, needed for async command support)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+#include "8253.h"
+
+/*
+ * Register I/O map
+ */
+#define DAS6402_AI_DATA_REG 0x00
+#define DAS6402_AI_MUX_REG 0x02
+#define DAS6402_AI_MUX_LO(x) (((x) & 0x3f) << 0)
+#define DAS6402_AI_MUX_HI(x) (((x) & 0x3f) << 8)
+#define DAS6402_DI_DO_REG 0x03
+#define DAS6402_AO_DATA_REG(x) (0x04 + ((x) * 2))
+#define DAS6402_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define DAS6402_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define DAS6402_STATUS_REG 0x08
+#define DAS6402_STATUS_FFNE (1 << 0)
+#define DAS6402_STATUS_FHALF (1 << 1)
+#define DAS6402_STATUS_FFULL (1 << 2)
+#define DAS6402_STATUS_XINT (1 << 3)
+#define DAS6402_STATUS_INT (1 << 4)
+#define DAS6402_STATUS_XTRIG (1 << 5)
+#define DAS6402_STATUS_INDGT (1 << 6)
+#define DAS6402_STATUS_10MHZ (1 << 7)
+#define DAS6402_STATUS_W_CLRINT (1 << 0)
+#define DAS6402_STATUS_W_CLRXTR (1 << 1)
+#define DAS6402_STATUS_W_CLRXIN (1 << 2)
+#define DAS6402_STATUS_W_EXTEND (1 << 4)
+#define DAS6402_STATUS_W_ARMED (1 << 5)
+#define DAS6402_STATUS_W_POSTMODE (1 << 6)
+#define DAS6402_STATUS_W_10MHZ (1 << 7)
+#define DAS6402_CTRL_REG 0x09
+#define DAS6402_CTRL_SOFT_TRIG (0 << 0)
+#define DAS6402_CTRL_EXT_FALL_TRIG (1 << 0)
+#define DAS6402_CTRL_EXT_RISE_TRIG (2 << 0)
+#define DAS6402_CTRL_PACER_TRIG (3 << 0)
+#define DAS6402_CTRL_BURSTEN (1 << 2)
+#define DAS6402_CTRL_XINTE (1 << 3)
+#define DAS6402_CTRL_IRQ(x) ((x) << 4)
+#define DAS6402_CTRL_INTE (1 << 7)
+#define DAS6402_TRIG_REG 0x0a
+#define DAS6402_TRIG_TGEN (1 << 0)
+#define DAS6402_TRIG_TGSEL (1 << 1)
+#define DAS6402_TRIG_TGPOL (1 << 2)
+#define DAS6402_TRIG_PRETRIG (1 << 3)
+#define DAS6402_AO_RANGE(_chan, _range) ((_range) << ((_chan) ? 6 : 4))
+#define DAS6402_AO_RANGE_MASK(_chan) (3 << ((_chan) ? 6 : 4))
+#define DAS6402_MODE_REG 0x0b
+#define DAS6402_MODE_RANGE(x) ((x) << 0)
+#define DAS6402_MODE_POLLED (0 << 2)
+#define DAS6402_MODE_FIFONEPTY (1 << 2)
+#define DAS6402_MODE_FIFOHFULL (2 << 2)
+#define DAS6402_MODE_EOB (3 << 2)
+#define DAS6402_MODE_ENHANCED (1 << 4)
+#define DAS6402_MODE_SE (1 << 5)
+#define DAS6402_MODE_UNI (1 << 6)
+#define DAS6402_MODE_DMA1 (0 << 7)
+#define DAS6402_MODE_DMA3 (1 << 7)
+#define DAS6402_TIMER_BASE 0x0c
+
+static const struct comedi_lrange das6402_ai_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)
+ }
+};
+
+/*
+ * Analog output ranges are programmable on the DAS6402/12.
+ * For the DAS6402/16 the range bits have no function, the
+ * DAC ranges are selected by switches on the board.
+ */
+static const struct comedi_lrange das6402_ao_ranges = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+struct das6402_boardinfo {
+ const char *name;
+ unsigned int maxdata;
+};
+
+static struct das6402_boardinfo das6402_boards[] = {
+ {
+ .name = "das6402-12",
+ .maxdata = 0x0fff,
+ }, {
+ .name = "das6402-16",
+ .maxdata = 0xffff,
+ },
+};
+
+struct das6402_private {
+ unsigned int irq;
+
+ unsigned int count;
+ unsigned int divider1;
+ unsigned int divider2;
+
+ unsigned int ao_range;
+ unsigned int ao_readback[2];
+};
+
+static void das6402_set_mode(struct comedi_device *dev,
+ unsigned int mode)
+{
+ outb(DAS6402_MODE_ENHANCED | mode, dev->iobase + DAS6402_MODE_REG);
+}
+
+static void das6402_set_extended(struct comedi_device *dev,
+ unsigned int val)
+{
+ outb(DAS6402_STATUS_W_EXTEND, dev->iobase + DAS6402_STATUS_REG);
+ outb(DAS6402_STATUS_W_EXTEND | val, dev->iobase + DAS6402_STATUS_REG);
+ outb(val, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static void das6402_clear_all_interrupts(struct comedi_device *dev)
+{
+ outb(DAS6402_STATUS_W_CLRINT |
+ DAS6402_STATUS_W_CLRXTR |
+ DAS6402_STATUS_W_CLRXIN, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static void das6402_ai_clear_eoc(struct comedi_device *dev)
+{
+ outb(DAS6402_STATUS_W_CLRINT, dev->iobase + DAS6402_STATUS_REG);
+}
+
+static void das6402_enable_counter(struct comedi_device *dev, bool load)
+{
+ struct das6402_private *devpriv = dev->private;
+ unsigned long timer_iobase = dev->iobase + DAS6402_TIMER_BASE;
+
+ if (load) {
+ i8254_set_mode(timer_iobase, 0, 0, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_iobase, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_iobase, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_iobase, 0, 0, devpriv->count);
+ i8254_write(timer_iobase, 0, 1, devpriv->divider1);
+ i8254_write(timer_iobase, 0, 2, devpriv->divider2);
+
+ } else {
+ i8254_set_mode(timer_iobase, 0, 0, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_iobase, 0, 1, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_iobase, 0, 2, I8254_MODE0 | I8254_BINARY);
+ }
+}
+
+static irqreturn_t das6402_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+
+ das6402_clear_all_interrupts(dev);
+
+ return IRQ_HANDLED;
+}
+
+static int das6402_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ return -EINVAL;
+}
+
+static int das6402_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ return -EINVAL;
+}
+
+static int das6402_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ return 0;
+}
+
+static void das6402_ai_soft_trig(struct comedi_device *dev)
+{
+ outw(0, dev->iobase + DAS6402_AI_DATA_REG);
+}
+
+static int das6402_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS6402_STATUS_REG);
+ if (status & DAS6402_STATUS_FFNE)
+ return 0;
+ return -EBUSY;
+}
+
+static int das6402_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ val = DAS6402_MODE_RANGE(range) | DAS6402_MODE_POLLED;
+ if (aref == AREF_DIFF) {
+ if (chan > s->n_chan / 2)
+ return -EINVAL;
+ } else {
+ val |= DAS6402_MODE_SE;
+ }
+ if (comedi_range_is_unipolar(s, range))
+ val |= DAS6402_MODE_UNI;
+
+ /* enable software conversion trigger */
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ das6402_set_mode(dev, val);
+
+ /* load the mux for single channel conversion */
+ outw(DAS6402_AI_MUX_HI(chan) | DAS6402_AI_MUX_LO(chan),
+ dev->iobase + DAS6402_AI_MUX_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ das6402_ai_clear_eoc(dev);
+ das6402_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, das6402_ai_eoc, 0);
+ if (ret)
+ break;
+
+ val = inw(dev->iobase + DAS6402_AI_DATA_REG);
+
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+
+ data[i] = val;
+ }
+
+ das6402_ai_clear_eoc(dev);
+
+ return insn->n;
+}
+
+static int das6402_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das6402_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ /* set the range for this channel */
+ val = devpriv->ao_range;
+ val &= ~DAS6402_AO_RANGE_MASK(chan);
+ val |= DAS6402_AO_RANGE(chan, range);
+ if (val != devpriv->ao_range) {
+ devpriv->ao_range = val;
+ outb(val, dev->iobase + DAS6402_TRIG_REG);
+ }
+
+ /*
+ * The DAS6402/16 has a jumper to select either individual
+ * update (UPDATE) or simultaneous updating (XFER) of both
+ * DAC's. In UPDATE mode, when the MSB is written, that DAC
+ * is updated. In XFER mode, after both DAC's are loaded,
+ * a read cycle of any DAC register will update both DAC's
+ * simultaneously.
+ *
+ * If you have XFER mode enabled a (*insn_read) will need
+ * to be performed in order to update the DAC's with the
+ * last value written.
+ */
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ devpriv->ao_readback[chan] = val;
+
+ if (s->maxdata == 0x0fff) {
+ /*
+ * DAS6402/12 has the two 8-bit DAC registers, left
+ * justified (the 4 LSB bits are don't care). Data
+ * can be written as one word.
+ */
+ val <<= 4;
+ outw(val, dev->iobase + DAS6402_AO_DATA_REG(chan));
+ } else {
+ /*
+ * DAS6402/16 uses both 8-bit DAC registers and needs
+ * to be written LSB then MSB.
+ */
+ outb(val & 0xff,
+ dev->iobase + DAS6402_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff,
+ dev->iobase + DAS6402_AO_LSB_REG(chan));
+ }
+ }
+
+ return insn->n;
+}
+
+static int das6402_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das6402_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ /*
+ * If XFER mode is enabled, reading any DAC register
+ * will update both DAC's simultaneously.
+ */
+ inw(dev->iobase + DAS6402_AO_LSB_REG(chan));
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int das6402_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + DAS6402_DI_DO_REG);
+
+ return insn->n;
+}
+
+static int das6402_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAS6402_DI_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void das6402_reset(struct comedi_device *dev)
+{
+ struct das6402_private *devpriv = dev->private;
+
+ /* enable "Enhanced" mode */
+ outb(DAS6402_MODE_ENHANCED, dev->iobase + DAS6402_MODE_REG);
+
+ /* enable 10MHz pacer clock */
+ das6402_set_extended(dev, DAS6402_STATUS_W_10MHZ);
+
+ /* enable software conversion trigger */
+ outb(DAS6402_CTRL_SOFT_TRIG, dev->iobase + DAS6402_CTRL_REG);
+
+ /* default ADC to single-ended unipolar 10V inputs */
+ das6402_set_mode(dev, DAS6402_MODE_RANGE(0) |
+ DAS6402_MODE_POLLED |
+ DAS6402_MODE_SE |
+ DAS6402_MODE_UNI);
+
+ /* default mux for single channel conversion (channel 0) */
+ outw(DAS6402_AI_MUX_HI(0) | DAS6402_AI_MUX_LO(0),
+ dev->iobase + DAS6402_AI_MUX_REG);
+
+ /* set both DAC's for unipolar 5V output range */
+ devpriv->ao_range = DAS6402_AO_RANGE(0, 2) | DAS6402_AO_RANGE(1, 2);
+ outb(devpriv->ao_range, dev->iobase + DAS6402_TRIG_REG);
+
+ /* set both DAC's to 0V */
+ outw(0, dev->iobase + DAS6402_AO_DATA_REG(0));
+ outw(0, dev->iobase + DAS6402_AO_DATA_REG(0));
+ inw(dev->iobase + DAS6402_AO_LSB_REG(0));
+
+ das6402_enable_counter(dev, false);
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + DAS6402_DI_DO_REG);
+
+ das6402_clear_all_interrupts(dev);
+}
+
+static int das6402_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct das6402_boardinfo *board = comedi_board(dev);
+ struct das6402_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ das6402_reset(dev);
+
+ /* IRQs 2,3,5,6,7, 10,11,15 are valid for "enhanced" mode */
+ if ((1 << it->options[1]) & 0x8cec) {
+ ret = request_irq(it->options[1], das6402_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = it->options[1];
+
+ switch (dev->irq) {
+ case 10:
+ devpriv->irq = 4;
+ break;
+ case 11:
+ devpriv->irq = 1;
+ break;
+ case 15:
+ devpriv->irq = 6;
+ break;
+ default:
+ devpriv->irq = dev->irq;
+ break;
+ }
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 64;
+ s->maxdata = board->maxdata;
+ s->range_table = &das6402_ai_ranges;
+ s->insn_read = das6402_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = das6402_ai_cmdtest;
+ s->do_cmd = das6402_ai_cmd;
+ s->cancel = das6402_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 2;
+ s->maxdata = board->maxdata;
+ s->range_table = &das6402_ao_ranges;
+ s->insn_write = das6402_ao_insn_write;
+ s->insn_read = das6402_ao_insn_read;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das6402_di_insn_bits;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = das6402_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver das6402_driver = {
+ .driver_name = "das6402",
+ .module = THIS_MODULE,
+ .attach = das6402_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &das6402_boards[0].name,
+ .num_names = ARRAY_SIZE(das6402_boards),
+ .offset = sizeof(struct das6402_boardinfo),
+};
+module_comedi_driver(das6402_driver)
+
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("Comedi driver for DAS6402 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/das800.c b/drivers/staging/comedi/drivers/das800.c
new file mode 100644
index 00000000000..6f7f8d531dd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/das800.c
@@ -0,0 +1,771 @@
+/*
+ comedi/drivers/das800.c
+ Driver for Keitley das800 series boards and compatibles
+ Copyright (C) 2000 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: das800
+Description: Keithley Metrabyte DAS800 (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+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 <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#include "8253.h"
+#include "comedi_fc.h"
+
+#define DAS800_SIZE 8
+#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 /* cio-das802/16 fifo overflow */
+#define CIO_ENHF 0x90 /* cio-das802/16 fifo half full int ena */
+#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
+
+#define DAS802_16_HALF_FIFO_SZ 128
+
+struct das800_board {
+ const char *name;
+ int ai_speed;
+ const struct comedi_lrange *ai_range;
+ int resolution;
+};
+
+static const struct comedi_lrange range_das801_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(0.5),
+ UNI_RANGE(1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(0.1),
+ BIP_RANGE(0.01),
+ UNI_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_cio_das801_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(0.5),
+ UNI_RANGE(1),
+ BIP_RANGE(0.05),
+ UNI_RANGE(0.1),
+ BIP_RANGE(0.005),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_das802_ai = {
+ 9, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(2.5),
+ UNI_RANGE(5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(2.5),
+ BIP_RANGE(0.625),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_das80216_ai = {
+ 8, {
+ BIP_RANGE(10),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(5),
+ BIP_RANGE(2.5),
+ UNI_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(1.25)
+ }
+};
+
+enum das800_boardinfo {
+ BOARD_DAS800,
+ BOARD_CIODAS800,
+ BOARD_DAS801,
+ BOARD_CIODAS801,
+ BOARD_DAS802,
+ BOARD_CIODAS802,
+ BOARD_CIODAS80216,
+};
+
+static const struct das800_board das800_boards[] = {
+ [BOARD_DAS800] = {
+ .name = "das-800",
+ .ai_speed = 25000,
+ .ai_range = &range_bipolar5,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS800] = {
+ .name = "cio-das800",
+ .ai_speed = 20000,
+ .ai_range = &range_bipolar5,
+ .resolution = 12,
+ },
+ [BOARD_DAS801] = {
+ .name = "das-801",
+ .ai_speed = 25000,
+ .ai_range = &range_das801_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS801] = {
+ .name = "cio-das801",
+ .ai_speed = 20000,
+ .ai_range = &range_cio_das801_ai,
+ .resolution = 12,
+ },
+ [BOARD_DAS802] = {
+ .name = "das-802",
+ .ai_speed = 25000,
+ .ai_range = &range_das802_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS802] = {
+ .name = "cio-das802",
+ .ai_speed = 20000,
+ .ai_range = &range_das802_ai,
+ .resolution = 12,
+ },
+ [BOARD_CIODAS80216] = {
+ .name = "cio-das802/16",
+ .ai_speed = 10000,
+ .ai_range = &range_das80216_ai,
+ .resolution = 16,
+ },
+};
+
+struct das800_private {
+ unsigned int count; /* number of data points left to be taken */
+ unsigned int divisor1; /* counter 1 value for timed conversions */
+ unsigned int divisor2; /* counter 2 value for timed conversions */
+ unsigned int do_bits; /* digital output bits */
+};
+
+static void das800_ind_write(struct comedi_device *dev,
+ unsigned val, unsigned reg)
+{
+ /*
+ * Select dev->iobase + 2 to be desired register
+ * then write to that register.
+ */
+ outb(reg, dev->iobase + DAS800_GAIN);
+ outb(val, dev->iobase + 2);
+}
+
+static unsigned das800_ind_read(struct comedi_device *dev, unsigned reg)
+{
+ /*
+ * Select dev->iobase + 7 to be desired register
+ * then read from that register.
+ */
+ outb(reg, dev->iobase + DAS800_GAIN);
+ return inb(dev->iobase + 7);
+}
+
+static void das800_enable(struct comedi_device *dev)
+{
+ const struct das800_board *thisboard = comedi_board(dev);
+ struct das800_private *devpriv = dev->private;
+ 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);
+ /* enable hardware triggering */
+ das800_ind_write(dev, CONV_HCEN, CONV_CONTROL);
+ /* enable card's interrupt */
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+}
+
+static void das800_disable(struct comedi_device *dev)
+{
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ /* disable hardware triggering of conversions */
+ das800_ind_write(dev, 0x0, CONV_CONTROL);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+}
+
+static void das800_set_frequency(struct comedi_device *dev)
+{
+ struct das800_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + DAS800_8254;
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+}
+
+static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct das800_private *devpriv = dev->private;
+
+ devpriv->count = 0;
+ das800_disable(dev);
+ return 0;
+}
+
+static int das800_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be consecutive, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int das800_ai_do_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct das800_board *thisboard = comedi_board(dev);
+ struct das800_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_1MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= das800_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int das800_ai_do_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ const struct das800_board *thisboard = comedi_board(dev);
+ struct das800_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int gain = CR_RANGE(cmd->chanlist[0]);
+ unsigned int start_chan = CR_CHAN(cmd->chanlist[0]);
+ unsigned int end_chan = (start_chan + cmd->chanlist_len - 1) % 8;
+ unsigned int scan_chans = (end_chan << 3) | start_chan;
+ int conv_bits;
+ unsigned long irq_flags;
+
+ das800_disable(dev);
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ /* set scan limits */
+ das800_ind_write(dev, scan_chans, SCAN_LIMITS);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ /* set gain */
+ if (thisboard->resolution == 12 && gain > 0)
+ gain += 0x7;
+ gain &= 0xf;
+ outb(gain, dev->iobase + DAS800_GAIN);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->count = cmd->stop_arg * cmd->chanlist_len;
+ else /* TRIG_NONE */
+ devpriv->count = 0;
+
+ /* 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 (cmd->start_src == TRIG_EXT)
+ conv_bits |= DTEN;
+ if (cmd->convert_src == TRIG_TIMER) {
+ conv_bits |= CASC | ITE;
+ /* set conversion frequency */
+ das800_set_frequency(dev);
+ }
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, conv_bits, CONV_CONTROL);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ das800_enable(dev);
+ return 0;
+}
+
+static unsigned int das800_ai_get_sample(struct comedi_device *dev)
+{
+ unsigned int lsb = inb(dev->iobase + DAS800_LSB);
+ unsigned int msb = inb(dev->iobase + DAS800_MSB);
+
+ return (msb << 8) | lsb;
+}
+
+static irqreturn_t das800_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct das800_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ unsigned long irq_flags;
+ unsigned int status;
+ unsigned int val;
+ bool fifo_empty;
+ bool fifo_overflow;
+ int i;
+
+ status = inb(dev->iobase + DAS800_STATUS);
+ if (!(status & IRQ))
+ return IRQ_NONE;
+ if (!dev->attached)
+ return IRQ_HANDLED;
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ status = das800_ind_read(dev, CONTROL1) & STATUS2_HCEN;
+ /*
+ * Don't release spinlock yet since we want to make sure
+ * no one else disables hardware conversions.
+ */
+
+ /* if hardware conversions are not enabled, then quit */
+ if (status == 0) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < DAS802_16_HALF_FIFO_SZ; i++) {
+ val = das800_ai_get_sample(dev);
+ if (s->maxdata == 0x0fff) {
+ fifo_empty = !!(val & FIFO_EMPTY);
+ fifo_overflow = !!(val & FIFO_OVF);
+ } else {
+ /* cio-das802/16 has no fifo empty status bit */
+ fifo_empty = false;
+ fifo_overflow = !!(inb(dev->iobase + DAS800_GAIN) &
+ CIO_FFOV);
+ }
+ if (fifo_empty || fifo_overflow)
+ break;
+
+ if (s->maxdata == 0x0fff)
+ val >>= 4; /* 12-bit sample */
+
+ /* if there are more data points to collect */
+ if (cmd->stop_src == TRIG_NONE || devpriv->count > 0) {
+ /* write data point to buffer */
+ cfc_write_to_buffer(s, val & s->maxdata);
+ devpriv->count--;
+ }
+ }
+ async->events |= COMEDI_CB_BLOCK;
+
+ if (fifo_overflow) {
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+ }
+
+ if (cmd->stop_src == TRIG_NONE || devpriv->count > 0) {
+ /* Re-enable card's interrupt.
+ * We already have spinlock, so indirect addressing is safe */
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
+ CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ } else {
+ /* otherwise, stop taking data */
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ das800_disable(dev);
+ async->events |= COMEDI_CB_EOA;
+ }
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int das800_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DAS800_STATUS);
+ if ((status & BUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int das800_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned long irq_flags;
+ unsigned int val;
+ int ret;
+ int i;
+
+ das800_disable(dev);
+
+ /* set multiplexer */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, chan | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ /* set gain / range */
+ if (s->maxdata == 0x0fff && range)
+ range += 0x7;
+ range &= 0xf;
+ outb(range, dev->iobase + DAS800_GAIN);
+
+ udelay(5);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ outb_p(0, dev->iobase + DAS800_MSB);
+
+ ret = comedi_timeout(dev, s, insn, das800_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = das800_ai_get_sample(dev);
+ if (s->maxdata == 0x0fff)
+ val >>= 4; /* 12-bit sample */
+ data[i] = val & s->maxdata;
+ }
+
+ return insn->n;
+}
+
+static int das800_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = (inb(dev->iobase + DAS800_STATUS) >> 4) & 0x7;
+
+ return insn->n;
+}
+
+static int das800_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct das800_private *devpriv = dev->private;
+ unsigned long irq_flags;
+
+ if (comedi_dio_update_state(s, data)) {
+ devpriv->do_bits = s->state << 4;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits,
+ CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int das800_probe(struct comedi_device *dev)
+{
+ const struct das800_board *thisboard = comedi_board(dev);
+ int board = thisboard ? thisboard - das800_boards : -EINVAL;
+ int id_bits;
+ unsigned long irq_flags;
+
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ id_bits = das800_ind_read(dev, ID) & 0x3;
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ switch (id_bits) {
+ case 0x0:
+ if (board == BOARD_DAS800 || board == BOARD_CIODAS800)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-800\n");
+ board = BOARD_DAS800;
+ break;
+ case 0x2:
+ if (board == BOARD_DAS801 || board == BOARD_CIODAS801)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-801\n");
+ board = BOARD_DAS801;
+ break;
+ case 0x3:
+ if (board == BOARD_DAS802 || board == BOARD_CIODAS802 ||
+ board == BOARD_CIODAS80216)
+ break;
+ dev_dbg(dev->class_dev, "Board model (probed): DAS-802\n");
+ board = BOARD_DAS802;
+ break;
+ default:
+ dev_dbg(dev->class_dev, "Board model: 0x%x (unknown)\n",
+ id_bits);
+ board = -EINVAL;
+ break;
+ }
+ return board;
+}
+
+static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct das800_board *thisboard;
+ struct das800_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq = it->options[1];
+ unsigned long irq_flags;
+ int board;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], DAS800_SIZE);
+ if (ret)
+ return ret;
+
+ board = das800_probe(dev);
+ if (board < 0) {
+ dev_dbg(dev->class_dev, "unable to determine board type\n");
+ return -ENODEV;
+ }
+ dev->board_ptr = das800_boards + board;
+ thisboard = comedi_board(dev);
+ dev->board_name = thisboard->name;
+
+ if (irq > 1 && irq <= 7) {
+ ret = request_irq(irq, das800_interrupt, 0, dev->board_name,
+ dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = (1 << thisboard->resolution) - 1;
+ s->range_table = thisboard->ai_range;
+ s->insn_read = das800_ai_insn_read;
+ if (dev->irq) {
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 8;
+ s->do_cmdtest = das800_ai_do_cmdtest;
+ s->do_cmd = das800_ai_do_cmd;
+ s->cancel = das800_cancel;
+ }
+
+ /* Digital Input subdevice */
+ 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_insn_bits;
+
+ /* Digital Output subdevice */
+ 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_insn_bits;
+
+ das800_disable(dev);
+
+ /* initialize digital out channels */
+ spin_lock_irqsave(&dev->spinlock, irq_flags);
+ das800_ind_write(dev, CONTROL1_INTE | devpriv->do_bits, CONTROL1);
+ spin_unlock_irqrestore(&dev->spinlock, irq_flags);
+
+ return 0;
+};
+
+static struct comedi_driver driver_das800 = {
+ .driver_name = "das800",
+ .module = THIS_MODULE,
+ .attach = das800_attach,
+ .detach = comedi_legacy_detach,
+ .num_names = ARRAY_SIZE(das800_boards),
+ .board_name = &das800_boards[0].name,
+ .offset = sizeof(struct das800_board),
+};
+module_comedi_driver(driver_das800);
+
+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 00000000000..ad7a5d53b97
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dmm32at.c
@@ -0,0 +1,806 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: dmm32at
+Description: Diamond Systems mm32at driver.
+Devices:
+Author: Perry J. Piplani <perry.j.piplani@nasa.gov>
+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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/* 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
+
+/* 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)
+ }
+};
+
+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;
+
+};
+
+static int dmm32at_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + context);
+ if ((status & DMM32AT_STATUS) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int dmm32at_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n;
+ unsigned int d;
+ unsigned short msb, lsb;
+ unsigned char chan;
+ int range;
+ int ret;
+
+ /* get the channel and range number */
+
+ chan = CR_CHAN(insn->chanspec) & (s->n_chan - 1);
+ range = CR_RANGE(insn->chanspec);
+
+ /* zero scan and fifo control and reset fifo */
+ outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL);
+
+ /* write the ai channel range regs */
+ outb(chan, dev->iobase + DMM32AT_AILOW);
+ outb(chan, dev->iobase + DMM32AT_AIHIGH);
+ /* set the range bits */
+ outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF);
+
+ /* wait for circuit to settle */
+ ret = comedi_timeout(dev, s, insn, dmm32at_ai_status, DMM32AT_AIRBACK);
+ if (ret)
+ return ret;
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outb(0xff, dev->iobase + DMM32AT_CONV);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, dmm32at_ai_status,
+ DMM32AT_AISTAT);
+ if (ret)
+ return ret;
+
+ /* read data */
+ lsb = inb(dev->iobase + DMM32AT_AILSB);
+ msb = inb(dev->iobase + 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_ns_to_timer(unsigned int *ns, int round)
+{
+ /* trivial timer */
+ return *ns;
+}
+
+static int dmm32at_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ int i;
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i) % s->n_chan) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same gain\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int dmm32at_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER /*| TRIG_EXT */);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER /*| TRIG_EXT */);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SCAN_SPEED 1000000 /* in nanoseconds */
+#define MIN_SCAN_SPEED 1000000000 /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SCAN_SPEED);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MIN_SCAN_SPEED);
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ /* should specify multiple external triggers */
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ }
+
+ 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 */
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 9);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0xfffffff0);
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ dmm32at_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ dmm32at_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= dmm32at_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static 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 */
+ outb(0, dev->iobase + DMM32AT_CNTRDIO);
+
+ /* get access to the clock regs */
+ outb(DMM32AT_CLKACC, dev->iobase + DMM32AT_CNTRL);
+
+ /* write the counter 1 control word and low byte to counter */
+ outb(DMM32AT_CLKCT1, dev->iobase + DMM32AT_CLKCT);
+ outb(lo1, dev->iobase + DMM32AT_CLK1);
+
+ /* write the counter 2 control word and low byte then to counter */
+ outb(DMM32AT_CLKCT2, dev->iobase + DMM32AT_CLKCT);
+ outb(lo2, dev->iobase + DMM32AT_CLK2);
+ outb(hi2, dev->iobase + DMM32AT_CLK2);
+
+ /* enable the ai conversion interrupt and the clock to start scans */
+ outb(DMM32AT_ADINT | DMM32AT_CLKSEL, dev->iobase + DMM32AT_INTCLOCK);
+}
+
+static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dmm32at_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int range;
+ unsigned char chanlo, chanhi;
+ int ret;
+
+ 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 */
+ outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL);
+
+ /* set scan enable */
+ outb(DMM32AT_SCANENABLE, dev->iobase + DMM32AT_FIFOCNTRL);
+
+ /* write the ai channel range regs */
+ outb(chanlo, dev->iobase + DMM32AT_AILOW);
+ outb(chanhi, dev->iobase + DMM32AT_AIHIGH);
+
+ /* set the range bits */
+ outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF);
+
+ /* reset the interrupt just in case */
+ outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL);
+
+ 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
+ * we don't have the 'insn' here but it's not needed
+ */
+ ret = comedi_timeout(dev, s, NULL, dmm32at_ai_status, DMM32AT_AIRBACK);
+ if (ret)
+ return ret;
+
+ 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 */
+ outb(DMM32AT_ADINT, dev->iobase + DMM32AT_INTCLOCK);
+ outb(0xff, dev->iobase + DMM32AT_CONV);
+ }
+
+ return 0;
+
+}
+
+static int dmm32at_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dmm32at_private *devpriv = dev->private;
+
+ devpriv->ai_scans_left = 1;
+ return 0;
+}
+
+static irqreturn_t dmm32at_isr(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct dmm32at_private *devpriv = dev->private;
+ unsigned char intstat;
+ unsigned int samp;
+ unsigned short msb, lsb;
+ int i;
+
+ if (!dev->attached) {
+ comedi_error(dev, "spurious interrupt");
+ return IRQ_HANDLED;
+ }
+
+ intstat = inb(dev->iobase + 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 = inb(dev->iobase + DMM32AT_AILSB);
+ msb = inb(dev->iobase + DMM32AT_AIMSB);
+
+ /* invert sign bit to make range unsigned */
+ samp = ((msb ^ 0x0080) << 8) + lsb;
+ comedi_buf_put(s, samp);
+ }
+
+ if (devpriv->ai_scans_left != 0xffffffff) { /* TRIG_COUNT */
+ devpriv->ai_scans_left--;
+ if (devpriv->ai_scans_left == 0) {
+ /* disable further interrupts and clocks */
+ outb(0x0, dev->iobase + DMM32AT_INTCLOCK);
+ /* 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 */
+ outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL);
+ return IRQ_HANDLED;
+}
+
+static int dmm32at_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + DMM32AT_DACSTAT);
+ if ((status & DMM32AT_DACBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int dmm32at_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dmm32at_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned char hi, lo, status;
+ int ret;
+
+ /* 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);
+ /* write the low and high values to the board */
+ outb(lo, dev->iobase + DMM32AT_DACLSB);
+ outb(hi, dev->iobase + DMM32AT_DACMSB);
+
+ /* wait for circuit to settle */
+ ret = comedi_timeout(dev, s, insn, dmm32at_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* dummy read to update trigger the output */
+ status = inb(dev->iobase + DMM32AT_DACMSB);
+
+ }
+
+ /* return the number of samples read/written */
+ return i;
+}
+
+static int dmm32at_ao_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dmm32at_private *devpriv = dev->private;
+ 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 dmm32at_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dmm32at_private *devpriv = dev->private;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /* get access to the DIO regs */
+ outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL);
+
+ /* if either part of dio is set for output */
+ if (((devpriv->dio_config & DMM32AT_DIRCL) == 0) ||
+ ((devpriv->dio_config & DMM32AT_DIRCH) == 0)) {
+ val = (s->state & 0x00ff0000) >> 16;
+ outb(val, dev->iobase + DMM32AT_DIOC);
+ }
+ if ((devpriv->dio_config & DMM32AT_DIRB) == 0) {
+ val = (s->state & 0x0000ff00) >> 8;
+ outb(val, dev->iobase + DMM32AT_DIOB);
+ }
+ if ((devpriv->dio_config & DMM32AT_DIRA) == 0) {
+ val = (s->state & 0x000000ff);
+ outb(val, dev->iobase + DMM32AT_DIOA);
+ }
+ }
+
+ val = inb(dev->iobase + DMM32AT_DIOA);
+ val |= inb(dev->iobase + DMM32AT_DIOB) << 8;
+ val |= inb(dev->iobase + DMM32AT_DIOC) << 16;
+ s->state = val;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dmm32at_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dmm32at_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned char chanbit;
+ int ret;
+
+ if (chan < 8) {
+ mask = 0x0000ff;
+ chanbit = DMM32AT_DIRA;
+ } else if (chan < 16) {
+ mask = 0x00ff00;
+ chanbit = DMM32AT_DIRB;
+ } else if (chan < 20) {
+ mask = 0x0f0000;
+ chanbit = DMM32AT_DIRCL;
+ } else {
+ mask = 0xf00000;
+ chanbit = DMM32AT_DIRCH;
+ }
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (data[0] == INSN_CONFIG_DIO_OUTPUT)
+ devpriv->dio_config &= ~chanbit;
+ else
+ devpriv->dio_config |= chanbit;
+ /* get access to the DIO regs */
+ outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL);
+ /* set the DIO's to the new configuration setting */
+ outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF);
+
+ return insn->n;
+}
+
+static int dmm32at_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct dmm32at_private *devpriv;
+ int ret;
+ struct comedi_subdevice *s;
+ unsigned char aihi, ailo, fifostat, aistat, intstat, airback;
+
+ ret = comedi_request_region(dev, it->options[0], DMM32AT_MEMSIZE);
+ if (ret)
+ return ret;
+
+ /* the following just makes sure the board is there and gets
+ it to a known state */
+
+ /* reset the board */
+ outb(DMM32AT_RESET, dev->iobase + DMM32AT_CNTRL);
+
+ /* allow a millisecond to reset */
+ udelay(1000);
+
+ /* zero scan and fifo control */
+ outb(0x0, dev->iobase + DMM32AT_FIFOCNTRL);
+
+ /* zero interrupt and clock control */
+ outb(0x0, dev->iobase + DMM32AT_INTCLOCK);
+
+ /* write a test channel range, the high 3 bits should drop */
+ outb(0x80, dev->iobase + DMM32AT_AILOW);
+ outb(0xff, dev->iobase + DMM32AT_AIHIGH);
+
+ /* set the range at 10v unipolar */
+ outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AICONF);
+
+ /* should take 10 us to settle, here's a hundred */
+ udelay(100);
+
+ /* read back the values */
+ ailo = inb(dev->iobase + DMM32AT_AILOW);
+ aihi = inb(dev->iobase + DMM32AT_AIHIGH);
+ fifostat = inb(dev->iobase + DMM32AT_FIFOSTAT);
+ aistat = inb(dev->iobase + DMM32AT_AISTAT);
+ intstat = inb(dev->iobase + DMM32AT_INTCLOCK);
+ airback = inb(dev->iobase + DMM32AT_AIRBACK);
+
+ if ((ailo != 0x00) || (aihi != 0x1f) || (fifostat != 0x80) ||
+ (aistat != 0x60 || (intstat != 0x00) || airback != 0x0c)) {
+ dev_err(dev->class_dev, "board detection failed\n");
+ return -EIO;
+ }
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], dmm32at_isr, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* 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 = 32;
+ s->maxdata = 0xffff;
+ s->range_table = &dmm32at_airanges;
+ s->insn_read = dmm32at_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 32;
+ 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 = 4;
+ s->maxdata = 0x0fff;
+ s->range_table = &dmm32at_aoranges;
+ s->insn_write = dmm32at_ao_winsn;
+ s->insn_read = dmm32at_ao_rinsn;
+
+ s = &dev->subdevices[2];
+ /* digital i/o subdevice */
+
+ /* get access to the DIO regs */
+ outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL);
+ /* set the DIO's to the defualt input setting */
+ devpriv->dio_config = DMM32AT_DIRA | DMM32AT_DIRB |
+ DMM32AT_DIRCL | DMM32AT_DIRCH | DMM32AT_DIENABLE;
+ outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF);
+
+ /* set up the subdevice */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ 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;
+
+ return 0;
+}
+
+static struct comedi_driver dmm32at_driver = {
+ .driver_name = "dmm32at",
+ .module = THIS_MODULE,
+ .attach = dmm32at_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dmm32at_driver);
+
+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 00000000000..4263014426f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2801.c
@@ -0,0 +1,654 @@
+/*
+ * 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 <linux/module.h>
+#include "../comedidev.h"
+#include <linux/delay.h>
+
+#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
+
+#if 0
+/* ignore 'defined but not used' warning */
+static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+#endif
+static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+#if 0
+/* ignore 'defined but not used' warning */
+static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+#endif
+static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(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},
+};
+
+struct dt2801_private {
+
+ const struct comedi_lrange *dac_range_types[2];
+ unsigned int ao_readback[2];
+};
+
+/* 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 = 0;
+ int hb = 0;
+ 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;
+ }
+ } 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) {
+ dev_dbg(dev->class_dev,
+ "composite-error in %s, ignoring\n", __func__);
+ }
+ if (!(stat & DT_S_READY))
+ dev_dbg(dev->class_dev, "!ready in %s, ignoring\n", __func__);
+ 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;
+
+ /* 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);
+
+ /* 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)
+ dev_dbg(dev->class_dev, "timeout 1 status=0x%02x\n", stat);
+
+ /* dt2801_readdata(dev,&board_code); */
+
+ 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)
+ dev_dbg(dev->class_dev, "timeout 2 status=0x%02x\n", stat);
+
+ dt2801_readdata(dev, &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;
+}
+
+static int dt2801_error(struct comedi_device *dev, int stat)
+{
+ if (stat < 0) {
+ if (stat == -ETIME)
+ dev_dbg(dev->class_dev, "timeout\n");
+ else
+ dev_dbg(dev->class_dev, "error %d\n", stat);
+ return stat;
+ }
+ dev_dbg(dev->class_dev, "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)
+{
+ struct dt2801_private *devpriv = dev->private;
+
+ 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)
+{
+ struct dt2801_private *devpriv = dev->private;
+
+ 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 = (s == &dev->subdevices[3]) ? 1 : 0;
+ unsigned int val = 0;
+
+ if (comedi_dio_update_state(s, data)) {
+ 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, &val);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dt2801_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xff);
+ if (ret)
+ return ret;
+
+ dt2801_writecmd(dev, s->io_bits ? DT_C_SET_DIGOUT : DT_C_SET_DIGIN);
+ dt2801_writedata(dev, (s == &dev->subdevices[3]) ? 1 : 0);
+
+ return insn->n;
+}
+
+/*
+ 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)
+{
+ const struct dt2801_board *board;
+ struct dt2801_private *devpriv;
+ struct comedi_subdevice *s;
+ int board_code, type;
+ int ret = 0;
+ int n_ai_chans;
+
+ ret = comedi_request_region(dev, it->options[0], DT2801_IOSIZE);
+ if (ret)
+ return ret;
+
+ /* 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;
+ }
+ dev_dbg(dev->class_dev,
+ "unrecognized board code=0x%02x, contact author\n", board_code);
+ type = 0;
+
+havetype:
+ dev->board_ptr = boardtypes + type;
+ board = comedi_board(dev);
+
+ n_ai_chans = probe_number_of_ai_chans(dev);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ goto out;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ dev->board_name = board->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 = board->ad_chan;
+ else
+ s->n_chan = board->ad_chan / 2;
+#endif
+ s->maxdata = (1 << board->adbits) - 1;
+ s->range_table = ai_range_lkup(board->adrangetype, it->options[3]);
+ s->insn_read = dt2801_ai_insn_read;
+
+ s = &dev->subdevices[1];
+ /* ao subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = (1 << board->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 = &dev->subdevices[2];
+ /* 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 = &dev->subdevices[3];
+ /* 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:
+ return ret;
+}
+
+static struct comedi_driver dt2801_driver = {
+ .driver_name = "dt2801",
+ .module = THIS_MODULE,
+ .attach = dt2801_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2801_driver);
+
+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 00000000000..ba7c2ba618e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2811.c
@@ -0,0 +1,490 @@
+/*
+ comedi/drivers/dt2811.c
+ Hardware driver for Data Translation DT2811
+
+ COMEDI - Linux Control and Measurement Device Interface
+ History:
+ Base Version - David A. Schleef <ds@schleef.org>
+ 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.
+ */
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = {
+ 4, {
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625),
+ BIP_RANGE(0.3125)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.05),
+ UNI_RANGE(0.01)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = {
+ 4, {
+ BIP_RANGE(2.5),
+ BIP_RANGE(0.25),
+ BIP_RANGE(0.025),
+ BIP_RANGE(0.005)
+ }
+};
+
+static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05),
+ BIP_RANGE(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;
+};
+
+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];
+};
+
+static const struct comedi_lrange *dac_range_types[] = {
+ &range_bipolar5,
+ &range_bipolar2_5,
+ &range_unipolar5
+};
+
+static int dt2811_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2811_ADCSR);
+ if ((status & DT2811_ADBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+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 ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(chan, dev->iobase + DT2811_ADGCR);
+
+ ret = comedi_timeout(dev, s, insn, dt2811_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inb(dev->iobase + DT2811_ADDATLO);
+ data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8;
+ data[i] &= 0xfff;
+ }
+
+ return i;
+}
+
+static int dt2811_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dt2811_private *devpriv = dev->private;
+ 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)
+{
+ struct dt2811_private *devpriv = dev->private;
+ 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)
+{
+ data[1] = inb(dev->iobase + DT2811_DIO);
+
+ return insn->n;
+}
+
+static int dt2811_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DT2811_DIO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+/*
+ 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; */
+ const struct dt2811_board *board = comedi_board(dev);
+ struct dt2811_private *devpriv;
+ int ret;
+ struct comedi_subdevice *s;
+
+ ret = comedi_request_region(dev, it->options[0], DT2811_SIZE);
+ if (ret)
+ return ret;
+
+#if 0
+ outb(0, dev->iobase + DT2811_ADCSR);
+ udelay(100);
+ i = inb(dev->iobase + DT2811_ADDATLO);
+ i = inb(dev->iobase + DT2811_ADDATHI);
+#endif
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ 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 = board->bip_5;
+ break;
+ case 1:
+ s->range_table = board->bip_2_5;
+ break;
+ case 2:
+ s->range_table = 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 const struct dt2811_board boardtypes[] = {
+ {
+ .name = "dt2811-pgh",
+ .bip_5 = &range_dt2811_pgh_ai_5_bipolar,
+ .bip_2_5 = &range_dt2811_pgh_ai_2_5_bipolar,
+ .unip_5 = &range_dt2811_pgh_ai_5_unipolar,
+ }, {
+ .name = "dt2811-pgl",
+ .bip_5 = &range_dt2811_pgl_ai_5_bipolar,
+ .bip_2_5 = &range_dt2811_pgl_ai_2_5_bipolar,
+ .unip_5 = &range_dt2811_pgl_ai_5_unipolar,
+ },
+};
+
+static struct comedi_driver dt2811_driver = {
+ .driver_name = "dt2811",
+ .module = THIS_MODULE,
+ .attach = dt2811_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct dt2811_board),
+};
+module_comedi_driver(dt2811_driver);
+
+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 00000000000..904c9f0e4af
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2814.c
@@ -0,0 +1,304 @@
+/*
+ comedi/drivers/dt2814.c
+ Hardware driver for Data Translation DT2814
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#include "comedi_fc.h"
+
+#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
+
+struct dt2814_private {
+
+ int ntrig;
+ int curadchan;
+};
+
+#define DT2814_TIMEOUT 10
+#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
+
+static int dt2814_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2814_CSR);
+ if (status & DT2814_FINISH)
+ return 0;
+ return -EBUSY;
+}
+
+static int dt2814_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n, hi, lo;
+ int chan;
+ int ret;
+
+ for (n = 0; n < insn->n; n++) {
+ chan = CR_CHAN(insn->chanspec);
+
+ outb(chan, dev->iobase + DT2814_CSR);
+
+ ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ 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;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ DT2814_MAX_SPEED);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 2);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->scan_begin_arg;
+ dt2814_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt2814_private *devpriv = dev->private;
+ 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 irqreturn_t dt2814_interrupt(int irq, void *d)
+{
+ int lo, hi;
+ struct comedi_device *dev = d;
+ struct dt2814_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int data;
+
+ if (!dev->attached) {
+ comedi_error(dev, "spurious interrupt");
+ return IRQ_HANDLED;
+ }
+
+ 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;
+}
+
+static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct dt2814_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0], DT2814_SIZE);
+ if (ret)
+ return ret;
+
+ outb(0, dev->iobase + DT2814_CSR);
+ udelay(100);
+ if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
+ dev_err(dev->class_dev, "reset error (fatal)\n");
+ return -EIO;
+ }
+ i = inb(dev->iobase + DT2814_DATA);
+ i = inb(dev->iobase + DT2814_DATA);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], dt2814_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16; /* XXX */
+ s->insn_read = dt2814_ai_insn_read;
+ s->maxdata = 0xfff;
+ s->range_table = &range_unknown; /* XXX */
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmd = dt2814_ai_cmd;
+ s->do_cmdtest = dt2814_ai_cmdtest;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver dt2814_driver = {
+ .driver_name = "dt2814",
+ .module = THIS_MODULE,
+ .attach = dt2814_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2814_driver);
+
+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 00000000000..b9ac4ed8bab
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2815.c
@@ -0,0 +1,225 @@
+/*
+ comedi/drivers/dt2815.c
+ Hardware driver for Data Translation DT2815
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#define DT2815_SIZE 2
+
+#define DT2815_DATA 0
+#define DT2815_STATUS 1
+
+struct dt2815_private {
+
+ const struct comedi_lrange *range_type_list[8];
+ unsigned int ao_readback[8];
+};
+
+static int dt2815_ao_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + DT2815_STATUS);
+ if (status == context)
+ return 0;
+ return -EBUSY;
+}
+
+static int dt2815_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dt2815_private *devpriv = dev->private;
+ 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)
+{
+ struct dt2815_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+ unsigned int lo, hi;
+ int ret;
+
+ for (i = 0; i < insn->n; i++) {
+ lo = ((data[i] & 0x0f) << 4) | (chan << 1) | 0x01;
+ hi = (data[i] & 0xff0) >> 4;
+
+ ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x00);
+ if (ret)
+ return ret;
+
+ outb(lo, dev->iobase + DT2815_DATA);
+
+ ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x10);
+ if (ret)
+ return ret;
+
+ 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 dt2815_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ const struct comedi_lrange *current_range_type, *voltage_range_type;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], DT2815_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ s = &dev->subdevices[0];
+ /* 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_4_20mA : &range_0_32mA;
+ 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);
+ dev_dbg(dev->class_dev, "program: 0x%x (@t=%d)\n",
+ program, i);
+ break;
+ } else if (status != 0x00) {
+ dev_dbg(dev->class_dev,
+ "unexpected status 0x%x (@t=%d)\n",
+ status, i);
+ if (status & 0x60)
+ outb(0x00, dev->iobase + DT2815_STATUS);
+ }
+ }
+
+ return 0;
+}
+
+static struct comedi_driver dt2815_driver = {
+ .driver_name = "dt2815",
+ .module = THIS_MODULE,
+ .attach = dt2815_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2815_driver);
+
+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 00000000000..bf589936e54
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt2817.c
@@ -0,0 +1,151 @@
+/*
+ comedi/drivers/dt2817.c
+ Hardware driver for Data Translation DT2817
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#define DT2817_SIZE 5
+
+#define DT2817_CR 0
+#define DT2817_DATA 1
+
+static int dt2817_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int oe = 0;
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ 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 insn->n;
+}
+
+static int dt2817_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long iobase = dev->iobase + DT2817_DATA;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x000000ff)
+ outb(s->state & 0xff, iobase + 0);
+ if (mask & 0x0000ff00)
+ outb((s->state >> 8) & 0xff, iobase + 1);
+ if (mask & 0x00ff0000)
+ outb((s->state >> 16) & 0xff, iobase + 2);
+ if (mask & 0xff000000)
+ outb((s->state >> 24) & 0xff, iobase + 3);
+ }
+
+ val = inb(iobase + 0);
+ val |= (inb(iobase + 1) << 8);
+ val |= (inb(iobase + 2) << 16);
+ val |= (inb(iobase + 3) << 24);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dt2817_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ int ret;
+ struct comedi_subdevice *s;
+
+ ret = comedi_request_region(dev, it->options[0], DT2817_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ 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);
+
+ return 0;
+}
+
+static struct comedi_driver dt2817_driver = {
+ .driver_name = "dt2817",
+ .module = THIS_MODULE,
+ .attach = dt2817_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(dt2817_driver);
+
+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 00000000000..c2a66dcf99f
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt282x.c
@@ -0,0 +1,1384 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include <asm/dma.h>
+
+#include "comedi_fc.h"
+
+#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, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_lo_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_5_bipolar = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ BIP_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_5_unipolar = {
+ 4, {
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ UNI_RANGE(0.625)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_hi_bipolar = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+static const struct comedi_lrange range_dt282x_ai_hi_unipolar = {
+ 4, {
+ UNI_RANGE(10),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1),
+ UNI_RANGE(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;
+};
+
+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];
+
+ unsigned 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;
+ unsigned 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;
+};
+
+/*
+ * Some useless abstractions
+ */
+#define chan_to_DAC(a) ((a)&1)
+
+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, unsigned short *buf,
+ unsigned int nbytes)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int i;
+ unsigned short mask = (1 << board->adbits) - 1;
+ unsigned short sign = 1 << (board->adbits - 1);
+ int n;
+
+ if (devpriv->ad_2scomp)
+ sign = 1 << (board->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)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ void *ptr;
+ int size;
+ int i;
+
+ outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR);
+
+ if (!s->async->prealloc_buf) {
+ dev_err(dev->class_dev, "no buffer in %s\n", __func__);
+ 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) {
+ dev_err(dev->class_dev, "AO underrun\n");
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ return;
+ }
+ prep_ao_dma(dev, i, size);
+ return;
+}
+
+static void dt282x_ai_dma_interrupt(struct comedi_device *dev)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ void *ptr;
+ int size;
+ int i;
+ int ret;
+
+ outw(devpriv->supcsr | DT2821_CLRDMADNE, dev->iobase + DT2821_SUPCSR);
+
+ if (!s->async->prealloc_buf) {
+ dev_err(dev->class_dev, "no buffer in %s\n", __func__);
+ 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) {
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ return;
+ }
+ devpriv->nread -= size / 2;
+
+ if (devpriv->nread < 0) {
+ dev_info(dev->class_dev, "nread off by one\n");
+ devpriv->nread = 0;
+ }
+ if (!devpriv->nread) {
+ 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);
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR);
+ }
+#endif
+ /* restart the channel */
+ prep_ai_dma(dev, i, 0);
+}
+
+static int prep_ai_dma(struct comedi_device *dev, int dma_index, int n)
+{
+ struct dt282x_private *devpriv = dev->private;
+ 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)
+{
+ struct dt282x_private *devpriv = dev->private;
+ 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 dt282x_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_subdevice *s_ao = dev->write_subdev;
+ unsigned int supcsr, adcsr, dacsr;
+ int handled = 0;
+
+ if (!dev->attached) {
+ comedi_error(dev, "spurious interrupt");
+ return IRQ_HANDLED;
+ }
+
+ 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");
+ s->async->events |= COMEDI_CB_ERROR;
+ }
+ handled = 1;
+ }
+ if (dacsr & DT2821_DAERR) {
+ comedi_error(dev, "D/A error");
+ s_ao->async->events |= COMEDI_CB_ERROR;
+ handled = 1;
+ }
+#if 0
+ if (adcsr & DT2821_ADDONE) {
+ int ret;
+ unsigned short data;
+
+ data = inw(dev->iobase + DT2821_ADDAT);
+ data &= (1 << board->adbits) - 1;
+
+ if (devpriv->ad_2scomp)
+ data ^= 1 << (board->adbits - 1);
+ ret = comedi_buf_put(s, 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)
+ outw(devpriv->supcsr | DT2821_STRIG,
+ dev->iobase + DT2821_SUPCSR);
+ }
+ handled = 1;
+ }
+#endif
+ cfc_handle_events(dev, s);
+ cfc_handle_events(dev, s_ao);
+
+ return IRQ_RETVAL(handled);
+}
+
+static void dt282x_load_changain(struct comedi_device *dev, int n,
+ unsigned int *chanlist)
+{
+ struct dt282x_private *devpriv = dev->private;
+ 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]);
+ outw(devpriv->adcsr | (range << 4) | chan,
+ dev->iobase + DT2821_ADCSR);
+ }
+ outw(n - 1, dev->iobase + DT2821_CHANCSR);
+}
+
+static int dt282x_ai_timeout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + DT2821_ADCSR);
+ switch (context) {
+ case DT2821_MUXBUSY:
+ if ((status & DT2821_MUXBUSY) == 0)
+ return 0;
+ break;
+ case DT2821_ADDONE:
+ if (status & DT2821_ADDONE)
+ return 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return -EBUSY;
+}
+
+/*
+ * 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)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ struct dt282x_private *devpriv = dev->private;
+ int ret;
+ int i;
+
+ /* XXX should we really be enabling the ad clock here? */
+ devpriv->adcsr = DT2821_ADCLK;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR);
+
+ dt282x_load_changain(dev, 1, &insn->chanspec);
+
+ outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR);
+ ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout, DT2821_MUXBUSY);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ outw(devpriv->supcsr | DT2821_STRIG,
+ dev->iobase + DT2821_SUPCSR);
+
+ ret = comedi_timeout(dev, s, insn, dt282x_ai_timeout,
+ DT2821_ADDONE);
+ if (ret)
+ return ret;
+
+ data[i] =
+ inw(dev->iobase +
+ DT2821_ADDAT) & ((1 << board->adbits) - 1);
+ if (devpriv->ad_2scomp)
+ data[i] ^= (1 << (board->adbits - 1));
+ }
+
+ return i;
+}
+
+static int dt282x_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 4000);
+
+#define SLOWEST_TIMER (250*(1<<15)*255)
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER);
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, board->ai_speed);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else { /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->convert_arg;
+ dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int timer;
+ int ret;
+
+ 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 < board->ai_speed)
+ cmd->convert_arg = 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;
+ }
+ outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT,
+ dev->iobase + DT2821_SUPCSR);
+
+ 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;
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR);
+ }
+
+ devpriv->adcsr = 0;
+
+ dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist);
+
+ devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR);
+
+ outw(devpriv->supcsr | DT2821_PRLD, dev->iobase + DT2821_SUPCSR);
+ ret = comedi_timeout(dev, s, NULL, dt282x_ai_timeout, DT2821_MUXBUSY);
+ if (ret)
+ return ret;
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ outw(devpriv->supcsr | DT2821_STRIG,
+ dev->iobase + DT2821_SUPCSR);
+ } else {
+ devpriv->supcsr |= DT2821_XTRIG;
+ outw(devpriv->supcsr, dev->iobase + DT2821_SUPCSR);
+ }
+
+ return 0;
+}
+
+static void dt282x_disable_dma(struct comedi_device *dev)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ 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)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ dt282x_disable_dma(dev);
+
+ devpriv->adcsr = 0;
+ outw(devpriv->adcsr, dev->iobase + DT2821_ADCSR);
+
+ devpriv->supcsr = 0;
+ outw(devpriv->supcsr | DT2821_ADCINIT, dev->iobase + DT2821_SUPCSR);
+
+ 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)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ 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)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ struct dt282x_private *devpriv = dev->private;
+ unsigned short d;
+ unsigned int chan;
+
+ chan = CR_CHAN(insn->chanspec);
+ d = data[0];
+ d &= (1 << board->dabits) - 1;
+ devpriv->ao[chan] = d;
+
+ devpriv->dacsr |= DT2821_SSEL;
+
+ if (chan) {
+ /* select channel */
+ devpriv->dacsr |= DT2821_YSEL;
+ if (devpriv->da0_2scomp)
+ d ^= (1 << (board->dabits - 1));
+ } else {
+ devpriv->dacsr &= ~DT2821_YSEL;
+ if (devpriv->da1_2scomp)
+ d ^= (1 << (board->dabits - 1));
+ }
+
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
+
+ outw(d, dev->iobase + DT2821_DADAT);
+
+ outw(devpriv->supcsr | DT2821_DACON, dev->iobase + DT2821_SUPCSR);
+
+ return 1;
+}
+
+static int dt282x_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 5000);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else { /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ arg = cmd->scan_begin_arg;
+ dt282x_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+
+ if (err)
+ return 4;
+
+ return 0;
+
+}
+
+static int dt282x_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct dt282x_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int size;
+
+ if (trig_num != cmd->start_src)
+ return -EINVAL;
+
+ size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf,
+ devpriv->dma_maxsize);
+ if (size == 0) {
+ dev_err(dev->class_dev, "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) {
+ dev_err(dev->class_dev, "AO underrun\n");
+ return -EPIPE;
+ }
+ prep_ao_dma(dev, 1, size);
+
+ outw(devpriv->supcsr | DT2821_STRIG, dev->iobase + DT2821_SUPCSR);
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+ 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;
+ outw(devpriv->supcsr | DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT,
+ dev->iobase + DT2821_SUPCSR);
+
+ 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;
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
+
+ s->async->inttrig = dt282x_ao_inttrig;
+
+ return 0;
+}
+
+static int dt282x_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ dt282x_disable_dma(dev);
+
+ devpriv->dacsr = 0;
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
+
+ devpriv->supcsr = 0;
+ outw(devpriv->supcsr | DT2821_DACINIT, dev->iobase + DT2821_SUPCSR);
+
+ return 0;
+}
+
+static int dt282x_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + DT2821_DIODAT);
+
+ data[1] = inw(dev->iobase + DT2821_DIODAT);
+
+ return insn->n;
+}
+
+static int dt282x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt282x_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x00ff;
+ else
+ mask = 0xff00;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ devpriv->dacsr &= ~(DT2821_LBOE | DT2821_HBOE);
+ if (s->io_bits & 0x00ff)
+ devpriv->dacsr |= DT2821_LBOE;
+ if (s->io_bits & 0xff00)
+ devpriv->dacsr |= DT2821_HBOE;
+
+ outw(devpriv->dacsr, dev->iobase + DT2821_DACSR);
+
+ return insn->n;
+}
+
+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 */
+};
+
+static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2)
+{
+ struct dt282x_private *devpriv = dev->private;
+ int ret;
+
+ devpriv->usedma = 0;
+
+ if (!dma1 && !dma2)
+ 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)
+ return -ENOMEM;
+
+ devpriv->usedma = 1;
+
+ return 0;
+}
+
+/*
+ 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)
+{
+ const struct dt282x_board *board = comedi_board(dev);
+ struct dt282x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0], DT2821_SIZE);
+ if (ret)
+ return ret;
+
+ outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR);
+ i = inw(dev->iobase + DT2821_ADCSR);
+
+ 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)) {
+ dev_err(dev->class_dev, "board not found\n");
+ return -EIO;
+ }
+ /* should do board test */
+
+ if (it->options[opt_irq] > 0) {
+ ret = request_irq(it->options[opt_irq], dt282x_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[opt_irq];
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ if (dev->irq) {
+ ret = dt282x_grab_dma(dev, it->options[opt_dma1],
+ it->options[opt_dma2]);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE |
+ ((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON);
+ s->n_chan =
+ (it->options[opt_diff]) ? board->adchan_di : board->adchan_se;
+ s->insn_read = dt282x_ai_insn_read;
+ s->maxdata = (1 << board->adbits) - 1;
+ s->range_table =
+ opt_ai_range_lkup(board->ispgl, it->options[opt_ai_range]);
+ devpriv->ad_2scomp = it->options[opt_ai_twos];
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 16;
+ s->do_cmdtest = dt282x_ai_cmdtest;
+ s->do_cmd = dt282x_ai_cmd;
+ s->cancel = dt282x_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+
+ s->n_chan = board->dachan;
+ if (s->n_chan) {
+ /* ao subsystem */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->insn_read = dt282x_ao_insn_read;
+ s->insn_write = dt282x_ao_insn_write;
+ s->maxdata = (1 << board->dabits) - 1;
+ 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];
+ if (dev->irq) {
+ dev->write_subdev = s;
+ s->subdev_flags |= SDF_CMD_WRITE;
+ s->len_chanlist = 2;
+ s->do_cmdtest = dt282x_ao_cmdtest;
+ s->do_cmd = dt282x_ao_cmd;
+ s->cancel = dt282x_ao_cancel;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* 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;
+
+ return 0;
+}
+
+static void dt282x_detach(struct comedi_device *dev)
+{
+ struct dt282x_private *devpriv = dev->private;
+
+ 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);
+ }
+ comedi_legacy_detach(dev);
+}
+
+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,
+ },
+};
+
+static struct comedi_driver dt282x_driver = {
+ .driver_name = "dt282x",
+ .module = THIS_MODULE,
+ .attach = dt282x_attach,
+ .detach = dt282x_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct dt282x_board),
+};
+module_comedi_driver(dt282x_driver);
+
+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 00000000000..4ab4de00592
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt3000.c
@@ -0,0 +1,814 @@
+/*
+ comedi/drivers/dt3000.c
+ Data Translation DT3000 series driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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: not applicable, uses PCI auto config
+
+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.
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+static const struct comedi_lrange range_dt3000_ai = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+static const struct comedi_lrange range_dt3000_ai_pgl = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ BIP_RANGE(0.02)
+ }
+};
+
+enum dt3k_boardid {
+ BOARD_DT3001,
+ BOARD_DT3001_PGL,
+ BOARD_DT3002,
+ BOARD_DT3003,
+ BOARD_DT3003_PGL,
+ BOARD_DT3004,
+ BOARD_DT3005,
+};
+
+struct dt3k_boardtype {
+ const char *name;
+ int adchan;
+ int adbits;
+ int ai_speed;
+ const struct comedi_lrange *adrange;
+ int dachan;
+ int dabits;
+};
+
+static const struct dt3k_boardtype dt3k_boardtypes[] = {
+ [BOARD_DT3001] = {
+ .name = "dt3001",
+ .adchan = 16,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3001_PGL] = {
+ .name = "dt3001-pgl",
+ .adchan = 16,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai_pgl,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3002] = {
+ .name = "dt3002",
+ .adchan = 32,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ },
+ [BOARD_DT3003] = {
+ .name = "dt3003",
+ .adchan = 64,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3003_PGL] = {
+ .name = "dt3003-pgl",
+ .adchan = 64,
+ .adbits = 12,
+ .adrange = &range_dt3000_ai_pgl,
+ .ai_speed = 3000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3004] = {
+ .name = "dt3004",
+ .adchan = 16,
+ .adbits = 16,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 10000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+ [BOARD_DT3005] = {
+ .name = "dt3005", /* a.k.a. 3004-200 */
+ .adchan = 16,
+ .adbits = 16,
+ .adrange = &range_dt3000_ai,
+ .ai_speed = 5000,
+ .dachan = 2,
+ .dabits = 12,
+ },
+};
+
+/* 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 {
+ void __iomem *io_addr;
+ unsigned int lock;
+ unsigned int ao_readback[2];
+ unsigned int ai_front;
+ unsigned int ai_rear;
+};
+
+#define TIMEOUT 100
+
+static void dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd)
+{
+ struct dt3k_private *devpriv = dev->private;
+ 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)
+ dev_dbg(dev->class_dev, "%s: timeout/error status=0x%04x\n",
+ __func__, status);
+}
+
+static unsigned int dt3k_readsingle(struct comedi_device *dev,
+ unsigned int subsys, unsigned int chan,
+ unsigned int gain)
+{
+ struct dt3k_private *devpriv = dev->private;
+
+ 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)
+{
+ struct dt3k_private *devpriv = dev->private;
+
+ 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 void dt3k_ai_empty_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct dt3k_private *devpriv = dev->private;
+ int front;
+ int rear;
+ int count;
+ int i;
+ unsigned short data;
+
+ front = readw(devpriv->io_addr + DPR_AD_Buf_Front);
+ count = front - devpriv->ai_front;
+ if (count < 0)
+ count += AI_FIFO_DEPTH;
+
+ rear = devpriv->ai_rear;
+
+ for (i = 0; i < count; i++) {
+ data = readw(devpriv->io_addr + DPR_ADC_buffer + rear);
+ comedi_buf_put(s, 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_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt3k_private *devpriv = dev->private;
+
+ writew(SUBS_AI, devpriv->io_addr + DPR_SubSys);
+ dt3k_send_cmd(dev, CMD_STOP);
+
+ writew(0, devpriv->io_addr + DPR_Int_Mask);
+
+ return 0;
+}
+
+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 dt3k_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ status = readw(devpriv->io_addr + DPR_Intr_Flag);
+
+ 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)
+ s->async->events |= COMEDI_CB_EOA;
+
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+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_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct dt3k_boardtype *this_board = comedi_board(dev);
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ this_board->ai_speed);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ 100 * 16 * 65535);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ this_board->ai_speed);
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg,
+ 50 * 16 * 65535);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ dt3k_ns_to_timer(100, &arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ dt3k_ns_to_timer(50, &arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int dt3k_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct dt3k_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int i;
+ unsigned int chan, range, aref;
+ unsigned int divider;
+ unsigned int tscandiv;
+ unsigned int mode;
+
+ 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));
+
+ 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));
+ writew((divider & 0xffff), devpriv->io_addr + DPR_Params(2));
+ }
+
+ 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));
+ writew((tscandiv & 0xffff), devpriv->io_addr + DPR_Params(4));
+ }
+
+ mode = DT3000_AD_RETRIG_INTERNAL | 0 | 0;
+ writew(mode, devpriv->io_addr + DPR_Params(5));
+ writew(aref == AREF_DIFF, devpriv->io_addr + DPR_Params(6));
+
+ writew(AI_FIFO_DEPTH / 2, devpriv->io_addr + DPR_Params(7));
+
+ writew(SUBS_AI, devpriv->io_addr + DPR_SubSys);
+ 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);
+ dt3k_send_cmd(dev, CMD_START);
+
+ 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)
+{
+ struct dt3k_private *devpriv = dev->private;
+ 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)
+{
+ struct dt3k_private *devpriv = dev->private;
+ 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)
+{
+ struct dt3k_private *devpriv = dev->private;
+
+ /* 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)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ dt3k_dio_config(dev, (s->io_bits & 0x01) | ((s->io_bits & 0x10) >> 3));
+
+ 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 (comedi_dio_update_state(s, data))
+ dt3k_writesingle(dev, SUBS_DOUT, 0, s->state);
+
+ data[1] = dt3k_readsingle(dev, SUBS_DIN, 0, 0);
+
+ return insn->n;
+}
+
+static int dt3k_mem_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dt3k_private *devpriv = dev->private;
+ 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 dt3000_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct dt3k_boardtype *this_board = NULL;
+ struct dt3k_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret = 0;
+
+ if (context < ARRAY_SIZE(dt3k_boardtypes))
+ this_board = &dt3k_boardtypes[context];
+ if (!this_board)
+ return -ENODEV;
+ dev->board_ptr = this_board;
+ dev->board_name = this_board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret < 0)
+ return ret;
+
+ devpriv->io_addr = pci_ioremap_bar(pcidev, 0);
+ if (!devpriv->io_addr)
+ return -ENOMEM;
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, dt3k_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = this_board->adchan;
+ s->insn_read = dt3k_ai_insn;
+ s->maxdata = (1 << this_board->adbits) - 1;
+ s->range_table = &range_dt3000_ai; /* XXX */
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 512;
+ s->do_cmd = dt3k_ai_cmd;
+ s->do_cmdtest = dt3k_ai_cmdtest;
+ s->cancel = dt3k_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* 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 = &dev->subdevices[2];
+ /* 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 = &dev->subdevices[3];
+ /* 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 = &dev->subdevices[4];
+ /* proc subsystem */
+ s->type = COMEDI_SUBD_PROC;
+#endif
+
+ return 0;
+}
+
+static void dt3000_detach(struct comedi_device *dev)
+{
+ struct dt3k_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->io_addr)
+ iounmap(devpriv->io_addr);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver dt3000_driver = {
+ .driver_name = "dt3000",
+ .module = THIS_MODULE,
+ .auto_attach = dt3000_auto_attach,
+ .detach = dt3000_detach,
+};
+
+static int dt3000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dt3000_driver, id->driver_data);
+}
+
+static const struct pci_device_id dt3000_pci_table[] = {
+ { PCI_VDEVICE(DT, 0x0022), BOARD_DT3001 },
+ { PCI_VDEVICE(DT, 0x0023), BOARD_DT3002 },
+ { PCI_VDEVICE(DT, 0x0024), BOARD_DT3003 },
+ { PCI_VDEVICE(DT, 0x0025), BOARD_DT3004 },
+ { PCI_VDEVICE(DT, 0x0026), BOARD_DT3005 },
+ { PCI_VDEVICE(DT, 0x0027), BOARD_DT3001_PGL },
+ { PCI_VDEVICE(DT, 0x0028), BOARD_DT3003_PGL },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, dt3000_pci_table);
+
+static struct pci_driver dt3000_pci_driver = {
+ .name = "dt3000",
+ .id_table = dt3000_pci_table,
+ .probe = dt3000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dt3000_driver, dt3000_pci_driver);
+
+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 00000000000..b3aeb6fb2ad
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dt9812.c
@@ -0,0 +1,880 @@
+/*
+ * comedi/drivers/dt9812.c
+ * COMEDI driver for DataTranslation DT9812 USB module
+ *
+ * Copyright (C) 2005 Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * 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.
+ */
+
+/*
+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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#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
+
+/* usb_bulk_msg() timout in milliseconds */
+#define DT9812_USB_TIMEOUT 1000
+
+/*
+ * 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 */
+};
+
+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 {
+ __le16 numbytes;
+ __le16 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 {
+ __le32 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;
+};
+
+struct dt9812_private {
+ struct semaphore sem;
+ struct {
+ __u8 addr;
+ size_t size;
+ } cmd_wr, cmd_rd;
+ u16 device;
+ u16 ao_shadow[2];
+};
+
+static int dt9812_read_info(struct comedi_device *dev,
+ int offset, void *buf, size_t buf_size)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int count, ret;
+
+ 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!! */
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+ buf, buf_size, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_read_multiple_registers(struct comedi_device *dev,
+ int reg_count, u8 *address,
+ u8 *value)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count, ret;
+
+ 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!! */
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+ if (ret)
+ return ret;
+
+ return usb_bulk_msg(usb, usb_rcvbulkpipe(usb, devpriv->cmd_rd.addr),
+ value, reg_count, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_write_multiple_registers(struct comedi_device *dev,
+ int reg_count, u8 *address,
+ u8 *value)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count;
+
+ 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!! */
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_rmw_multiple_registers(struct comedi_device *dev,
+ int reg_count,
+ struct dt9812_rmw_byte *rmw)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_usb_cmd cmd;
+ int i, count;
+
+ 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!! */
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, devpriv->cmd_wr.addr),
+ &cmd, 32, &count, DT9812_USB_TIMEOUT);
+}
+
+static int dt9812_digital_in(struct comedi_device *dev, u8 *bits)
+{
+ struct dt9812_private *devpriv = dev->private;
+ u8 reg[2] = { F020_SFR_P3, F020_SFR_P1 };
+ u8 value[2];
+ int ret;
+
+ down(&devpriv->sem);
+ ret = dt9812_read_multiple_registers(dev, 2, reg, value);
+ if (ret == 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);
+ }
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_digital_out(struct comedi_device *dev, u8 bits)
+{
+ struct dt9812_private *devpriv = dev->private;
+ u8 reg[1] = { F020_SFR_P2 };
+ u8 value[1] = { bits };
+ int ret;
+
+ down(&devpriv->sem);
+ ret = dt9812_write_multiple_registers(dev, 1, reg, value);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void dt9812_configure_mux(struct comedi_device *dev,
+ struct dt9812_rmw_byte *rmw, int channel)
+{
+ struct dt9812_private *devpriv = dev->private;
+
+ if (devpriv->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 comedi_device *dev,
+ struct dt9812_rmw_byte *rmw,
+ enum dt9812_gain gain)
+{
+ struct dt9812_private *devpriv = dev->private;
+
+ /* In the DT9812/10V, there is an external gain of 0.5 */
+ if (devpriv->device == DT9812_DEVID_DT9812_10)
+ 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;
+ default:
+ /* this should never happen, just use a gain of 1 */
+ 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;
+ }
+}
+
+static int dt9812_analog_in(struct comedi_device *dev,
+ int channel, u16 *value, enum dt9812_gain gain)
+{
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_rmw_byte rmw[3];
+ u8 reg[3] = {
+ F020_SFR_ADC0CN,
+ F020_SFR_ADC0H,
+ F020_SFR_ADC0L
+ };
+ u8 val[3];
+ int ret;
+
+ down(&devpriv->sem);
+
+ /* 1 select the gain */
+ dt9812_configure_gain(dev, &rmw[0], gain);
+
+ /* 2 set the MUX to select the channel */
+ dt9812_configure_mux(dev, &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;
+
+ ret = dt9812_rmw_multiple_registers(dev, 3, rmw);
+ if (ret)
+ goto exit;
+
+ /* read the status and ADC */
+ ret = dt9812_read_multiple_registers(dev, 3, reg, val);
+ if (ret)
+ 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 (devpriv->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(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_analog_out(struct comedi_device *dev, int channel, u16 value)
+{
+ struct dt9812_private *devpriv = dev->private;
+ struct dt9812_rmw_byte rmw[3];
+ int ret;
+
+ down(&devpriv->sem);
+
+ 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;
+ }
+ ret = dt9812_rmw_multiple_registers(dev, 3, rmw);
+ devpriv->ao_shadow[channel] = value;
+
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int dt9812_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ u8 bits = 0;
+ int ret;
+
+ ret = dt9812_digital_in(dev, &bits);
+ if (ret)
+ return ret;
+
+ data[1] = bits;
+
+ return insn->n;
+}
+
+static int dt9812_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ dt9812_digital_out(dev, s->state);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int dt9812_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ u16 val = 0;
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = dt9812_analog_in(dev, chan, &val, DT9812_GAIN_1);
+ if (ret)
+ return ret;
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int dt9812_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dt9812_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ down(&devpriv->sem);
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_shadow[chan];
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int dt9812_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);
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = dt9812_analog_out(dev, chan, data[i]);
+ if (ret)
+ return ret;
+ }
+
+ return insn->n;
+}
+
+static int dt9812_find_endpoints(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_host_interface *host = intf->cur_altsetting;
+ struct dt9812_private *devpriv = dev->private;
+ struct usb_endpoint_descriptor *ep;
+ int i;
+
+ if (host->desc.bNumEndpoints != 5) {
+ dev_err(dev->class_dev, "Wrong number of endpoints\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < host->desc.bNumEndpoints; ++i) {
+ int dir = -1;
+ ep = &host->endpoint[i].desc;
+ switch (i) {
+ case 0:
+ /* unused message pipe */
+ dir = USB_DIR_IN;
+ break;
+ case 1:
+ dir = USB_DIR_OUT;
+ devpriv->cmd_wr.addr = ep->bEndpointAddress;
+ devpriv->cmd_wr.size = le16_to_cpu(ep->wMaxPacketSize);
+ break;
+ case 2:
+ dir = USB_DIR_IN;
+ devpriv->cmd_rd.addr = ep->bEndpointAddress;
+ devpriv->cmd_rd.size = le16_to_cpu(ep->wMaxPacketSize);
+ break;
+ case 3:
+ /* unused write stream */
+ dir = USB_DIR_OUT;
+ break;
+ case 4:
+ /* unused read stream */
+ dir = USB_DIR_IN;
+ break;
+ }
+ if ((ep->bEndpointAddress & USB_DIR_IN) != dir) {
+ dev_err(dev->class_dev,
+ "Endpoint has wrong direction\n");
+ return -ENODEV;
+ }
+ }
+ return 0;
+}
+
+static int dt9812_reset_device(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct dt9812_private *devpriv = dev->private;
+ u32 serial;
+ u16 vendor;
+ u16 product;
+ u8 tmp8;
+ __le16 tmp16;
+ __le32 tmp32;
+ int ret;
+ int i;
+
+ ret = dt9812_read_info(dev, 0, &tmp8, sizeof(tmp8));
+ if (ret) {
+ /*
+ * Seems like a configuration reset is necessary if driver is
+ * reloaded while device is attached
+ */
+ usb_reset_configuration(usb);
+ for (i = 0; i < 10; i++) {
+ ret = dt9812_read_info(dev, 1, &tmp8, sizeof(tmp8));
+ if (ret == 0)
+ break;
+ }
+ if (ret) {
+ dev_err(dev->class_dev,
+ "unable to reset configuration\n");
+ return ret;
+ }
+ }
+
+ ret = dt9812_read_info(dev, 1, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read vendor id\n");
+ return ret;
+ }
+ vendor = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 3, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read product id\n");
+ return ret;
+ }
+ product = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 5, &tmp16, sizeof(tmp16));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read device id\n");
+ return ret;
+ }
+ devpriv->device = le16_to_cpu(tmp16);
+
+ ret = dt9812_read_info(dev, 7, &tmp32, sizeof(tmp32));
+ if (ret) {
+ dev_err(dev->class_dev, "failed to read serial number\n");
+ return ret;
+ }
+ serial = le32_to_cpu(tmp32);
+
+ /* let the user know what node this device is now attached to */
+ dev_info(dev->class_dev, "USB DT9812 (%4.4x.%4.4x.%4.4x) #0x%8.8x\n",
+ vendor, product, devpriv->device, serial);
+
+ if (devpriv->device != DT9812_DEVID_DT9812_10 &&
+ devpriv->device != DT9812_DEVID_DT9812_2PT5) {
+ dev_err(dev->class_dev, "Unsupported device!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dt9812_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct dt9812_private *devpriv;
+ struct comedi_subdevice *s;
+ bool is_unipolar;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+ usb_set_intfdata(intf, devpriv);
+
+ ret = dt9812_find_endpoints(dev);
+ if (ret)
+ return ret;
+
+ ret = dt9812_reset_device(dev);
+ if (ret)
+ return ret;
+
+ is_unipolar = (devpriv->device == DT9812_DEVID_DT9812_2PT5);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt9812_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dt9812_do_insn_bits;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10;
+ s->insn_read = dt9812_ai_insn_read;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = is_unipolar ? &range_unipolar2_5 : &range_bipolar10;
+ s->insn_write = dt9812_ao_insn_write;
+ s->insn_read = dt9812_ao_insn_read;
+
+ devpriv->ao_shadow[0] = is_unipolar ? 0x0000 : 0x0800;
+ devpriv->ao_shadow[1] = is_unipolar ? 0x0000 : 0x0800;
+
+ return 0;
+}
+
+static void dt9812_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct dt9812_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver dt9812_driver = {
+ .driver_name = "dt9812",
+ .module = THIS_MODULE,
+ .auto_attach = dt9812_auto_attach,
+ .detach = dt9812_detach,
+};
+
+static int dt9812_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &dt9812_driver, id->driver_info);
+}
+
+static const struct usb_device_id dt9812_usb_table[] = {
+ { USB_DEVICE(0x0867, 0x9812) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, dt9812_usb_table);
+
+static struct usb_driver dt9812_usb_driver = {
+ .name = "dt9812",
+ .id_table = dt9812_usb_table,
+ .probe = dt9812_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(dt9812_driver, dt9812_usb_driver);
+
+MODULE_AUTHOR("Anders Blomdell <anders.blomdell@control.lth.se>");
+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 00000000000..e5593f8c740
--- /dev/null
+++ b/drivers/staging/comedi/drivers/dyna_pci10xx.c
@@ -0,0 +1,281 @@
+/*
+ * 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.
+ */
+
+/*
+ Driver: dyna_pci10xx
+ Devices: Dynalog India PCI DAQ Cards, http://www.dynalogindia.com/
+ Author: Prashant Shah <pshah.mumbai@gmail.com>
+ Developed at Automation Labs, Chemical Dept., IIT Bombay, India.
+ Prof. Kannan Moudgalya <kannan@iitb.ac.in>
+ 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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+
+#include "../comedidev.h"
+
+#define READ_TIMEOUT 50
+
+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 };
+
+struct dyna_pci10xx_private {
+ struct mutex mutex;
+ unsigned long BADR3;
+};
+
+static int dyna_pci10xx_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw_p(dev->iobase);
+ if (status & (1 << 15))
+ return 0;
+ return -EBUSY;
+}
+
+static int dyna_pci10xx_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ int n;
+ u16 d = 0;
+ int ret = 0;
+ unsigned int chan, range;
+
+ /* get the channel number and range */
+ chan = CR_CHAN(insn->chanspec);
+ range = range_codes_pci1050_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, dev->iobase + 2);
+ udelay(10);
+
+ ret = comedi_timeout(dev, s, insn, dyna_pci10xx_ai_eoc, 0);
+ if (ret)
+ break;
+
+ /* read data */
+ d = inw_p(dev->iobase);
+ /* mask the first 4 bits - EOC bits */
+ d &= 0x0FFF;
+ data[n] = d;
+ }
+ mutex_unlock(&devpriv->mutex);
+
+ /* return the number of samples read/written */
+ return ret ? ret : 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)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ int n;
+ unsigned int chan, range;
+
+ chan = CR_CHAN(insn->chanspec);
+ range = range_codes_pci1050_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], dev->iobase);
+ 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)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+ u16 d = 0;
+
+ 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 insn->n;
+}
+
+static int dyna_pci10xx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+
+ mutex_lock(&devpriv->mutex);
+ if (comedi_dio_update_state(s, data)) {
+ smp_mb();
+ outw_p(s->state, devpriv->BADR3);
+ udelay(10);
+ }
+
+ data[1] = s->state;
+ mutex_unlock(&devpriv->mutex);
+
+ return insn->n;
+}
+
+static int dyna_pci10xx_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct dyna_pci10xx_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 2);
+ devpriv->BADR3 = pci_resource_start(pcidev, 3);
+
+ mutex_init(&devpriv->mutex);
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* analog input */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0FFF;
+ s->range_table = &range_pci1050_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 = 16;
+ s->maxdata = 0x0FFF;
+ s->range_table = &range_unipolar10;
+ 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 = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->len_chanlist = 16;
+ 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 = 16;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->len_chanlist = 16;
+ s->state = 0;
+ s->insn_bits = dyna_pci10xx_do_insn_bits;
+
+ return 0;
+}
+
+static void dyna_pci10xx_detach(struct comedi_device *dev)
+{
+ struct dyna_pci10xx_private *devpriv = dev->private;
+
+ if (devpriv)
+ mutex_destroy(&devpriv->mutex);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver dyna_pci10xx_driver = {
+ .driver_name = "dyna_pci10xx",
+ .module = THIS_MODULE,
+ .auto_attach = dyna_pci10xx_auto_attach,
+ .detach = dyna_pci10xx_detach,
+};
+
+static int dyna_pci10xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &dyna_pci10xx_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id dyna_pci10xx_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_PLX, 0x1050) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, dyna_pci10xx_pci_table);
+
+static struct pci_driver dyna_pci10xx_pci_driver = {
+ .name = "dyna_pci10xx",
+ .id_table = dyna_pci10xx_pci_table,
+ .probe = dyna_pci10xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(dyna_pci10xx_driver, dyna_pci10xx_pci_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Prashant Shah <pshah.mumbai@gmail.com>");
+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 00000000000..4e410f3b0e2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/fl512.c
@@ -0,0 +1,178 @@
+/*
+ * fl512.c
+ * Anders Gnistrup <ex18@kalman.iau.dtu.dk>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: fl512
+ * Description: unknown
+ * Author: Anders Gnistrup <ex18@kalman.iau.dtu.dk>
+ * Devices: [unknown] FL512 (fl512)
+ * Status: unknown
+ *
+ * Digital I/O is not supported.
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+/*
+ * Register I/O map
+ */
+#define FL512_AI_LSB_REG 0x02
+#define FL512_AI_MSB_REG 0x03
+#define FL512_AI_MUX_REG 0x02
+#define FL512_AI_START_CONV_REG 0x03
+#define FL512_AO_DATA_REG(x) (0x04 + ((x) * 2))
+#define FL512_AO_TRIG_REG(x) (0x04 + ((x) * 2))
+
+struct fl512_private {
+ unsigned short ao_readback[2];
+};
+
+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_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ outb(chan, dev->iobase + FL512_AI_MUX_REG);
+
+ for (i = 0; i < insn->n; i++) {
+ outb(0, dev->iobase + FL512_AI_START_CONV_REG);
+
+ /* XXX should test "done" flag instead of delay */
+ udelay(30);
+
+ val = inb(dev->iobase + FL512_AI_LSB_REG);
+ val |= (inb(dev->iobase + FL512_AI_MSB_REG) << 8);
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int fl512_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct fl512_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* write LSB, MSB then trigger conversion */
+ outb(val & 0x0ff, dev->iobase + FL512_AO_DATA_REG(chan));
+ outb((val >> 8) & 0xf, dev->iobase + FL512_AO_DATA_REG(chan));
+ inb(dev->iobase + FL512_AO_TRIG_REG(chan));
+ }
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int fl512_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct fl512_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct fl512_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_fl512;
+ s->insn_read = fl512_ai_insn_read;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_fl512;
+ s->insn_write = fl512_ao_insn_write;
+ s->insn_read = fl512_ao_insn_read;
+
+ return 0;
+}
+
+static struct comedi_driver fl512_driver = {
+ .driver_name = "fl512",
+ .module = THIS_MODULE,
+ .attach = fl512_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(fl512_driver);
+
+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 00000000000..22333c1ad88
--- /dev/null
+++ b/drivers/staging/comedi/drivers/gsc_hpdi.c
@@ -0,0 +1,751 @@
+/*
+ * gsc_hpdi.c
+ * Comedi driver the General Standards Corporation
+ * High Speed Parallel Digital Interface rs485 boards.
+ *
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Copyright (C) 2003 Coherent Imaging Systems
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: gsc_hpdi
+ * Description: General Standards Corporation High
+ * Speed Parallel Digital Interface rs485 boards
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: only receive mode works, transmit not supported
+ * Updated: Thu, 01 Nov 2012 16:17:38 +0000
+ * Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi),
+ * PMC-HPDI32
+ *
+ * Configuration options:
+ * None.
+ *
+ * Manual configuration of supported devices is not supported; they are
+ * configured automatically.
+ *
+ * There are some additional hpdi models available from GSC for which
+ * support could be added to this driver.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "plx9080.h"
+#include "comedi_fc.h"
+
+/*
+ * PCI BAR2 Register map (devpriv->mmio)
+ */
+#define FIRMWARE_REV_REG 0x00
+#define FEATURES_REG_PRESENT_BIT (1 << 15)
+#define BOARD_CONTROL_REG 0x04
+#define BOARD_RESET_BIT (1 << 0)
+#define TX_FIFO_RESET_BIT (1 << 1)
+#define RX_FIFO_RESET_BIT (1 << 2)
+#define TX_ENABLE_BIT (1 << 4)
+#define RX_ENABLE_BIT (1 << 5)
+#define DEMAND_DMA_DIRECTION_TX_BIT (1 << 6) /* ch 0 only */
+#define LINE_VALID_ON_STATUS_VALID_BIT (1 << 7)
+#define START_TX_BIT (1 << 8)
+#define CABLE_THROTTLE_ENABLE_BIT (1 << 9)
+#define TEST_MODE_ENABLE_BIT (1 << 31)
+#define BOARD_STATUS_REG 0x08
+#define COMMAND_LINE_STATUS_MASK (0x7f << 0)
+#define TX_IN_PROGRESS_BIT (1 << 7)
+#define TX_NOT_EMPTY_BIT (1 << 8)
+#define TX_NOT_ALMOST_EMPTY_BIT (1 << 9)
+#define TX_NOT_ALMOST_FULL_BIT (1 << 10)
+#define TX_NOT_FULL_BIT (1 << 11)
+#define RX_NOT_EMPTY_BIT (1 << 12)
+#define RX_NOT_ALMOST_EMPTY_BIT (1 << 13)
+#define RX_NOT_ALMOST_FULL_BIT (1 << 14)
+#define RX_NOT_FULL_BIT (1 << 15)
+#define BOARD_JUMPER0_INSTALLED_BIT (1 << 16)
+#define BOARD_JUMPER1_INSTALLED_BIT (1 << 17)
+#define TX_OVERRUN_BIT (1 << 21)
+#define RX_UNDERRUN_BIT (1 << 22)
+#define RX_OVERRUN_BIT (1 << 23)
+#define TX_PROG_ALMOST_REG 0x0c
+#define RX_PROG_ALMOST_REG 0x10
+#define ALMOST_EMPTY_BITS(x) (((x) & 0xffff) << 0)
+#define ALMOST_FULL_BITS(x) (((x) & 0xff) << 16)
+#define FEATURES_REG 0x14
+#define FIFO_SIZE_PRESENT_BIT (1 << 0)
+#define FIFO_WORDS_PRESENT_BIT (1 << 1)
+#define LEVEL_EDGE_INTERRUPTS_PRESENT_BIT (1 << 2)
+#define GPIO_SUPPORTED_BIT (1 << 3)
+#define PLX_DMA_CH1_SUPPORTED_BIT (1 << 4)
+#define OVERRUN_UNDERRUN_SUPPORTED_BIT (1 << 5)
+#define FIFO_REG 0x18
+#define TX_STATUS_COUNT_REG 0x1c
+#define TX_LINE_VALID_COUNT_REG 0x20,
+#define TX_LINE_INVALID_COUNT_REG 0x24
+#define RX_STATUS_COUNT_REG 0x28
+#define RX_LINE_COUNT_REG 0x2c
+#define INTERRUPT_CONTROL_REG 0x30
+#define FRAME_VALID_START_INTR (1 << 0)
+#define FRAME_VALID_END_INTR (1 << 1)
+#define TX_FIFO_EMPTY_INTR (1 << 8)
+#define TX_FIFO_ALMOST_EMPTY_INTR (1 << 9)
+#define TX_FIFO_ALMOST_FULL_INTR (1 << 10)
+#define TX_FIFO_FULL_INTR (1 << 11)
+#define RX_EMPTY_INTR (1 << 12)
+#define RX_ALMOST_EMPTY_INTR (1 << 13)
+#define RX_ALMOST_FULL_INTR (1 << 14)
+#define RX_FULL_INTR (1 << 15)
+#define INTERRUPT_STATUS_REG 0x34
+#define TX_CLOCK_DIVIDER_REG 0x38
+#define TX_FIFO_SIZE_REG 0x40
+#define RX_FIFO_SIZE_REG 0x44
+#define FIFO_SIZE_MASK (0xfffff << 0)
+#define TX_FIFO_WORDS_REG 0x48
+#define RX_FIFO_WORDS_REG 0x4c
+#define INTERRUPT_EDGE_LEVEL_REG 0x50
+#define INTERRUPT_POLARITY_REG 0x54
+
+#define TIMER_BASE 50 /* 20MHz master clock */
+#define DMA_BUFFER_SIZE 0x10000
+#define NUM_DMA_BUFFERS 4
+#define NUM_DMA_DESCRIPTORS 256
+
+struct hpdi_board {
+ const char *name;
+ int device_id;
+ int 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
+};
+
+struct hpdi_private {
+ void __iomem *plx9080_mmio;
+ void __iomem *mmio;
+ 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 */
+ unsigned int dma_desc_index;
+ unsigned int tx_fifo_size;
+ unsigned int rx_fifo_size;
+ unsigned long dio_count;
+ /* number of bytes at which to generate COMEDI_CB_BLOCK events */
+ unsigned int block_size;
+};
+
+static void gsc_hpdi_drain_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct hpdi_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int idx;
+ unsigned int start;
+ unsigned int desc;
+ unsigned int size;
+ unsigned int next;
+
+ if (channel)
+ next = readl(devpriv->plx9080_mmio + PLX_DMA1_PCI_ADDRESS_REG);
+ else
+ next = readl(devpriv->plx9080_mmio + PLX_DMA0_PCI_ADDRESS_REG);
+
+ idx = devpriv->dma_desc_index;
+ start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr);
+ /* loop until we have read all the full buffers */
+ for (desc = 0; (next < start || next >= start + devpriv->block_size) &&
+ desc < devpriv->num_dma_descriptors; desc++) {
+ /* transfer data from dma buffer to comedi buffer */
+ size = devpriv->block_size / sizeof(uint32_t);
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (size > devpriv->dio_count)
+ size = devpriv->dio_count;
+ devpriv->dio_count -= size;
+ }
+ cfc_write_array_to_buffer(s, devpriv->desc_dio_buffer[idx],
+ size * sizeof(uint32_t));
+ idx++;
+ idx %= devpriv->num_dma_descriptors;
+ start = le32_to_cpu(devpriv->dma_desc[idx].pci_start_addr);
+
+ devpriv->dma_desc_index = idx;
+ }
+ /* XXX check for buffer overrun somehow */
+}
+
+static irqreturn_t gsc_hpdi_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct hpdi_private *devpriv = dev->private;
+ 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(devpriv->plx9080_mmio + PLX_INTRCS_REG);
+ if ((plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LIA)) == 0)
+ return IRQ_NONE;
+
+ hpdi_intr_status = readl(devpriv->mmio + INTERRUPT_STATUS_REG);
+ hpdi_board_status = readl(devpriv->mmio + BOARD_STATUS_REG);
+
+ if (hpdi_intr_status)
+ writel(hpdi_intr_status, devpriv->mmio + INTERRUPT_STATUS_REG);
+
+ /* spin lock makes sure no one else changes plx dma control reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ dma0_status = readb(devpriv->plx9080_mmio + 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,
+ devpriv->plx9080_mmio + PLX_DMA0_CS_REG);
+
+ if (dma0_status & PLX_DMA_EN_BIT)
+ gsc_hpdi_drain_dma(dev, 0);
+ }
+ 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(devpriv->plx9080_mmio + 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,
+ devpriv->plx9080_mmio + PLX_DMA1_CS_REG);
+ }
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* clear possible plx9080 interrupt sources */
+ if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */
+ plx_bits = readl(devpriv->plx9080_mmio + PLX_DBR_OUT_REG);
+ writel(plx_bits, devpriv->plx9080_mmio + PLX_DBR_OUT_REG);
+ }
+
+ if (hpdi_board_status & RX_OVERRUN_BIT) {
+ dev_err(dev->class_dev, "rx fifo overrun\n");
+ async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ }
+
+ if (hpdi_board_status & RX_UNDERRUN_BIT) {
+ dev_err(dev->class_dev, "rx fifo underrun\n");
+ async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ }
+
+ if (devpriv->dio_count == 0)
+ async->events |= COMEDI_CB_EOA;
+
+ cfc_handle_events(dev, s);
+
+ return IRQ_HANDLED;
+}
+
+static void gsc_hpdi_abort_dma(struct comedi_device *dev, unsigned int channel)
+{
+ struct hpdi_private *devpriv = dev->private;
+ unsigned long flags;
+
+ /* spinlock for plx dma control/status reg */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ plx9080_abort_dma(devpriv->plx9080_mmio, channel);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+}
+
+static int gsc_hpdi_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct hpdi_private *devpriv = dev->private;
+
+ writel(0, devpriv->mmio + BOARD_CONTROL_REG);
+ writel(0, devpriv->mmio + INTERRUPT_CONTROL_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+
+ return 0;
+}
+
+static int gsc_hpdi_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct hpdi_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long flags;
+ uint32_t bits;
+
+ if (s->io_bits)
+ return -EINVAL;
+
+ writel(RX_FIFO_RESET_BIT, devpriv->mmio + BOARD_CONTROL_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+
+ devpriv->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, devpriv->plx9080_mmio + PLX_DMA0_TRANSFER_SIZE_REG);
+ writel(0, devpriv->plx9080_mmio + PLX_DMA0_PCI_ADDRESS_REG);
+ writel(0, devpriv->plx9080_mmio + PLX_DMA0_LOCAL_ADDRESS_REG);
+
+ /* give location of first dma descriptor */
+ bits = devpriv->dma_desc_phys_addr | PLX_DESC_IN_PCI_BIT |
+ PLX_INTR_TERM_COUNT | PLX_XFER_LOCAL_TO_PCI;
+ writel(bits, devpriv->plx9080_mmio + PLX_DMA0_DESCRIPTOR_REG);
+
+ /* enable dma transfer */
+ spin_lock_irqsave(&dev->spinlock, flags);
+ writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | PLX_CLEAR_DMA_INTR_BIT,
+ devpriv->plx9080_mmio + PLX_DMA0_CS_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->dio_count = cmd->stop_arg;
+ else
+ devpriv->dio_count = 1;
+
+ /* clear over/under run status flags */
+ writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT,
+ devpriv->mmio + BOARD_STATUS_REG);
+
+ /* enable interrupts */
+ writel(RX_FULL_INTR, devpriv->mmio + INTERRUPT_CONTROL_REG);
+
+ writel(RX_ENABLE_BIT, devpriv->mmio + BOARD_CONTROL_REG);
+
+ return 0;
+}
+
+static int gsc_hpdi_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "chanlist must be ch 0 to 31 in order\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int gsc_hpdi_cmd_test(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ if (s->io_bits)
+ return -EINVAL;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (!cmd->chanlist_len || !cmd->chanlist) {
+ cmd->chanlist_len = 32;
+ err |= -EINVAL;
+ }
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= gsc_hpdi_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+
+}
+
+/* setup dma descriptors so a link completes every 'len' bytes */
+static int gsc_hpdi_setup_dma_descriptors(struct comedi_device *dev,
+ unsigned int len)
+{
+ struct hpdi_private *devpriv = dev->private;
+ dma_addr_t phys_addr = devpriv->dma_desc_phys_addr;
+ uint32_t next_bits = PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT |
+ PLX_XFER_LOCAL_TO_PCI;
+ unsigned int offset = 0;
+ unsigned int idx = 0;
+ unsigned int i;
+
+ if (len > DMA_BUFFER_SIZE)
+ len = DMA_BUFFER_SIZE;
+ len -= len % sizeof(uint32_t);
+ if (len == 0)
+ return -EINVAL;
+
+ for (i = 0; i < NUM_DMA_DESCRIPTORS && idx < NUM_DMA_BUFFERS; i++) {
+ devpriv->dma_desc[i].pci_start_addr =
+ cpu_to_le32(devpriv->dio_buffer_phys_addr[idx] + offset);
+ devpriv->dma_desc[i].local_start_addr = cpu_to_le32(FIFO_REG);
+ devpriv->dma_desc[i].transfer_size = cpu_to_le32(len);
+ devpriv->dma_desc[i].next = cpu_to_le32((phys_addr +
+ (i + 1) * sizeof(devpriv->dma_desc[0])) | next_bits);
+
+ devpriv->desc_dio_buffer[i] = devpriv->dio_buffer[idx] +
+ (offset / sizeof(uint32_t));
+
+ offset += len;
+ if (len + offset > DMA_BUFFER_SIZE) {
+ offset = 0;
+ idx++;
+ }
+ }
+ devpriv->num_dma_descriptors = i;
+ /* fix last descriptor to point back to first */
+ devpriv->dma_desc[i - 1].next = cpu_to_le32(phys_addr | next_bits);
+
+ devpriv->block_size = len;
+
+ return len;
+}
+
+static int gsc_hpdi_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ switch (data[0]) {
+ case INSN_CONFIG_BLOCK_SIZE:
+ ret = gsc_hpdi_setup_dma_descriptors(dev, data[1]);
+ if (ret)
+ return ret;
+
+ data[1] = ret;
+ break;
+ default:
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0xffffffff);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ return insn->n;
+}
+
+static int gsc_hpdi_init(struct comedi_device *dev)
+{
+ struct hpdi_private *devpriv = dev->private;
+ uint32_t plx_intcsr_bits;
+
+ /* wait 10usec after reset before accessing fifos */
+ writel(BOARD_RESET_BIT, devpriv->mmio + BOARD_CONTROL_REG);
+ udelay(10);
+
+ writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32),
+ devpriv->mmio + RX_PROG_ALMOST_REG);
+ writel(ALMOST_EMPTY_BITS(32) | ALMOST_FULL_BITS(32),
+ devpriv->mmio + TX_PROG_ALMOST_REG);
+
+ devpriv->tx_fifo_size = readl(devpriv->mmio + TX_FIFO_SIZE_REG) &
+ FIFO_SIZE_MASK;
+ devpriv->rx_fifo_size = readl(devpriv->mmio + RX_FIFO_SIZE_REG) &
+ FIFO_SIZE_MASK;
+
+ writel(0, devpriv->mmio + 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, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+
+ return 0;
+}
+
+static void gsc_hpdi_init_plx9080(struct comedi_device *dev)
+{
+ struct hpdi_private *devpriv = dev->private;
+ uint32_t bits;
+ void __iomem *plx_iobase = devpriv->plx9080_mmio;
+
+#ifdef __BIG_ENDIAN
+ bits = BIGEND_DMA0 | BIGEND_DMA1;
+#else
+ bits = 0;
+#endif
+ writel(bits, devpriv->plx9080_mmio + PLX_BIGEND_REG);
+
+ writel(0, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+
+ gsc_hpdi_abort_dma(dev, 0);
+ gsc_hpdi_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);
+}
+
+static const struct hpdi_board *gsc_hpdi_find_board(struct pci_dev *pcidev)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(hpdi_boards); i++)
+ if (pcidev->device == hpdi_boards[i].device_id &&
+ pcidev->subsystem_device == hpdi_boards[i].subdevice_id)
+ return &hpdi_boards[i];
+ return NULL;
+}
+
+static int gsc_hpdi_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct hpdi_board *thisboard;
+ struct hpdi_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int retval;
+
+ thisboard = gsc_hpdi_find_board(pcidev);
+ if (!thisboard) {
+ dev_err(dev->class_dev, "gsc_hpdi: pci %s not supported\n",
+ pci_name(pcidev));
+ return -EINVAL;
+ }
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ retval = comedi_pci_enable(dev);
+ if (retval)
+ return retval;
+ pci_set_master(pcidev);
+
+ devpriv->plx9080_mmio = pci_ioremap_bar(pcidev, 0);
+ devpriv->mmio = pci_ioremap_bar(pcidev, 2);
+ if (!devpriv->plx9080_mmio || !devpriv->mmio) {
+ dev_warn(dev->class_dev, "failed to remap io memory\n");
+ return -ENOMEM;
+ }
+
+ gsc_hpdi_init_plx9080(dev);
+
+ /* get irq */
+ if (request_irq(pcidev->irq, gsc_hpdi_interrupt, IRQF_SHARED,
+ dev->board_name, dev)) {
+ dev_warn(dev->class_dev,
+ "unable to allocate irq %u\n", pcidev->irq);
+ return -EINVAL;
+ }
+ dev->irq = pcidev->irq;
+
+ dev_dbg(dev->class_dev, " irq %u\n", dev->irq);
+
+ /* allocate pci dma buffers */
+ for (i = 0; i < NUM_DMA_BUFFERS; i++) {
+ devpriv->dio_buffer[i] =
+ pci_alloc_consistent(pcidev, DMA_BUFFER_SIZE,
+ &devpriv->dio_buffer_phys_addr[i]);
+ }
+ /* allocate dma descriptors */
+ devpriv->dma_desc = pci_alloc_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ NUM_DMA_DESCRIPTORS,
+ &devpriv->dma_desc_phys_addr);
+ if (devpriv->dma_desc_phys_addr & 0xf) {
+ dev_warn(dev->class_dev,
+ " dma descriptors not quad-word aligned (bug)\n");
+ return -EIO;
+ }
+
+ retval = gsc_hpdi_setup_dma_descriptors(dev, 0x1000);
+ if (retval < 0)
+ return retval;
+
+ retval = comedi_alloc_subdevices(dev, 1);
+ if (retval)
+ return retval;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[0];
+ dev->read_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 = gsc_hpdi_dio_insn_config;
+ s->do_cmd = gsc_hpdi_cmd;
+ s->do_cmdtest = gsc_hpdi_cmd_test;
+ s->cancel = gsc_hpdi_cancel;
+
+ return gsc_hpdi_init(dev);
+}
+
+static void gsc_hpdi_detach(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct hpdi_private *devpriv = dev->private;
+ unsigned int i;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->plx9080_mmio) {
+ writel(0, devpriv->plx9080_mmio + PLX_INTRCS_REG);
+ iounmap(devpriv->plx9080_mmio);
+ }
+ if (devpriv->mmio)
+ iounmap(devpriv->mmio);
+ /* free pci dma buffers */
+ for (i = 0; i < NUM_DMA_BUFFERS; i++) {
+ if (devpriv->dio_buffer[i])
+ pci_free_consistent(pcidev,
+ DMA_BUFFER_SIZE,
+ devpriv->dio_buffer[i],
+ devpriv->
+ dio_buffer_phys_addr[i]);
+ }
+ /* free dma descriptors */
+ if (devpriv->dma_desc)
+ pci_free_consistent(pcidev,
+ sizeof(struct plx_dma_desc) *
+ NUM_DMA_DESCRIPTORS,
+ devpriv->dma_desc,
+ devpriv->dma_desc_phys_addr);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver gsc_hpdi_driver = {
+ .driver_name = "gsc_hpdi",
+ .module = THIS_MODULE,
+ .auto_attach = gsc_hpdi_auto_attach,
+ .detach = gsc_hpdi_detach,
+};
+
+static int gsc_hpdi_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &gsc_hpdi_driver, id->driver_data);
+}
+
+static const struct pci_device_id gsc_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, gsc_hpdi_pci_table);
+
+static struct pci_driver gsc_hpdi_pci_driver = {
+ .name = "gsc_hpdi",
+ .id_table = gsc_hpdi_pci_table,
+ .probe = gsc_hpdi_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(gsc_hpdi_driver, gsc_hpdi_pci_driver);
+
+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 00000000000..0b8b2162b76
--- /dev/null
+++ b/drivers/staging/comedi/drivers/icp_multi.c
@@ -0,0 +1,612 @@
+/*
+ comedi/drivers/icp_multi.c
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+Driver: icp_multi
+Description: Inova ICP_MULTI
+Author: Anne Smorthit <anne.smorthit@sfwte.ch>
+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
+
+Configuration options: not applicable, uses PCI auto config
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#define PCI_DEVICE_ID_ICP_MULTI 0x8000
+
+#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 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 };
+
+/*
+==============================================================================
+ Data & Structure declarations
+==============================================================================
+*/
+
+struct icp_multi_private {
+ char valid; /* card is usable */
+ void __iomem *io_addr; /* Pointer to mapped 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 scanned 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 */
+ unsigned short ao_data[4]; /* data output buffer */
+ unsigned int do_data; /* Remember digital output data */
+};
+
+static void setup_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int i, range, chanprog;
+ unsigned int diff;
+
+ 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 = range_codes_analog[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);
+ }
+}
+
+static int icp_multi_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readw(devpriv->io_addr + ICP_MULTI_ADC_CSR);
+ if ((status & ADC_BSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int icp_multi_insn_read_ai(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ int ret = 0;
+ int n;
+
+ /* 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);
+
+ 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;
+
+ udelay(1);
+
+ /* Wait for conversion to complete, or get fed up waiting */
+ ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
+ if (ret)
+ break;
+
+ 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);
+
+ return ret ? ret : n;
+}
+
+static int icp_multi_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readw(devpriv->io_addr + ICP_MULTI_DAC_CSR);
+ if ((status & DAC_BSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int icp_multi_insn_write_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ int n, chan, range;
+ int ret;
+
+ /* 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 |= range_codes_analog[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 */
+ ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
+ if (ret) {
+ /* 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;
+
+ return ret;
+ }
+
+ /* 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];
+ }
+
+ return n;
+}
+
+static int icp_multi_insn_read_ao(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ 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;
+}
+
+static int icp_multi_insn_bits_di(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+
+ data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
+
+ return insn->n;
+}
+
+static int icp_multi_insn_bits_do(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct icp_multi_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
+
+ data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
+
+ return insn->n;
+}
+
+static int icp_multi_insn_read_ctr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ return 0;
+}
+
+static int icp_multi_insn_write_ctr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ return 0;
+}
+
+static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct icp_multi_private *devpriv = dev->private;
+ int int_no;
+
+ /* 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;
+
+ /* 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;
+
+ }
+
+ return IRQ_HANDLED;
+}
+
+#if 0
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int n_chan)
+{
+ unsigned int i;
+
+ /* 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]) > (s->nchan / 2)) {
+ comedi_error(dev,
+ "Incorrect differential ai ch-nr");
+ return 0;
+ }
+ } else {
+ if (CR_CHAN(chanlist[i]) > s->n_chan) {
+ comedi_error(dev,
+ "Incorrect ai channel number");
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+#endif
+
+static int icp_multi_reset(struct comedi_device *dev)
+{
+ struct icp_multi_private *devpriv = dev->private;
+ unsigned int i;
+
+ /* Clear INT enables and requests */
+ writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
+ writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
+
+ /* Set DACs to 0..5V range and 0V output */
+ for (i = 0; i < 4; 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);
+
+ return 0;
+}
+
+static int icp_multi_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct icp_multi_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
+ if (!devpriv->io_addr)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ icp_multi_reset(dev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
+ IRQF_SHARED, dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 16;
+ s->range_table = &range_analog;
+ s->insn_read = icp_multi_insn_read_ai;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 4;
+ s->range_table = &range_analog;
+ s->insn_write = icp_multi_insn_write_ao;
+ s->insn_read = icp_multi_insn_read_ao;
+
+ s = &dev->subdevices[2];
+ 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 = icp_multi_insn_bits_di;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->len_chanlist = 8;
+ s->range_table = &range_digital;
+ s->insn_bits = icp_multi_insn_bits_do;
+
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->len_chanlist = 4;
+ s->state = 0;
+ s->insn_read = icp_multi_insn_read_ctr;
+ s->insn_write = icp_multi_insn_write_ctr;
+
+ devpriv->valid = 1;
+
+ return 0;
+}
+
+static void icp_multi_detach(struct comedi_device *dev)
+{
+ struct icp_multi_private *devpriv = dev->private;
+
+ if (devpriv)
+ if (devpriv->valid)
+ icp_multi_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv && devpriv->io_addr)
+ iounmap(devpriv->io_addr);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver icp_multi_driver = {
+ .driver_name = "icp_multi",
+ .module = THIS_MODULE,
+ .auto_attach = icp_multi_auto_attach,
+ .detach = icp_multi_detach,
+};
+
+static int icp_multi_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
+}
+
+static const struct pci_device_id icp_multi_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
+
+static struct pci_driver icp_multi_pci_driver = {
+ .name = "icp_multi",
+ .id_table = icp_multi_pci_table,
+ .probe = icp_multi_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ii_pci20kc.c b/drivers/staging/comedi/drivers/ii_pci20kc.c
new file mode 100644
index 00000000000..2516ce83483
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ii_pci20kc.c
@@ -0,0 +1,534 @@
+/*
+ * ii_pci20kc.c
+ * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
+ *
+ * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
+ * with suggestions from David Schleef 16.06.2000
+ */
+
+/*
+ * Driver: ii_pci20kc
+ * Description: Intelligent Instruments PCI-20001C carrier board
+ * Devices: (Intelligent Instrumentation) PCI-20001C [ii_pci20kc]
+ * Author: Markus Kempf <kempf@matsci.uni-sb.de>
+ * Status: works
+ *
+ * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
+ * -2a version has 32 on-board DIO channels. Three add-on modules
+ * can be added to the carrier board for additional functionality.
+ *
+ * Supported add-on modules:
+ * PCI-20006M-1 1 channel, 16-bit analog output module
+ * PCI-20006M-2 2 channel, 16-bit analog output module
+ * PCI-20341M-1A 4 channel, 16-bit analog input module
+ *
+ * Options:
+ * 0 Board base address
+ * 1 IRQ (not-used)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define II20K_MOD_OFFSET 0x100
+#define II20K_ID_REG 0x00
+#define II20K_ID_MOD1_EMPTY (1 << 7)
+#define II20K_ID_MOD2_EMPTY (1 << 6)
+#define II20K_ID_MOD3_EMPTY (1 << 5)
+#define II20K_ID_MASK 0x1f
+#define II20K_ID_PCI20001C_1A 0x1b /* no on-board DIO */
+#define II20K_ID_PCI20001C_2A 0x1d /* on-board DIO */
+#define II20K_MOD_STATUS_REG 0x40
+#define II20K_MOD_STATUS_IRQ_MOD1 (1 << 7)
+#define II20K_MOD_STATUS_IRQ_MOD2 (1 << 6)
+#define II20K_MOD_STATUS_IRQ_MOD3 (1 << 5)
+#define II20K_DIO0_REG 0x80
+#define II20K_DIO1_REG 0x81
+#define II20K_DIR_ENA_REG 0x82
+#define II20K_DIR_DIO3_OUT (1 << 7)
+#define II20K_DIR_DIO2_OUT (1 << 6)
+#define II20K_BUF_DISAB_DIO3 (1 << 5)
+#define II20K_BUF_DISAB_DIO2 (1 << 4)
+#define II20K_DIR_DIO1_OUT (1 << 3)
+#define II20K_DIR_DIO0_OUT (1 << 2)
+#define II20K_BUF_DISAB_DIO1 (1 << 1)
+#define II20K_BUF_DISAB_DIO0 (1 << 0)
+#define II20K_CTRL01_REG 0x83
+#define II20K_CTRL01_SET (1 << 7)
+#define II20K_CTRL01_DIO0_IN (1 << 4)
+#define II20K_CTRL01_DIO1_IN (1 << 1)
+#define II20K_DIO2_REG 0xc0
+#define II20K_DIO3_REG 0xc1
+#define II20K_CTRL23_REG 0xc3
+#define II20K_CTRL23_SET (1 << 7)
+#define II20K_CTRL23_DIO2_IN (1 << 4)
+#define II20K_CTRL23_DIO3_IN (1 << 1)
+
+#define II20K_ID_PCI20006M_1 0xe2 /* 1 AO channels */
+#define II20K_ID_PCI20006M_2 0xe3 /* 2 AO channels */
+#define II20K_AO_STRB_REG(x) (0x0b + ((x) * 0x08))
+#define II20K_AO_LSB_REG(x) (0x0d + ((x) * 0x08))
+#define II20K_AO_MSB_REG(x) (0x0e + ((x) * 0x08))
+#define II20K_AO_STRB_BOTH_REG 0x1b
+
+#define II20K_ID_PCI20341M_1 0x77 /* 4 AI channels */
+#define II20K_AI_STATUS_CMD_REG 0x01
+#define II20K_AI_STATUS_CMD_BUSY (1 << 7)
+#define II20K_AI_STATUS_CMD_HW_ENA (1 << 1)
+#define II20K_AI_STATUS_CMD_EXT_START (1 << 0)
+#define II20K_AI_LSB_REG 0x02
+#define II20K_AI_MSB_REG 0x03
+#define II20K_AI_PACER_RESET_REG 0x04
+#define II20K_AI_16BIT_DATA_REG 0x06
+#define II20K_AI_CONF_REG 0x10
+#define II20K_AI_CONF_ENA (1 << 2)
+#define II20K_AI_OPT_REG 0x11
+#define II20K_AI_OPT_TRIG_ENA (1 << 5)
+#define II20K_AI_OPT_TRIG_INV (1 << 4)
+#define II20K_AI_OPT_TIMEBASE(x) (((x) & 0x3) << 1)
+#define II20K_AI_OPT_BURST_MODE (1 << 0)
+#define II20K_AI_STATUS_REG 0x12
+#define II20K_AI_STATUS_INT (1 << 7)
+#define II20K_AI_STATUS_TRIG (1 << 6)
+#define II20K_AI_STATUS_TRIG_ENA (1 << 5)
+#define II20K_AI_STATUS_PACER_ERR (1 << 2)
+#define II20K_AI_STATUS_DATA_ERR (1 << 1)
+#define II20K_AI_STATUS_SET_TIME_ERR (1 << 0)
+#define II20K_AI_LAST_CHAN_ADDR_REG 0x13
+#define II20K_AI_CUR_ADDR_REG 0x14
+#define II20K_AI_SET_TIME_REG 0x15
+#define II20K_AI_DELAY_LSB_REG 0x16
+#define II20K_AI_DELAY_MSB_REG 0x17
+#define II20K_AI_CHAN_ADV_REG 0x18
+#define II20K_AI_CHAN_RESET_REG 0x19
+#define II20K_AI_START_TRIG_REG 0x1a
+#define II20K_AI_COUNT_RESET_REG 0x1b
+#define II20K_AI_CHANLIST_REG 0x80
+#define II20K_AI_CHANLIST_ONBOARD_ONLY (1 << 5)
+#define II20K_AI_CHANLIST_GAIN(x) (((x) & 0x3) << 3)
+#define II20K_AI_CHANLIST_MUX_ENA (1 << 2)
+#define II20K_AI_CHANLIST_CHAN(x) (((x) & 0x3) << 0)
+#define II20K_AI_CHANLIST_LEN 0x80
+
+/* the AO range is set by jumpers on the 20006M module */
+static const struct comedi_lrange ii20k_ao_ranges = {
+ 3, {
+ BIP_RANGE(5), /* Chan 0 - W1/W3 in Chan 1 - W2/W4 in */
+ UNI_RANGE(10), /* Chan 0 - W1/W3 out Chan 1 - W2/W4 in */
+ BIP_RANGE(10) /* Chan 0 - W1/W3 in Chan 1 - W2/W4 out */
+ }
+};
+
+static const struct comedi_lrange ii20k_ai_ranges = {
+ 4, {
+ BIP_RANGE(5), /* gain 1 */
+ BIP_RANGE(0.5), /* gain 10 */
+ BIP_RANGE(0.05), /* gain 100 */
+ BIP_RANGE(0.025) /* gain 200 */
+ },
+};
+
+struct ii20k_ao_private {
+ unsigned int last_data[2];
+};
+
+struct ii20k_private {
+ void __iomem *ioaddr;
+};
+
+static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ii20k_private *devpriv = dev->private;
+
+ return devpriv->ioaddr + (s->index + 1) * II20K_MOD_OFFSET;
+}
+
+static int ii20k_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ii20k_ao_private *ao_spriv = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = ao_spriv->last_data[chan];
+
+ return insn->n;
+}
+
+static int ii20k_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ii20k_ao_private *ao_spriv = s->private;
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = ao_spriv->last_data[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* munge data */
+ val += ((s->maxdata + 1) >> 1);
+ val &= s->maxdata;
+
+ writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
+ writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
+ writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
+ }
+
+ ao_spriv->last_data[chan] = val;
+
+ return insn->n;
+}
+
+static int ii20k_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned char status;
+
+ status = readb(iobase + II20K_AI_STATUS_REG);
+ if ((status & II20K_AI_STATUS_INT) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static void ii20k_ai_setup(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned char val;
+
+ /* initialize module */
+ writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
+
+ /* software conversion */
+ writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
+
+ /* set the time base for the settling time counter based on the gain */
+ val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
+ writeb(val, iobase + II20K_AI_OPT_REG);
+
+ /* set the settling time counter based on the gain */
+ val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
+ writeb(val, iobase + II20K_AI_SET_TIME_REG);
+
+ /* set number of input channels */
+ writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
+
+ /* set the channel list byte */
+ val = II20K_AI_CHANLIST_ONBOARD_ONLY |
+ II20K_AI_CHANLIST_MUX_ENA |
+ II20K_AI_CHANLIST_GAIN(range) |
+ II20K_AI_CHANLIST_CHAN(chan);
+ writeb(val, iobase + II20K_AI_CHANLIST_REG);
+
+ /* reset settling time counter and trigger delay counter */
+ writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
+
+ /* reset channel scanner */
+ writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
+}
+
+static int ii20k_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ int ret;
+ int i;
+
+ ii20k_ai_setup(dev, s, insn->chanspec);
+
+ for (i = 0; i < insn->n; i++) {
+ unsigned int val;
+
+ /* generate a software start convert signal */
+ readb(iobase + II20K_AI_PACER_RESET_REG);
+
+ ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = readb(iobase + II20K_AI_LSB_REG);
+ val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
+
+ /* munge two's complement data */
+ val += ((s->maxdata + 1) >> 1);
+ val &= s->maxdata;
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static void ii20k_dio_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ii20k_private *devpriv = dev->private;
+ unsigned char ctrl01 = 0;
+ unsigned char ctrl23 = 0;
+ unsigned char dir_ena = 0;
+
+ /* port 0 - channels 0-7 */
+ if (s->io_bits & 0x000000ff) {
+ /* output port */
+ ctrl01 &= ~II20K_CTRL01_DIO0_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO0;
+ dir_ena |= II20K_DIR_DIO0_OUT;
+ } else {
+ /* input port */
+ ctrl01 |= II20K_CTRL01_DIO0_IN;
+ dir_ena &= ~II20K_DIR_DIO0_OUT;
+ }
+
+ /* port 1 - channels 8-15 */
+ if (s->io_bits & 0x0000ff00) {
+ /* output port */
+ ctrl01 &= ~II20K_CTRL01_DIO1_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO1;
+ dir_ena |= II20K_DIR_DIO1_OUT;
+ } else {
+ /* input port */
+ ctrl01 |= II20K_CTRL01_DIO1_IN;
+ dir_ena &= ~II20K_DIR_DIO1_OUT;
+ }
+
+ /* port 2 - channels 16-23 */
+ if (s->io_bits & 0x00ff0000) {
+ /* output port */
+ ctrl23 &= ~II20K_CTRL23_DIO2_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO2;
+ dir_ena |= II20K_DIR_DIO2_OUT;
+ } else {
+ /* input port */
+ ctrl23 |= II20K_CTRL23_DIO2_IN;
+ dir_ena &= ~II20K_DIR_DIO2_OUT;
+ }
+
+ /* port 3 - channels 24-31 */
+ if (s->io_bits & 0xff000000) {
+ /* output port */
+ ctrl23 &= ~II20K_CTRL23_DIO3_IN;
+ dir_ena &= ~II20K_BUF_DISAB_DIO3;
+ dir_ena |= II20K_DIR_DIO3_OUT;
+ } else {
+ /* input port */
+ ctrl23 |= II20K_CTRL23_DIO3_IN;
+ dir_ena &= ~II20K_DIR_DIO3_OUT;
+ }
+
+ ctrl23 |= II20K_CTRL01_SET;
+ ctrl23 |= II20K_CTRL23_SET;
+
+ /* order is important */
+ writeb(ctrl01, devpriv->ioaddr + II20K_CTRL01_REG);
+ writeb(ctrl23, devpriv->ioaddr + II20K_CTRL23_REG);
+ writeb(dir_ena, devpriv->ioaddr + II20K_DIR_ENA_REG);
+}
+
+static int ii20k_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ ii20k_dio_config(dev, s);
+
+ return insn->n;
+}
+
+static int ii20k_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ii20k_private *devpriv = dev->private;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x000000ff)
+ writeb((s->state >> 0) & 0xff,
+ devpriv->ioaddr + II20K_DIO0_REG);
+ if (mask & 0x0000ff00)
+ writeb((s->state >> 8) & 0xff,
+ devpriv->ioaddr + II20K_DIO1_REG);
+ if (mask & 0x00ff0000)
+ writeb((s->state >> 16) & 0xff,
+ devpriv->ioaddr + II20K_DIO2_REG);
+ if (mask & 0xff000000)
+ writeb((s->state >> 24) & 0xff,
+ devpriv->ioaddr + II20K_DIO3_REG);
+ }
+
+ data[1] = readb(devpriv->ioaddr + II20K_DIO0_REG);
+ data[1] |= readb(devpriv->ioaddr + II20K_DIO1_REG) << 8;
+ data[1] |= readb(devpriv->ioaddr + II20K_DIO2_REG) << 16;
+ data[1] |= readb(devpriv->ioaddr + II20K_DIO3_REG) << 24;
+
+ return insn->n;
+}
+
+static int ii20k_init_module(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ii20k_ao_private *ao_spriv;
+ void __iomem *iobase = ii20k_module_iobase(dev, s);
+ unsigned char id;
+
+ id = readb(iobase + II20K_ID_REG);
+ switch (id) {
+ case II20K_ID_PCI20006M_1:
+ case II20K_ID_PCI20006M_2:
+ ao_spriv = comedi_alloc_spriv(s, sizeof(*ao_spriv));
+ if (!ao_spriv)
+ return -ENOMEM;
+
+ /* Analog Output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = (id == II20K_ID_PCI20006M_2) ? 2 : 1;
+ s->maxdata = 0xffff;
+ s->range_table = &ii20k_ao_ranges;
+ s->insn_read = ii20k_ao_insn_read;
+ s->insn_write = ii20k_ao_insn_write;
+ break;
+ case II20K_ID_PCI20341M_1:
+ /* Analog Input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->range_table = &ii20k_ai_ranges;
+ s->insn_read = ii20k_ai_insn_read;
+ break;
+ default:
+ s->type = COMEDI_SUBD_UNUSED;
+ break;
+ }
+
+ return 0;
+}
+
+static int ii20k_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct ii20k_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned char id;
+ bool has_dio;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->ioaddr = (void __iomem *)(unsigned long)it->options[0];
+
+ id = readb(devpriv->ioaddr + II20K_ID_REG);
+ switch (id & II20K_ID_MASK) {
+ case II20K_ID_PCI20001C_1A:
+ has_dio = false;
+ break;
+ case II20K_ID_PCI20001C_2A:
+ has_dio = true;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (id & II20K_ID_MOD1_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ s = &dev->subdevices[1];
+ if (id & II20K_ID_MOD2_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ s = &dev->subdevices[2];
+ if (id & II20K_ID_MOD3_EMPTY) {
+ s->type = COMEDI_SUBD_UNUSED;
+ } else {
+ ret = ii20k_init_module(dev, s);
+ if (ret)
+ return ret;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[3];
+ if (has_dio) {
+ 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_bits = ii20k_dio_insn_bits;
+ s->insn_config = ii20k_dio_insn_config;
+
+ /* default all channels to input */
+ ii20k_dio_config(dev, s);
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver ii20k_driver = {
+ .driver_name = "ii_pci20kc",
+ .module = THIS_MODULE,
+ .attach = ii20k_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(ii20k_driver);
+
+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 00000000000..a8db9d86aad
--- /dev/null
+++ b/drivers/staging/comedi/drivers/jr3_pci.c
@@ -0,0 +1,830 @@
+/*
+ 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 <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+ * Driver: jr3_pci
+ * Description: JR3/PCI force sensor board
+ * Author: Anders Blomdell <anders.blomdell@control.lth.se>
+ * Updated: Thu, 01 Nov 2012 17:34:55 +0000
+ * Status: works
+ * Devices: [JR3] PCI force sensor board (jr3_pci)
+ *
+ * Configuration options:
+ * None
+ *
+ * Manual configuration of comedi devices is not supported by this
+ * driver; supported PCI devices are configured as comedi devices
+ * automatically.
+ *
+ * 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. The file is called "jr3pci.idm".
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+#include "../comedidev.h"
+
+#include "jr3_pci.h"
+
+#define PCI_VENDOR_ID_JR3 0x1762
+
+enum jr3_pci_boardid {
+ BOARD_JR3_1,
+ BOARD_JR3_2,
+ BOARD_JR3_3,
+ BOARD_JR3_4,
+};
+
+struct jr3_pci_board {
+ const char *name;
+ int n_subdevs;
+};
+
+static const struct jr3_pci_board jr3_pci_boards[] = {
+ [BOARD_JR3_1] = {
+ .name = "jr3_pci_1",
+ .n_subdevs = 1,
+ },
+ [BOARD_JR3_2] = {
+ .name = "jr3_pci_2",
+ .n_subdevs = 2,
+ },
+ [BOARD_JR3_3] = {
+ .name = "jr3_pci_3",
+ .n_subdevs = 3,
+ },
+ [BOARD_JR3_4] = {
+ .name = "jr3_pci_4",
+ .n_subdevs = 4,
+ },
+};
+
+struct jr3_pci_transform {
+ struct {
+ u16 link_type;
+ s16 link_amount;
+ } link[8];
+};
+
+struct jr3_pci_poll_delay {
+ int min;
+ int max;
+};
+
+struct jr3_pci_dev_private {
+ struct jr3_t __iomem *iobase;
+ struct timer_list timer;
+};
+
+struct jr3_pci_subdev_private {
+ struct jr3_channel __iomem *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 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;
+};
+
+static struct jr3_pci_poll_delay poll_delay_min_max(int min, int max)
+{
+ struct jr3_pci_poll_delay result;
+
+ result.min = min;
+ result.max = max;
+ return result;
+}
+
+static int is_complete(struct jr3_channel __iomem *channel)
+{
+ return get_s16(&channel->command_word0) == 0;
+}
+
+static void set_transforms(struct jr3_channel __iomem *channel,
+ struct jr3_pci_transform 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(struct jr3_channel __iomem *channel,
+ short transf_num)
+{
+ set_s16(&channel->command_word0, 0x0500 + (transf_num & 0x000f));
+}
+
+static void use_offset(struct jr3_channel __iomem *channel, short offset_num)
+{
+ set_s16(&channel->command_word0, 0x0600 + (offset_num & 0x000f));
+}
+
+static void set_offset(struct jr3_channel __iomem *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(struct jr3_channel __iomem *channel,
+ struct six_axis_t full_scale)
+{
+ 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(struct jr3_channel __iomem
+ *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(struct jr3_channel __iomem
+ *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 unsigned int jr3_pci_ai_read_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ unsigned int val = 0;
+
+ if (spriv->state != state_jr3_done)
+ return 0;
+
+ if (chan < 56) {
+ unsigned int axis = chan % 8;
+ unsigned filter = chan / 8;
+
+ switch (axis) {
+ case 0:
+ val = get_s16(&spriv->channel->filter[filter].fx);
+ break;
+ case 1:
+ val = get_s16(&spriv->channel->filter[filter].fy);
+ break;
+ case 2:
+ val = get_s16(&spriv->channel->filter[filter].fz);
+ break;
+ case 3:
+ val = get_s16(&spriv->channel->filter[filter].mx);
+ break;
+ case 4:
+ val = get_s16(&spriv->channel->filter[filter].my);
+ break;
+ case 5:
+ val = get_s16(&spriv->channel->filter[filter].mz);
+ break;
+ case 6:
+ val = get_s16(&spriv->channel->filter[filter].v1);
+ break;
+ case 7:
+ val = get_s16(&spriv->channel->filter[filter].v2);
+ break;
+ }
+ val += 0x4000;
+ } else if (chan == 56) {
+ val = get_u16(&spriv->channel->model_no);
+ } else if (chan == 57) {
+ val = get_u16(&spriv->channel->serial_no);
+ }
+
+ return val;
+}
+
+static int jr3_pci_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ u16 errors;
+ int i;
+
+ if (!spriv)
+ return -EINVAL;
+
+ errors = get_u16(&spriv->channel->errors);
+ if (spriv->state != state_jr3_done ||
+ (errors & (watch_dog | watch_dog2 | sensor_change))) {
+ /* No sensor or sensor changed */
+ if (spriv->state == state_jr3_done) {
+ /* Restart polling */
+ spriv->state = state_jr3_poll;
+ }
+ return -EAGAIN;
+ }
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = jr3_pci_ai_read_chan(dev, s, chan);
+
+ return insn->n;
+}
+
+static int jr3_pci_open(struct comedi_device *dev)
+{
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ int i;
+
+ dev_dbg(dev->class_dev, "jr3_pci_open\n");
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+ if (spriv)
+ dev_dbg(dev->class_dev, "serial: %p %d (%d)\n",
+ spriv, spriv->serial_no, s->index);
+ }
+ return 0;
+}
+
+static int read_idm_word(const u8 *data, size_t size, int *pos,
+ unsigned int *val)
+{
+ int result = 0;
+ if (pos && val) {
+ /* 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_check_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size)
+{
+ int more = 1;
+ int pos = 0;
+
+ /*
+ * IDM file format is:
+ * { count, address, data <count> } *
+ * ffff
+ */
+ while (more) {
+ unsigned int count = 0;
+ unsigned int addr = 0;
+
+ more = more && read_idm_word(data, size, &pos, &count);
+ if (more && count == 0xffff)
+ return 0;
+
+ more = more && read_idm_word(data, size, &pos, &addr);
+ while (more && count > 0) {
+ unsigned int dummy = 0;
+
+ more = more && read_idm_word(data, size, &pos, &dummy);
+ count--;
+ }
+ }
+
+ return -ENODATA;
+}
+
+static void jr3_write_firmware(struct comedi_device *dev,
+ int subdev, const u8 *data, size_t size)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_t __iomem *iobase = devpriv->iobase;
+ u32 __iomem *lo;
+ u32 __iomem *hi;
+ int more = 1;
+ int pos = 0;
+
+ while (more) {
+ unsigned int count = 0;
+ unsigned int addr = 0;
+
+ more = more && read_idm_word(data, size, &pos, &count);
+ if (more && count == 0xffff)
+ return;
+
+ more = more && read_idm_word(data, size, &pos, &addr);
+
+ dev_dbg(dev->class_dev, "Loading#%d %4.4x bytes at %4.4x\n",
+ subdev, count, addr);
+
+ while (more && count > 0) {
+ if (addr & 0x4000) {
+ /* 16 bit data, never seen in real life!! */
+ unsigned int data1 = 0;
+
+ more = more &&
+ read_idm_word(data, size, &pos, &data1);
+ count--;
+ /* jr3[addr + 0x20000 * pnum] = data1; */
+ } else {
+ /* Download 24 bit program */
+ unsigned int data1 = 0;
+ unsigned int data2 = 0;
+
+ lo = &iobase->channel[subdev].program_lo[addr];
+ hi = &iobase->channel[subdev].program_hi[addr];
+
+ more = more &&
+ read_idm_word(data, size, &pos, &data1);
+ more = more &&
+ read_idm_word(data, size, &pos, &data2);
+ count -= 2;
+ if (more) {
+ set_u16(lo, data1);
+ udelay(1);
+ set_u16(hi, data2);
+ udelay(1);
+ }
+ }
+ addr++;
+ }
+ }
+}
+
+static int jr3_download_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ int subdev;
+ int ret;
+
+ /* verify IDM file format */
+ ret = jr3_check_firmware(dev, data, size);
+ if (ret)
+ return ret;
+
+ /* write firmware to each subdevice */
+ for (subdev = 0; subdev < dev->n_subdevices; subdev++)
+ jr3_write_firmware(dev, subdev, data, size);
+
+ return 0;
+}
+
+static struct jr3_pci_poll_delay jr3_pci_poll_subdevice(struct comedi_subdevice *s)
+{
+ struct jr3_pci_subdev_private *spriv = s->private;
+ struct jr3_pci_poll_delay result = poll_delay_min_max(1000, 2000);
+ struct jr3_channel __iomem *channel;
+ u16 model_no;
+ u16 serial_no;
+ int errors;
+ int i;
+
+ if (!spriv)
+ return result;
+
+ channel = spriv->channel;
+ errors = get_u16(&channel->errors);
+
+ if (errors != spriv->errors)
+ spriv->errors = errors;
+
+ /* Sensor communication lost? force poll mode */
+ if (errors & (watch_dog | watch_dog2 | sensor_change))
+ spriv->state = state_jr3_poll;
+
+ switch (spriv->state) {
+ case state_jr3_poll:
+ model_no = get_u16(&channel->model_no);
+ 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.
+ */
+ } else {
+ spriv->retries = 0;
+ spriv->state = state_jr3_init_wait_for_offset;
+ }
+ break;
+ case state_jr3_init_wait_for_offset:
+ spriv->retries++;
+ if (spriv->retries < 10) {
+ /*
+ * Wait for offeset to stabilize
+ * (< 10 s according to manual)
+ */
+ } else {
+ struct jr3_pci_transform transf;
+
+ spriv->model_no = get_u16(&channel->model_no);
+ spriv->serial_no = get_u16(&channel->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);
+ spriv->state = state_jr3_init_transform_complete;
+ /* Allow 20 ms for completion */
+ result = poll_delay_min_max(20, 100);
+ }
+ break;
+ case state_jr3_init_transform_complete:
+ if (!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);
+ max_full_scale = get_max_full_scales(channel);
+ set_full_scales(channel, max_full_scale);
+
+ spriv->state = state_jr3_init_set_full_scale_complete;
+ /* Allow 20 ms for completion */
+ result = poll_delay_min_max(20, 100);
+ }
+ break;
+ case state_jr3_init_set_full_scale_complete:
+ if (!is_complete(channel)) {
+ result = poll_delay_min_max(20, 100);
+ } else {
+ struct force_array __iomem *fs = &channel->full_scale;
+
+ /* Use ranges in kN or we will overflow around 2000N! */
+ spriv->range[0].range.min = -get_s16(&fs->fx) * 1000;
+ spriv->range[0].range.max = get_s16(&fs->fx) * 1000;
+ spriv->range[1].range.min = -get_s16(&fs->fy) * 1000;
+ spriv->range[1].range.max = get_s16(&fs->fy) * 1000;
+ spriv->range[2].range.min = -get_s16(&fs->fz) * 1000;
+ spriv->range[2].range.max = get_s16(&fs->fz) * 1000;
+ spriv->range[3].range.min = -get_s16(&fs->mx) * 100;
+ spriv->range[3].range.max = get_s16(&fs->mx) * 100;
+ spriv->range[4].range.min = -get_s16(&fs->my) * 100;
+ spriv->range[4].range.max = get_s16(&fs->my) * 100;
+ spriv->range[5].range.min = -get_s16(&fs->mz) * 100;
+ /* the next five are questionable */
+ spriv->range[5].range.max = get_s16(&fs->mz) * 100;
+ spriv->range[6].range.min = -get_s16(&fs->v1) * 100;
+ spriv->range[6].range.max = get_s16(&fs->v1) * 100;
+ spriv->range[7].range.min = -get_s16(&fs->v2) * 100;
+ spriv->range[7].range.max = get_s16(&fs->v2) * 100;
+ spriv->range[8].range.min = 0;
+ spriv->range[8].range.max = 65535;
+
+ use_offset(channel, 0);
+ spriv->state = state_jr3_init_use_offset_complete;
+ /* Allow 40 ms for completion */
+ result = poll_delay_min_max(40, 100);
+ }
+ break;
+ case state_jr3_init_use_offset_complete:
+ if (!is_complete(channel)) {
+ result = poll_delay_min_max(20, 100);
+ } else {
+ 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);
+
+ spriv->state = state_jr3_done;
+ }
+ break;
+ case state_jr3_done:
+ result = poll_delay_min_max(10000, 20000);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+static void jr3_pci_poll_dev(unsigned long data)
+{
+ struct comedi_device *dev = (struct comedi_device *)data;
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ unsigned long flags;
+ 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 < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+
+ if (now > spriv->next_time_min) {
+ struct jr3_pci_poll_delay sub_delay;
+
+ sub_delay = jr3_pci_poll_subdevice(s);
+
+ spriv->next_time_min = jiffies +
+ msecs_to_jiffies(sub_delay.min);
+ spriv->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 struct jr3_pci_subdev_private *
+jr3_pci_alloc_spriv(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+ struct jr3_pci_subdev_private *spriv;
+ int j;
+ int k;
+
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return NULL;
+
+ spriv->channel = &devpriv->iobase->channel[s->index].data;
+
+ for (j = 0; j < 8; j++) {
+ spriv->range[j].length = 1;
+ spriv->range[j].range.min = -1000000;
+ spriv->range[j].range.max = 1000000;
+
+ for (k = 0; k < 7; k++) {
+ spriv->range_table_list[j + k * 8] =
+ (struct comedi_lrange *)&spriv->range[j];
+ spriv->maxdata_list[j + k * 8] = 0x7fff;
+ }
+ }
+ spriv->range[8].length = 1;
+ spriv->range[8].range.min = 0;
+ spriv->range[8].range.max = 65536;
+
+ spriv->range_table_list[56] = (struct comedi_lrange *)&spriv->range[8];
+ spriv->range_table_list[57] = (struct comedi_lrange *)&spriv->range[8];
+ spriv->maxdata_list[56] = 0xffff;
+ spriv->maxdata_list[57] = 0xffff;
+
+ dev_dbg(dev->class_dev, "p->channel %p %p (%tx)\n",
+ spriv->channel, devpriv->iobase,
+ ((char __iomem *)spriv->channel -
+ (char __iomem *)devpriv->iobase));
+
+ return spriv;
+}
+
+static int jr3_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ static const struct jr3_pci_board *board = NULL;
+ struct jr3_pci_dev_private *devpriv;
+ struct jr3_pci_subdev_private *spriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (sizeof(struct jr3_channel) != 0xc00) {
+ dev_err(dev->class_dev,
+ "sizeof(struct jr3_channel) = %x [expected %x]\n",
+ (unsigned)sizeof(struct jr3_channel), 0xc00);
+ return -EINVAL;
+ }
+
+ if (context < ARRAY_SIZE(jr3_pci_boards))
+ board = &jr3_pci_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ init_timer(&devpriv->timer);
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->iobase = pci_ioremap_bar(pcidev, 0);
+ if (!devpriv->iobase)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, board->n_subdevs);
+ if (ret)
+ return ret;
+
+ dev->open = jr3_pci_open;
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8 * 7 + 2;
+ s->insn_read = jr3_pci_ai_insn_read;
+
+ spriv = jr3_pci_alloc_spriv(dev, s);
+ if (spriv) {
+ /* Channel specific range and maxdata */
+ s->range_table_list = spriv->range_table_list;
+ s->maxdata_list = spriv->maxdata_list;
+ }
+ }
+
+ /* Reset DSP card */
+ writel(0, &devpriv->iobase->channel[0].reset);
+
+ ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ "comedi/jr3pci.idm",
+ jr3_download_firmware, 0);
+ dev_dbg(dev->class_dev, "Firmare load %d\n", ret);
+ if (ret < 0)
+ return ret;
+ /*
+ * TODO: use firmware to load preferred offset tables. Suggested
+ * format:
+ * model serial Fx Fy Fz Mx My Mz\n
+ *
+ * comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ * "comedi/jr3_offsets_table",
+ * jr3_download_firmware, 1);
+ */
+
+ /*
+ * 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->class_dev, "%c\n",
+ get_u16(&devpriv->iobase->channel[0].
+ data.copyright[i]) >> 8);
+ }
+
+ /* Start card timer */
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+
+ spriv->next_time_min = jiffies + msecs_to_jiffies(500);
+ spriv->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);
+
+ return 0;
+}
+
+static void jr3_pci_detach(struct comedi_device *dev)
+{
+ struct jr3_pci_dev_private *devpriv = dev->private;
+
+ if (devpriv) {
+ del_timer_sync(&devpriv->timer);
+
+ if (devpriv->iobase)
+ iounmap(devpriv->iobase);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver jr3_pci_driver = {
+ .driver_name = "jr3_pci",
+ .module = THIS_MODULE,
+ .auto_attach = jr3_pci_auto_attach,
+ .detach = jr3_pci_detach,
+};
+
+static int jr3_pci_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &jr3_pci_driver, id->driver_data);
+}
+
+static const struct pci_device_id jr3_pci_pci_table[] = {
+ { PCI_VDEVICE(JR3, 0x1111), BOARD_JR3_1 },
+ { PCI_VDEVICE(JR3, 0x3111), BOARD_JR3_1 },
+ { PCI_VDEVICE(JR3, 0x3112), BOARD_JR3_2 },
+ { PCI_VDEVICE(JR3, 0x3113), BOARD_JR3_3 },
+ { PCI_VDEVICE(JR3, 0x3114), BOARD_JR3_4 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, jr3_pci_pci_table);
+
+static struct pci_driver jr3_pci_pci_driver = {
+ .name = "jr3_pci",
+ .id_table = jr3_pci_pci_table,
+ .probe = jr3_pci_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(jr3_pci_driver, jr3_pci_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("comedi/jr3pci.idm");
diff --git a/drivers/staging/comedi/drivers/jr3_pci.h b/drivers/staging/comedi/drivers/jr3_pci.h
new file mode 100644
index 00000000000..20478ae8fad
--- /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(const u32 __iomem *p)
+{
+ return (u16)readl(p);
+}
+
+static inline void set_u16(u32 __iomem *p, u16 val)
+{
+ writel(val, p);
+}
+
+static inline s16 get_s16(const s32 __iomem *p)
+{
+ return (s16)readl(p);
+}
+
+static inline void set_s16(s32 __iomem *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
+};
+
+/* 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
+};
+
+/* 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_lo[0x4000]; /* 0x00000 - 0x10000 */
+ struct jr3_channel data; /* 0x10000 - 0x10c00 */
+ char pad2[0x30000 - 0x00c00]; /* 0x10c00 - 0x40000 */
+ u32 program_hi[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 00000000000..ec43c38958d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ke_counter.c
@@ -0,0 +1,181 @@
+/*
+ * 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 <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * 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: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+/*
+ * PCI BAR 0 Register I/O map
+ */
+#define KE_RESET_REG(x) (0x00 + ((x) * 0x20))
+#define KE_LATCH_REG(x) (0x00 + ((x) * 0x20))
+#define KE_LSB_REG(x) (0x04 + ((x) * 0x20))
+#define KE_MID_REG(x) (0x08 + ((x) * 0x20))
+#define KE_MSB_REG(x) (0x0c + ((x) * 0x20))
+#define KE_SIGN_REG(x) (0x10 + ((x) * 0x20))
+#define KE_OSC_SEL_REG 0xf8
+#define KE_OSC_SEL_EXT (1 << 0)
+#define KE_OSC_SEL_4MHZ (2 << 0)
+#define KE_OSC_SEL_20MHZ (3 << 0)
+#define KE_DO_REG 0xfc
+
+static int ke_counter_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 val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[0];
+
+ /* Order matters */
+ outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan));
+ outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan));
+ outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int ke_counter_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Order matters */
+ inb(dev->iobase + KE_LATCH_REG(chan));
+
+ val = inb(dev->iobase + KE_LSB_REG(chan));
+ val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8);
+ val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16);
+ val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24);
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int ke_counter_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + KE_DO_REG);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int ke_counter_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 3;
+ s->maxdata = 0x01ffffff;
+ s->range_table = &range_unknown;
+ s->insn_read = ke_counter_insn_read;
+ s->insn_write = ke_counter_insn_write;
+
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 3;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ke_counter_do_insn_bits;
+
+ outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG);
+
+ outb(0, dev->iobase + KE_RESET_REG(0));
+ outb(0, dev->iobase + KE_RESET_REG(1));
+ outb(0, dev->iobase + KE_RESET_REG(2));
+
+ return 0;
+}
+
+static struct comedi_driver ke_counter_driver = {
+ .driver_name = "ke_counter",
+ .module = THIS_MODULE,
+ .auto_attach = ke_counter_auto_attach,
+ .detach = comedi_pci_disable,
+};
+
+static int ke_counter_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ke_counter_driver,
+ id->driver_data);
+}
+
+static const struct pci_device_id ke_counter_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ke_counter_pci_table);
+
+static struct pci_driver ke_counter_pci_driver = {
+ .name = "ke_counter",
+ .id_table = ke_counter_pci_table,
+ .probe = ke_counter_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c
new file mode 100644
index 00000000000..25ce2f78db8
--- /dev/null
+++ b/drivers/staging/comedi/drivers/me4000.c
@@ -0,0 +1,1615 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+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 <g.gebhardt@meilhaus.com>)
+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: not applicable, uses PCI auto config
+
+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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+#include "plx9052.h"
+
+#if 0
+/* file removed due to GPL incompatibility */
+#include "me4000_fw.h"
+#endif
+
+/*
+ * ME4000 Register map and bit defines
+ */
+#define ME4000_AO_CHAN(x) ((x) * 0x18)
+
+#define ME4000_AO_CTRL_REG(x) (0x00 + ME4000_AO_CHAN(x))
+#define ME4000_AO_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_AO_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_AO_CTRL_MASK_MODE (3 << 0)
+#define ME4000_AO_CTRL_BIT_STOP (1 << 2)
+#define ME4000_AO_CTRL_BIT_ENABLE_FIFO (1 << 3)
+#define ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG (1 << 4)
+#define ME4000_AO_CTRL_BIT_EX_TRIG_EDGE (1 << 5)
+#define ME4000_AO_CTRL_BIT_IMMEDIATE_STOP (1 << 7)
+#define ME4000_AO_CTRL_BIT_ENABLE_DO (1 << 8)
+#define ME4000_AO_CTRL_BIT_ENABLE_IRQ (1 << 9)
+#define ME4000_AO_CTRL_BIT_RESET_IRQ (1 << 10)
+#define ME4000_AO_STATUS_REG(x) (0x04 + ME4000_AO_CHAN(x))
+#define ME4000_AO_STATUS_BIT_FSM (1 << 0)
+#define ME4000_AO_STATUS_BIT_FF (1 << 1)
+#define ME4000_AO_STATUS_BIT_HF (1 << 2)
+#define ME4000_AO_STATUS_BIT_EF (1 << 3)
+#define ME4000_AO_FIFO_REG(x) (0x08 + ME4000_AO_CHAN(x))
+#define ME4000_AO_SINGLE_REG(x) (0x0c + ME4000_AO_CHAN(x))
+#define ME4000_AO_TIMER_REG(x) (0x10 + ME4000_AO_CHAN(x))
+#define ME4000_AI_CTRL_REG 0x74
+#define ME4000_AI_STATUS_REG 0x74
+#define ME4000_AI_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_AI_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_AI_CTRL_BIT_MODE_2 (1 << 2)
+#define ME4000_AI_CTRL_BIT_SAMPLE_HOLD (1 << 3)
+#define ME4000_AI_CTRL_BIT_IMMEDIATE_STOP (1 << 4)
+#define ME4000_AI_CTRL_BIT_STOP (1 << 5)
+#define ME4000_AI_CTRL_BIT_CHANNEL_FIFO (1 << 6)
+#define ME4000_AI_CTRL_BIT_DATA_FIFO (1 << 7)
+#define ME4000_AI_CTRL_BIT_FULLSCALE (1 << 8)
+#define ME4000_AI_CTRL_BIT_OFFSET (1 << 9)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG (1 << 10)
+#define ME4000_AI_CTRL_BIT_EX_TRIG (1 << 11)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_FALLING (1 << 12)
+#define ME4000_AI_CTRL_BIT_EX_IRQ (1 << 13)
+#define ME4000_AI_CTRL_BIT_EX_IRQ_RESET (1 << 14)
+#define ME4000_AI_CTRL_BIT_LE_IRQ (1 << 15)
+#define ME4000_AI_CTRL_BIT_LE_IRQ_RESET (1 << 16)
+#define ME4000_AI_CTRL_BIT_HF_IRQ (1 << 17)
+#define ME4000_AI_CTRL_BIT_HF_IRQ_RESET (1 << 18)
+#define ME4000_AI_CTRL_BIT_SC_IRQ (1 << 19)
+#define ME4000_AI_CTRL_BIT_SC_IRQ_RESET (1 << 20)
+#define ME4000_AI_CTRL_BIT_SC_RELOAD (1 << 21)
+#define ME4000_AI_STATUS_BIT_EF_CHANNEL (1 << 22)
+#define ME4000_AI_STATUS_BIT_HF_CHANNEL (1 << 23)
+#define ME4000_AI_STATUS_BIT_FF_CHANNEL (1 << 24)
+#define ME4000_AI_STATUS_BIT_EF_DATA (1 << 25)
+#define ME4000_AI_STATUS_BIT_HF_DATA (1 << 26)
+#define ME4000_AI_STATUS_BIT_FF_DATA (1 << 27)
+#define ME4000_AI_STATUS_BIT_LE (1 << 28)
+#define ME4000_AI_STATUS_BIT_FSM (1 << 29)
+#define ME4000_AI_CTRL_BIT_EX_TRIG_BOTH (1 << 31)
+#define ME4000_AI_CHANNEL_LIST_REG 0x78
+#define ME4000_AI_LIST_INPUT_SINGLE_ENDED (0 << 5)
+#define ME4000_AI_LIST_INPUT_DIFFERENTIAL (1 << 5)
+#define ME4000_AI_LIST_RANGE_BIPOLAR_10 (0 << 6)
+#define ME4000_AI_LIST_RANGE_BIPOLAR_2_5 (1 << 6)
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_10 (2 << 6)
+#define ME4000_AI_LIST_RANGE_UNIPOLAR_2_5 (3 << 6)
+#define ME4000_AI_LIST_LAST_ENTRY (1 << 8)
+#define ME4000_AI_DATA_REG 0x7c
+#define ME4000_AI_CHAN_TIMER_REG 0x80
+#define ME4000_AI_CHAN_PRE_TIMER_REG 0x84
+#define ME4000_AI_SCAN_TIMER_LOW_REG 0x88
+#define ME4000_AI_SCAN_TIMER_HIGH_REG 0x8c
+#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG 0x90
+#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG 0x94
+#define ME4000_AI_START_REG 0x98
+#define ME4000_IRQ_STATUS_REG 0x9c
+#define ME4000_IRQ_STATUS_BIT_EX (1 << 0)
+#define ME4000_IRQ_STATUS_BIT_LE (1 << 1)
+#define ME4000_IRQ_STATUS_BIT_AI_HF (1 << 2)
+#define ME4000_IRQ_STATUS_BIT_AO_0_HF (1 << 3)
+#define ME4000_IRQ_STATUS_BIT_AO_1_HF (1 << 4)
+#define ME4000_IRQ_STATUS_BIT_AO_2_HF (1 << 5)
+#define ME4000_IRQ_STATUS_BIT_AO_3_HF (1 << 6)
+#define ME4000_IRQ_STATUS_BIT_SC (1 << 7)
+#define ME4000_DIO_PORT_0_REG 0xa0
+#define ME4000_DIO_PORT_1_REG 0xa4
+#define ME4000_DIO_PORT_2_REG 0xa8
+#define ME4000_DIO_PORT_3_REG 0xac
+#define ME4000_DIO_DIR_REG 0xb0
+#define ME4000_AO_LOADSETREG_XX 0xb4
+#define ME4000_DIO_CTRL_REG 0xb8
+#define ME4000_DIO_CTRL_BIT_MODE_0 (1 << 0)
+#define ME4000_DIO_CTRL_BIT_MODE_1 (1 << 1)
+#define ME4000_DIO_CTRL_BIT_MODE_2 (1 << 2)
+#define ME4000_DIO_CTRL_BIT_MODE_3 (1 << 3)
+#define ME4000_DIO_CTRL_BIT_MODE_4 (1 << 4)
+#define ME4000_DIO_CTRL_BIT_MODE_5 (1 << 5)
+#define ME4000_DIO_CTRL_BIT_MODE_6 (1 << 6)
+#define ME4000_DIO_CTRL_BIT_MODE_7 (1 << 7)
+#define ME4000_DIO_CTRL_BIT_FUNCTION_0 (1 << 8)
+#define ME4000_DIO_CTRL_BIT_FUNCTION_1 (1 << 9)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_0 (1 << 10)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_1 (1 << 11)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_2 (1 << 12)
+#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_3 (1 << 13)
+#define ME4000_AO_DEMUX_ADJUST_REG 0xbc
+#define ME4000_AO_DEMUX_ADJUST_VALUE 0x4c
+#define ME4000_AI_SAMPLE_COUNTER_REG 0xc0
+
+#define ME4000_AI_FIFO_COUNT 2048
+
+#define ME4000_AI_MIN_TICKS 66
+#define ME4000_AI_MIN_SAMPLE_TIME 2000
+#define ME4000_AI_BASE_FREQUENCY (unsigned int) 33E6
+
+#define ME4000_AI_CHANNEL_LIST_COUNT 1024
+
+struct me4000_info {
+ unsigned long plx_regbase;
+ unsigned long timer_regbase;
+
+ unsigned int ao_readback[4];
+};
+
+enum me4000_boardid {
+ BOARD_ME4650,
+ BOARD_ME4660,
+ BOARD_ME4660I,
+ BOARD_ME4660S,
+ BOARD_ME4660IS,
+ BOARD_ME4670,
+ BOARD_ME4670I,
+ BOARD_ME4670S,
+ BOARD_ME4670IS,
+ BOARD_ME4680,
+ BOARD_ME4680I,
+ BOARD_ME4680S,
+ BOARD_ME4680IS,
+};
+
+struct me4000_board {
+ const char *name;
+ int ao_nchan;
+ int ao_fifo;
+ int ai_nchan;
+ int ai_diff_nchan;
+ int ai_sh_nchan;
+ int ex_trig_analog;
+ int dio_nchan;
+ int has_counter;
+};
+
+static const struct me4000_board me4000_boards[] = {
+ [BOARD_ME4650] = {
+ .name = "ME-4650",
+ .ai_nchan = 16,
+ .dio_nchan = 32,
+ },
+ [BOARD_ME4660] = {
+ .name = "ME-4660",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660I] = {
+ .name = "ME-4660i",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660S] = {
+ .name = "ME-4660s",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4660IS] = {
+ .name = "ME-4660is",
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670] = {
+ .name = "ME-4670",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670I] = {
+ .name = "ME-4670i",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670S] = {
+ .name = "ME-4670s",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4670IS] = {
+ .name = "ME-4670is",
+ .ao_nchan = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680] = {
+ .name = "ME-4680",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680I] = {
+ .name = "ME-4680i",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680S] = {
+ .name = "ME-4680s",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+ [BOARD_ME4680IS] = {
+ .name = "ME-4680is",
+ .ao_nchan = 4,
+ .ao_fifo = 4,
+ .ai_nchan = 32,
+ .ai_diff_nchan = 16,
+ .ai_sh_nchan = 8,
+ .ex_trig_analog = 1,
+ .dio_nchan = 32,
+ .has_counter = 1,
+ },
+};
+
+static const struct comedi_lrange me4000_ai_range = {
+ 4, {
+ UNI_RANGE(2.5),
+ UNI_RANGE(10),
+ BIP_RANGE(2.5),
+ BIP_RANGE(10)
+ }
+};
+
+#define FIRMWARE_NOT_AVAILABLE 1
+#if FIRMWARE_NOT_AVAILABLE
+extern unsigned char *xilinx_firm;
+#endif
+
+static int xilinx_download(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct me4000_info *info = dev->private;
+ unsigned long xilinx_iobase = pci_resource_start(pcidev, 5);
+ u32 value = 0;
+ wait_queue_head_t queue;
+ int idx = 0;
+ int size = 0;
+ unsigned int intcsr;
+
+ if (!xilinx_iobase)
+ return -ENODEV;
+
+ init_waitqueue_head(&queue);
+
+ /*
+ * Set PLX local interrupt 2 polarity to high.
+ * Interrupt is thrown by init pin of xilinx.
+ */
+ outl(PLX9052_INTCSR_LI2POL, info->plx_regbase + PLX9052_INTCSR);
+
+ /* Set /CS and /WRITE of the Xilinx */
+ value = inl(info->plx_regbase + PLX9052_CNTRL);
+ value |= PLX9052_CNTRL_UIO2_DATA;
+ outl(value, info->plx_regbase + PLX9052_CNTRL);
+
+ /* Init Xilinx with CS1 */
+ inb(xilinx_iobase + 0xC8);
+
+ /* Wait until /INIT pin is set */
+ udelay(20);
+ intcsr = inl(info->plx_regbase + PLX9052_INTCSR);
+ if (!(intcsr & PLX9052_INTCSR_LI2STAT)) {
+ dev_err(dev->class_dev, "Can't init Xilinx\n");
+ return -EIO;
+ }
+
+ /* Reset /CS and /WRITE of the Xilinx */
+ value = inl(info->plx_regbase + PLX9052_CNTRL);
+ value &= ~PLX9052_CNTRL_UIO2_DATA;
+ outl(value, info->plx_regbase + PLX9052_CNTRL);
+ if (FIRMWARE_NOT_AVAILABLE) {
+ dev_err(dev->class_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], xilinx_iobase);
+ udelay(10);
+
+ /* Check if BUSY flag is low */
+ if (inl(info->plx_regbase + PLX9052_CNTRL) & PLX9052_CNTRL_UIO1_DATA) {
+ dev_err(dev->class_dev,
+ "Xilinx is still busy (idx = %d)\n",
+ idx);
+ return -EIO;
+ }
+ }
+ }
+
+ /* If done flag is high download was successful */
+ if (inl(info->plx_regbase + PLX9052_CNTRL) & PLX9052_CNTRL_UIO0_DATA) {
+ } else {
+ dev_err(dev->class_dev, "DONE flag is not set\n");
+ dev_err(dev->class_dev, "Download not successful\n");
+ return -EIO;
+ }
+
+ /* Set /CS and /WRITE */
+ value = inl(info->plx_regbase + PLX9052_CNTRL);
+ value |= PLX9052_CNTRL_UIO2_DATA;
+ outl(value, info->plx_regbase + PLX9052_CNTRL);
+
+ return 0;
+}
+
+static void me4000_reset(struct comedi_device *dev)
+{
+ struct me4000_info *info = dev->private;
+ unsigned int val;
+ int chan;
+
+ /* Make a hardware reset */
+ val = inl(info->plx_regbase + PLX9052_CNTRL);
+ val |= PLX9052_CNTRL_PCI_RESET;
+ outl(val, info->plx_regbase + PLX9052_CNTRL);
+ val &= ~PLX9052_CNTRL_PCI_RESET;
+ outl(val , info->plx_regbase + PLX9052_CNTRL);
+
+ /* 0x8000 to the DACs means an output voltage of 0V */
+ for (chan = 0; chan < 4; chan++)
+ outl(0x8000, dev->iobase + ME4000_AO_SINGLE_REG(chan));
+
+ /* Set both stop bits in the analog input control register */
+ outl(ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP,
+ dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Set both stop bits in the analog output control register */
+ val = ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP;
+ for (chan = 0; chan < 4; chan++)
+ outl(val, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Enable interrupts on the PLX */
+ outl(PLX9052_INTCSR_LI1ENAB |
+ PLX9052_INTCSR_LI1POL |
+ PLX9052_INTCSR_PCIENAB, info->plx_regbase + PLX9052_INTCSR);
+
+ /* Set the adustment register for AO demux */
+ outl(ME4000_AO_DEMUX_ADJUST_VALUE,
+ dev->iobase + ME4000_AO_DEMUX_ADJUST_REG);
+
+ /*
+ * Set digital I/O direction for port 0
+ * to output on isolated versions
+ */
+ if (!(inl(dev->iobase + ME4000_DIO_DIR_REG) & 0x1))
+ outl(0x1, dev->iobase + ME4000_DIO_CTRL_REG);
+}
+
+/*=============================================================================
+ Analog input section
+ ===========================================================================*/
+
+static int me4000_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *subdevice,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct me4000_board *thisboard = comedi_board(dev);
+ int chan = CR_CHAN(insn->chanspec);
+ int rang = CR_RANGE(insn->chanspec);
+ int aref = CR_AREF(insn->chanspec);
+
+ unsigned int entry = 0;
+ unsigned int tmp;
+ unsigned int lval;
+
+ if (insn->n == 0) {
+ return 0;
+ } else if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length %d\n",
+ 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:
+ dev_err(dev->class_dev, "Invalid range specified\n");
+ return -EINVAL;
+ }
+
+ switch (aref) {
+ case AREF_GROUND:
+ case AREF_COMMON:
+ if (chan >= thisboard->ai_nchan) {
+ dev_err(dev->class_dev,
+ "Analog input is not available\n");
+ return -EINVAL;
+ }
+ entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan;
+ break;
+
+ case AREF_DIFF:
+ if (rang == 0 || rang == 1) {
+ dev_err(dev->class_dev,
+ "Range must be bipolar when aref = diff\n");
+ return -EINVAL;
+ }
+
+ if (chan >= thisboard->ai_diff_nchan) {
+ dev_err(dev->class_dev,
+ "Analog input is not available\n");
+ return -EINVAL;
+ }
+ entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan;
+ break;
+ default:
+ dev_err(dev->class_dev, "Invalid aref specified\n");
+ return -EINVAL;
+ }
+
+ entry |= ME4000_AI_LIST_LAST_ENTRY;
+
+ /* Clear channel list, data fifo and both stop bits */
+ tmp = inl(dev->iobase + ME4000_AI_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);
+ outl(tmp, dev->iobase + ME4000_AI_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);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Enable channel list and data fifo */
+ tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Generate channel list entry */
+ outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);
+
+ /* Set the timer to maximum sample rate */
+ outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_TIMER_REG);
+ outl(ME4000_AI_MIN_TICKS, dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);
+
+ /* Start conversion by dummy read */
+ inl(dev->iobase + ME4000_AI_START_REG);
+
+ /* Wait until ready */
+ udelay(10);
+ if (!(inl(dev->iobase + ME4000_AI_STATUS_REG) &
+ ME4000_AI_STATUS_BIT_EF_DATA)) {
+ dev_err(dev->class_dev, "Value not available after wait\n");
+ return -EIO;
+ }
+
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ data[0] = lval ^ 0x8000;
+
+ return 1;
+}
+
+static int me4000_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int tmp;
+
+ /* Stop any running conversion */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Clear the control register */
+ outl(0x0, dev->iobase + ME4000_AI_CTRL_REG);
+
+ return 0;
+}
+
+static int me4000_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct me4000_board *board = comedi_board(dev);
+ unsigned int max_diff_chan = board->ai_diff_nchan;
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "Mode is not equal for all entries\n");
+ return -EINVAL;
+ }
+
+ if (aref == SDF_DIFF) {
+ if (chan >= max_diff_chan) {
+ dev_dbg(dev->class_dev,
+ "Channel number to high\n");
+ return -EINVAL;
+ }
+
+ if (!comedi_range_is_bipolar(s, range)) {
+ dev_dbg(dev->class_dev,
+ "Bipolar is not selected in differential mode\n");
+ 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;
+
+ *init_ticks = 0;
+ *scan_ticks = 0;
+ *chan_ticks = 0;
+
+ if (cmd->start_arg) {
+ *init_ticks = (cmd->start_arg * 33) / 1000;
+ rest = (cmd->start_arg * 33) % 1000;
+
+ if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) {
+ if (rest > 33)
+ (*init_ticks)++;
+ } else if ((cmd->flags & TRIG_ROUND_MASK) == 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_MASK) == TRIG_ROUND_NEAREST) {
+ if (rest > 33)
+ (*scan_ticks)++;
+ } else if ((cmd->flags & TRIG_ROUND_MASK) == 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_MASK) == TRIG_ROUND_NEAREST) {
+ if (rest > 33)
+ (*chan_ticks)++;
+ } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_UP) {
+ if (rest)
+ (*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)
+{
+ outl(init_ticks - 1, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG);
+ outl(0x0, dev->iobase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG);
+
+ if (scan_ticks) {
+ outl(scan_ticks - 1, dev->iobase + ME4000_AI_SCAN_TIMER_LOW_REG);
+ outl(0x0, dev->iobase + ME4000_AI_SCAN_TIMER_HIGH_REG);
+ }
+
+ outl(chan_ticks - 1, dev->iobase + ME4000_AI_CHAN_PRE_TIMER_REG);
+ outl(chan_ticks - 1, dev->iobase + ME4000_AI_CHAN_TIMER_REG);
+}
+
+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;
+
+ 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;
+
+ outl(entry, dev->iobase + ME4000_AI_CHANNEL_LIST_REG);
+ }
+
+ return 0;
+}
+
+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 int tmp = 0;
+
+ /* Write timer arguments */
+ ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks);
+
+ /* Reset control register */
+ outl(tmp, dev->iobase + ME4000_AI_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) {
+ outl(cmd->chanlist_len * cmd->stop_arg,
+ dev->iobase + ME4000_AI_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) {
+ outl(cmd->scan_end_arg,
+ dev->iobase + ME4000_AI_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 */
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Write the channel list */
+ ai_write_chanlist(dev, s, cmd);
+
+ 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;
+
+ /* 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 */
+ inl(dev->iobase + ME4000_AI_START_REG);
+
+ return 0;
+}
+
+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;
+
+ /* 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);
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src,
+ TRIG_NONE | TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE | TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_end_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ 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 {
+ err |= -EINVAL;
+ }
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->chanlist_len < 1) {
+ cmd->chanlist_len = 1;
+ err |= -EINVAL;
+ }
+ if (init_ticks < 66) {
+ cmd->start_arg = 2000;
+ err |= -EINVAL;
+ }
+ if (scan_ticks && scan_ticks < 67) {
+ cmd->scan_begin_arg = 2031;
+ err |= -EINVAL;
+ }
+ if (chan_ticks < 66) {
+ cmd->convert_arg = 2000;
+ err |= -EINVAL;
+ }
+
+ 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+
+ /* 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ cmd->convert_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (scan_ticks <= cmd->chanlist_len * chan_ticks) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+
+ /* 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ if (chan_ticks < ME4000_AI_MIN_TICKS) {
+ dev_err(dev->class_dev, "Invalid convert arg\n");
+ 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) {
+ dev_err(dev->class_dev, "Invalid start arg\n");
+ cmd->start_arg = 2000; /* 66 ticks at least */
+ err++;
+ }
+ }
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (cmd->stop_arg == 0) {
+ dev_err(dev->class_dev, "Invalid stop arg\n");
+ cmd->stop_arg = 1;
+ err++;
+ }
+ }
+ if (cmd->scan_end_src == TRIG_COUNT) {
+ if (cmd->scan_end_arg == 0) {
+ dev_err(dev->class_dev, "Invalid scan end arg\n");
+ cmd->scan_end_arg = 1;
+ err++;
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= me4000_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ 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->read_subdev;
+ int i;
+ int c = 0;
+ unsigned int lval;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
+ ME4000_IRQ_STATUS_BIT_AI_HF) {
+ /* Read status register to find out what happened */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+
+ if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) &&
+ !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) &&
+ (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+ 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);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+
+ dev_err(dev->class_dev, "FIFO overflow\n");
+ } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA)
+ && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA)
+ && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) {
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ c = ME4000_AI_FIFO_COUNT / 2;
+ } else {
+ dev_err(dev->class_dev,
+ "Can't determine state of fifo\n");
+ 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);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+
+ dev_err(dev->class_dev, "Undefined FIFO state\n");
+ }
+
+ for (i = 0; i < c; i++) {
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ lval ^= 0x8000;
+
+ if (!comedi_buf_put(s, 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);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ s->async->events |= COMEDI_CB_OVERFLOW;
+
+ dev_err(dev->class_dev, "Buffer overflow\n");
+
+ break;
+ }
+ }
+
+ /* Work is done, so reset the interrupt */
+ tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ }
+
+ if (inl(dev->iobase + ME4000_IRQ_STATUS_REG) &
+ ME4000_IRQ_STATUS_BIT_SC) {
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA;
+
+ /*
+ * Acquisition is complete, so stop
+ * conversion and disable all interrupts
+ */
+ tmp = inl(dev->iobase + ME4000_AI_CTRL_REG);
+ tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP;
+ tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ);
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+
+ /* Poll data until fifo empty */
+ while (inl(dev->iobase + ME4000_AI_CTRL_REG) &
+ ME4000_AI_STATUS_BIT_EF_DATA) {
+ /* Read value from data fifo */
+ lval = inl(dev->iobase + ME4000_AI_DATA_REG) & 0xFFFF;
+ lval ^= 0x8000;
+
+ if (!comedi_buf_put(s, lval)) {
+ dev_err(dev->class_dev, "Buffer overflow\n");
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ break;
+ }
+ }
+
+ /* Work is done, so reset the interrupt */
+ tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET;
+ outl(tmp, dev->iobase + ME4000_AI_CTRL_REG);
+ }
+
+ 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)
+{
+ const struct me4000_board *thisboard = comedi_board(dev);
+ struct me4000_info *info = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int rang = CR_RANGE(insn->chanspec);
+ int aref = CR_AREF(insn->chanspec);
+ unsigned int tmp;
+
+ if (insn->n == 0) {
+ return 0;
+ } else if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length %d\n",
+ insn->n);
+ return -EINVAL;
+ }
+
+ if (chan >= thisboard->ao_nchan) {
+ dev_err(dev->class_dev, "Invalid channel %d\n", insn->n);
+ return -EINVAL;
+ }
+
+ if (rang != 0) {
+ dev_err(dev->class_dev, "Invalid range %d\n", insn->n);
+ return -EINVAL;
+ }
+
+ if (aref != AREF_GROUND && aref != AREF_COMMON) {
+ dev_err(dev->class_dev, "Invalid aref %d\n", insn->n);
+ return -EINVAL;
+ }
+
+ /* Stop any running conversion */
+ tmp = inl(dev->iobase + ME4000_AO_CTRL_REG(chan));
+ tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP;
+ outl(tmp, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Clear control register and set to single mode */
+ outl(0x0, dev->iobase + ME4000_AO_CTRL_REG(chan));
+
+ /* Write data value */
+ outl(data[0], dev->iobase + ME4000_AO_SINGLE_REG(chan));
+
+ /* Store in the mirror */
+ info->ao_readback[chan] = 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)
+{
+ struct me4000_info *info = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+
+ if (insn->n == 0) {
+ return 0;
+ } else if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length\n");
+ return -EINVAL;
+ }
+
+ data[0] = info->ao_readback[chan];
+
+ return 1;
+}
+
+static int me4000_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outl((s->state >> 0) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_0_REG);
+ outl((s->state >> 8) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_1_REG);
+ outl((s->state >> 16) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_2_REG);
+ outl((s->state >> 24) & 0xFF,
+ dev->iobase + ME4000_DIO_PORT_3_REG);
+ }
+
+ data[1] = ((inl(dev->iobase + ME4000_DIO_PORT_0_REG) & 0xFF) << 0) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_1_REG) & 0xFF) << 8) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_2_REG) & 0xFF) << 16) |
+ ((inl(dev->iobase + ME4000_DIO_PORT_3_REG) & 0xFF) << 24);
+
+ return insn->n;
+}
+
+static int me4000_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned int tmp;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x000000ff;
+ else if (chan < 16)
+ mask = 0x0000ff00;
+ else if (chan < 24)
+ mask = 0x00ff0000;
+ else
+ mask = 0xff000000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ tmp = inl(dev->iobase + ME4000_DIO_CTRL_REG);
+ tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | ME4000_DIO_CTRL_BIT_MODE_1 |
+ ME4000_DIO_CTRL_BIT_MODE_2 | ME4000_DIO_CTRL_BIT_MODE_3 |
+ ME4000_DIO_CTRL_BIT_MODE_4 | ME4000_DIO_CTRL_BIT_MODE_5 |
+ ME4000_DIO_CTRL_BIT_MODE_6 | ME4000_DIO_CTRL_BIT_MODE_7);
+ if (s->io_bits & 0x000000ff)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
+ if (s->io_bits & 0x0000ff00)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_2;
+ if (s->io_bits & 0x00ff0000)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_4;
+ if (s->io_bits & 0xff000000)
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_6;
+
+ /*
+ * 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 (inl(dev->iobase + ME4000_DIO_DIR_REG)) {
+ s->io_bits |= 0x000000ff;
+ s->io_bits &= ~0x0000ff00;
+ tmp |= ME4000_DIO_CTRL_BIT_MODE_0;
+ tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 |
+ ME4000_DIO_CTRL_BIT_MODE_3);
+ }
+
+ outl(tmp, dev->iobase + ME4000_DIO_CTRL_REG);
+
+ return insn->n;
+}
+
+/*=============================================================================
+ Counter section
+ ===========================================================================*/
+
+static int me4000_cnt_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me4000_info *info = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int err;
+
+ switch (data[0]) {
+ case GPCT_RESET:
+ if (insn->n != 1)
+ return -EINVAL;
+
+ err = i8254_set_mode(info->timer_regbase, 0, chan,
+ I8254_MODE0 | I8254_BINARY);
+ if (err)
+ return err;
+ i8254_write(info->timer_regbase, 0, chan, 0);
+ break;
+ case GPCT_SET_OPERATION:
+ if (insn->n != 2)
+ return -EINVAL;
+
+ err = i8254_set_mode(info->timer_regbase, 0, chan,
+ (data[1] << 1) | I8254_BINARY);
+ if (err)
+ return err;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int me4000_cnt_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct me4000_info *info = dev->private;
+
+ if (insn->n == 0)
+ return 0;
+
+ if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length %d\n",
+ insn->n);
+ return -EINVAL;
+ }
+
+ data[0] = i8254_read(info->timer_regbase, 0, insn->chanspec);
+
+ return 1;
+}
+
+static int me4000_cnt_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct me4000_info *info = dev->private;
+
+ if (insn->n == 0) {
+ return 0;
+ } else if (insn->n > 1) {
+ dev_err(dev->class_dev, "Invalid instruction length %d\n",
+ insn->n);
+ return -EINVAL;
+ }
+
+ i8254_write(info->timer_regbase, 0, insn->chanspec, data[0]);
+
+ return 1;
+}
+
+static int me4000_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct me4000_board *thisboard = NULL;
+ struct me4000_info *info;
+ struct comedi_subdevice *s;
+ int result;
+
+ if (context < ARRAY_SIZE(me4000_boards))
+ thisboard = &me4000_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ info = comedi_alloc_devpriv(dev, sizeof(*info));
+ if (!info)
+ return -ENOMEM;
+
+ result = comedi_pci_enable(dev);
+ if (result)
+ return result;
+
+ info->plx_regbase = pci_resource_start(pcidev, 1);
+ dev->iobase = pci_resource_start(pcidev, 2);
+ info->timer_regbase = pci_resource_start(pcidev, 3);
+ if (!info->plx_regbase || !dev->iobase || !info->timer_regbase)
+ return -ENODEV;
+
+ result = xilinx_download(dev);
+ if (result)
+ return result;
+
+ me4000_reset(dev);
+
+ if (pcidev->irq > 0) {
+ result = request_irq(pcidev->irq, me4000_ai_isr, IRQF_SHARED,
+ dev->board_name, dev);
+ if (result == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ result = comedi_alloc_subdevices(dev, 4);
+ if (result)
+ return result;
+
+ /*=========================================================================
+ Analog input subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[0];
+
+ if (thisboard->ai_nchan) {
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags =
+ SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
+ s->n_chan = thisboard->ai_nchan;
+ 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 (dev->irq) {
+ 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 {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /*=========================================================================
+ Analog output subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[1];
+
+ if (thisboard->ao_nchan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_COMMON | SDF_GROUND;
+ s->n_chan = thisboard->ao_nchan;
+ s->maxdata = 0xFFFF; /* 16 bit DAC */
+ s->range_table = &range_bipolar10;
+ 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_nchan) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = thisboard->dio_nchan;
+ 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 (!inl(dev->iobase + ME4000_DIO_DIR_REG)) {
+ s->io_bits |= 0xFF;
+ outl(ME4000_DIO_CTRL_BIT_MODE_0,
+ dev->iobase + ME4000_DIO_DIR_REG);
+ }
+
+ /*=========================================================================
+ Counter subdevice
+ ========================================================================*/
+
+ s = &dev->subdevices[3];
+
+ if (thisboard->has_counter) {
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 3;
+ 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 void me4000_detach(struct comedi_device *dev)
+{
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (dev->iobase)
+ me4000_reset(dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver me4000_driver = {
+ .driver_name = "me4000",
+ .module = THIS_MODULE,
+ .auto_attach = me4000_auto_attach,
+ .detach = me4000_detach,
+};
+
+static int me4000_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &me4000_driver, id->driver_data);
+}
+
+static const struct pci_device_id me4000_pci_table[] = {
+ { PCI_VDEVICE(MEILHAUS, 0x4650), BOARD_ME4650 },
+ { PCI_VDEVICE(MEILHAUS, 0x4660), BOARD_ME4660 },
+ { PCI_VDEVICE(MEILHAUS, 0x4661), BOARD_ME4660I },
+ { PCI_VDEVICE(MEILHAUS, 0x4662), BOARD_ME4660S },
+ { PCI_VDEVICE(MEILHAUS, 0x4663), BOARD_ME4660IS },
+ { PCI_VDEVICE(MEILHAUS, 0x4670), BOARD_ME4670 },
+ { PCI_VDEVICE(MEILHAUS, 0x4671), BOARD_ME4670I },
+ { PCI_VDEVICE(MEILHAUS, 0x4672), BOARD_ME4670S },
+ { PCI_VDEVICE(MEILHAUS, 0x4673), BOARD_ME4670IS },
+ { PCI_VDEVICE(MEILHAUS, 0x4680), BOARD_ME4680 },
+ { PCI_VDEVICE(MEILHAUS, 0x4681), BOARD_ME4680I },
+ { PCI_VDEVICE(MEILHAUS, 0x4682), BOARD_ME4680S },
+ { PCI_VDEVICE(MEILHAUS, 0x4683), BOARD_ME4680IS },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, me4000_pci_table);
+
+static struct pci_driver me4000_pci_driver = {
+ .name = "me4000",
+ .id_table = me4000_pci_table,
+ .probe = me4000_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(me4000_driver, me4000_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/me_daq.c b/drivers/staging/comedi/drivers/me_daq.c
new file mode 100644
index 00000000000..0ff126b1fdf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/me_daq.c
@@ -0,0 +1,603 @@
+/*
+ * comedi/drivers/me_daq.c
+ * Hardware driver for Meilhaus data acquisition cards:
+ * ME-2000i, ME-2600i, ME-3000vm1
+ *
+ * Copyright (C) 2002 Michael Hillmann <hillmann@syscongroup.de>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: me_daq
+ * Description: Meilhaus PCI data acquisition cards
+ * Devices: (Meilhaus) ME-2600i [me-2600i]
+ * (Meilhaus) ME-2000i [me-2000i]
+ * Author: Michael Hillmann <hillmann@syscongroup.de>
+ * Status: experimental
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ *
+ * Supports:
+ * Analog Input, Analog Output, Digital I/O
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedidev.h"
+
+#include "plx9052.h"
+
+#define ME2600_FIRMWARE "me2600_firmware.bin"
+
+#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 | - */
+
+static const struct comedi_lrange me_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 me_ao_range = {
+ 3, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+enum me_boardid {
+ BOARD_ME2600,
+ BOARD_ME2000,
+};
+
+struct me_board {
+ const char *name;
+ int needs_firmware;
+ int has_ao;
+};
+
+static const struct me_board me_boards[] = {
+ [BOARD_ME2600] = {
+ .name = "me-2600i",
+ .needs_firmware = 1,
+ .has_ao = 1,
+ },
+ [BOARD_ME2000] = {
+ .name = "me-2000i",
+ },
+};
+
+struct me_private_data {
+ void __iomem *plx_regbase; /* PLX configuration base address */
+ void __iomem *me_regbase; /* Base address of the Meilhaus card */
+
+ 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 */
+};
+
+static inline void sleep(unsigned sec)
+{
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(sec * HZ);
+}
+
+static int me_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 16)
+ mask = 0x0000ffff;
+ else
+ mask = 0xffff0000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (s->io_bits & 0x0000ffff)
+ devpriv->control_2 |= ENABLE_PORT_A;
+ else
+ devpriv->control_2 &= ~ENABLE_PORT_A;
+ if (s->io_bits & 0xffff0000)
+ devpriv->control_2 |= ENABLE_PORT_B;
+ else
+ devpriv->control_2 &= ~ENABLE_PORT_B;
+
+ writew(devpriv->control_2, devpriv->me_regbase + ME_CONTROL_2);
+
+ return insn->n;
+}
+
+static int me_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ void __iomem *mmio_porta = dev_private->me_regbase + ME_DIO_PORT_A;
+ void __iomem *mmio_portb = dev_private->me_regbase + ME_DIO_PORT_B;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x0000ffff)
+ writew((s->state & 0xffff), mmio_porta);
+ if (mask & 0xffff0000)
+ writew(((s->state >> 16) & 0xffff), mmio_portb);
+ }
+
+ if (s->io_bits & 0x0000ffff)
+ val = s->state & 0xffff;
+ else
+ val = readw(mmio_porta);
+
+ if (s->io_bits & 0xffff0000)
+ val |= (s->state & 0xffff0000);
+ else
+ val |= (readw(mmio_portb) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int me_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int status;
+
+ status = readw(dev_private->me_regbase + ME_STATUS);
+ if ((status & 0x0004) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int me_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int rang = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned short val;
+ int ret;
+
+ /* 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 */
+ val = chan & 0x0f; /* b3:b0 channel */
+ val |= (rang & 0x03) << 4; /* b5:b4 gain */
+ val |= (rang & 0x04) << 4; /* b6 polarity */
+ val |= ((aref & AREF_DIFF) ? 0x80 : 0); /* b7 differential */
+ writew(val & 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 */
+ ret = comedi_timeout(dev, s, insn, me_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* get value from ADC fifo */
+ val = readw(dev_private->me_regbase + ME_READ_AD_FIFO);
+ val = (val ^ 0x800) & 0x0fff;
+ data[0] = val;
+
+ /* stop any running conversion */
+ dev_private->control_1 &= 0xFFFC;
+ writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1);
+
+ return 1;
+}
+
+static int me_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int rang = CR_RANGE(insn->chanspec);
+ 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++) {
+ /* 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++) {
+ 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 insn->n;
+}
+
+static int me_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = dev_private->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int me2600_xilinx_download(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct me_private_data *dev_private = dev->private;
+ unsigned int value;
+ unsigned int file_length;
+ unsigned int i;
+
+ /* disable irq's on PLX */
+ writel(0x00, dev_private->plx_regbase + PLX9052_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 (size < 16)
+ return -EINVAL;
+
+ file_length = (((unsigned int)data[0] & 0xff) << 24) +
+ (((unsigned int)data[1] & 0xff) << 16) +
+ (((unsigned int)data[2] & 0xff) << 8) +
+ ((unsigned int)data[3] & 0xff);
+
+ /*
+ * Loop for writing firmware byte by byte to xilinx
+ * Firmware data start at offset 16
+ */
+ for (i = 0; i < file_length; i++)
+ writeb((data[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 + PLX9052_INTCSR);
+ if (value & PLX9052_INTCSR_LI2STAT) {
+ /* Disable interrupt */
+ writel(0x00, dev_private->plx_regbase + PLX9052_INTCSR);
+ dev_err(dev->class_dev, "Xilinx download failed\n");
+ return -EIO;
+ }
+
+ /* Wait until the Xilinx is ready for real work */
+ sleep(1);
+
+ /* Enable PLX-Interrupts */
+ writel(PLX9052_INTCSR_LI1ENAB |
+ PLX9052_INTCSR_LI1POL |
+ PLX9052_INTCSR_PCIENAB,
+ dev_private->plx_regbase + PLX9052_INTCSR);
+
+ return 0;
+}
+
+static int me_reset(struct comedi_device *dev)
+{
+ struct me_private_data *dev_private = dev->private;
+
+ /* 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;
+}
+
+static int me_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct me_board *board = NULL;
+ struct me_private_data *dev_private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(me_boards))
+ board = &me_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private));
+ if (!dev_private)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ dev_private->plx_regbase = pci_ioremap_bar(pcidev, 0);
+ if (!dev_private->plx_regbase)
+ return -ENOMEM;
+
+ dev_private->me_regbase = pci_ioremap_bar(pcidev, 2);
+ if (!dev_private->me_regbase)
+ return -ENOMEM;
+
+ /* Download firmware and reset card */
+ if (board->needs_firmware) {
+ ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev,
+ ME2600_FIRMWARE,
+ me2600_xilinx_download, 0);
+ if (ret < 0)
+ return ret;
+ }
+ me_reset(dev);
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_COMMON;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 16;
+ s->range_table = &me_ai_range;
+ s->insn_read = me_ai_insn_read;
+
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_COMMON;
+ s->n_chan = 4;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = 4;
+ s->range_table = &me_ao_range;
+ s->insn_read = me_ao_insn_read;
+ s->insn_write = me_ao_insn_write;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITEABLE;
+ s->n_chan = 32;
+ s->maxdata = 1;
+ s->len_chanlist = 32;
+ s->range_table = &range_digital;
+ s->insn_bits = me_dio_insn_bits;
+ s->insn_config = me_dio_insn_config;
+
+ return 0;
+}
+
+static void me_detach(struct comedi_device *dev)
+{
+ struct me_private_data *dev_private = dev->private;
+
+ 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);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver me_daq_driver = {
+ .driver_name = "me_daq",
+ .module = THIS_MODULE,
+ .auto_attach = me_auto_attach,
+ .detach = me_detach,
+};
+
+static int me_daq_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &me_daq_driver, id->driver_data);
+}
+
+static const struct pci_device_id me_daq_pci_table[] = {
+ { PCI_VDEVICE(MEILHAUS, 0x2600), BOARD_ME2600 },
+ { PCI_VDEVICE(MEILHAUS, 0x2000), BOARD_ME2000 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, me_daq_pci_table);
+
+static struct pci_driver me_daq_pci_driver = {
+ .name = "me_daq",
+ .id_table = me_daq_pci_table,
+ .probe = me_daq_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(me_daq_driver, me_daq_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(ME2600_FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/mf6x4.c b/drivers/staging/comedi/drivers/mf6x4.c
new file mode 100644
index 00000000000..a4f7d6f138d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mf6x4.c
@@ -0,0 +1,351 @@
+/*
+ * comedi/drivers/mf6x4.c
+ * Driver for Humusoft MF634 and MF624 Data acquisition cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+/*
+ * Driver: mf6x4
+ * Description: Humusoft MF634 and MF624 Data acquisition card driver
+ * Devices: Humusoft MF634, Humusoft MF624
+ * Author: Rostislav Lisovy <lisovy@gmail.com>
+ * Status: works
+ * Updated:
+ * Configuration Options: none
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "../comedidev.h"
+
+/* Registers present in BAR0 memory region */
+#define MF624_GPIOC_R 0x54
+
+#define MF6X4_GPIOC_EOLC /* End Of Last Conversion */ (1 << 17)
+#define MF6X4_GPIOC_LDAC /* Load DACs */ (1 << 23)
+#define MF6X4_GPIOC_DACEN (1 << 26)
+
+/* BAR1 registers */
+#define MF6X4_DIN_R 0x10
+#define MF6X4_DIN_M 0xff
+#define MF6X4_DOUT_R 0x10
+#define MF6X4_DOUT_M 0xff
+
+#define MF6X4_ADSTART_R 0x20
+#define MF6X4_ADDATA_R 0x00
+#define MF6X4_ADCTRL_R 0x00
+#define MF6X4_ADCTRL_M 0xff
+
+#define MF6X4_DA0_R 0x20
+#define MF6X4_DA1_R 0x22
+#define MF6X4_DA2_R 0x24
+#define MF6X4_DA3_R 0x26
+#define MF6X4_DA4_R 0x28
+#define MF6X4_DA5_R 0x2a
+#define MF6X4_DA6_R 0x2c
+#define MF6X4_DA7_R 0x2e
+/* Map DAC cahnnel id to real HW-dependent offset value */
+#define MF6X4_DAC_R(x) (0x20 + ((x) * 2))
+#define MF6X4_DA_M 0x3fff
+
+/* BAR2 registers */
+#define MF634_GPIOC_R 0x68
+
+enum mf6x4_boardid {
+ BOARD_MF634,
+ BOARD_MF624,
+};
+
+struct mf6x4_board {
+ const char *name;
+ unsigned int bar_nums[3]; /* We need to keep track of the
+ order of BARs used by the cards */
+};
+
+static const struct mf6x4_board mf6x4_boards[] = {
+ [BOARD_MF634] = {
+ .name = "mf634",
+ .bar_nums = {0, 2, 3},
+ },
+ [BOARD_MF624] = {
+ .name = "mf624",
+ .bar_nums = {0, 2, 4},
+ },
+};
+
+struct mf6x4_private {
+ /*
+ * Documentation for both MF634 and MF624 describes registers
+ * present in BAR0, 1 and 2 regions.
+ * The real (i.e. in HW) BAR numbers are different for MF624
+ * and MF634 yet we will call them 0, 1, 2
+ */
+ void __iomem *bar0_mem;
+ void __iomem *bar1_mem;
+ void __iomem *bar2_mem;
+
+ /*
+ * This configuration register has the same function and fields
+ * for both cards however it lies in different BARs on different
+ * offsets -- this variable makes the access easier
+ */
+ void __iomem *gpioc_R;
+
+ /* DAC value cache -- used for insn_read function */
+ int ao_readback[8];
+};
+
+static int mf6x4_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+
+ data[1] = ioread16(devpriv->bar1_mem + MF6X4_DIN_R) & MF6X4_DIN_M;
+
+ return insn->n;
+}
+
+static int mf6x4_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ iowrite16(s->state & MF6X4_DOUT_M,
+ devpriv->bar1_mem + MF6X4_DOUT_R);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int mf6x4_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = ioread32(devpriv->gpioc_R);
+ if (status & MF6X4_GPIOC_EOLC)
+ return 0;
+ return -EBUSY;
+}
+
+static int mf6x4_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+ int d;
+
+ /* Set the ADC channel number in the scan list */
+ iowrite16((1 << chan) & MF6X4_ADCTRL_M,
+ devpriv->bar1_mem + MF6X4_ADCTRL_R);
+
+ for (i = 0; i < insn->n; i++) {
+ /* Trigger ADC conversion by reading ADSTART */
+ ioread16(devpriv->bar1_mem + MF6X4_ADSTART_R);
+
+ ret = comedi_timeout(dev, s, insn, mf6x4_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Read the actual value */
+ d = ioread16(devpriv->bar1_mem + MF6X4_ADDATA_R);
+ d &= s->maxdata;
+ data[i] = d;
+ }
+
+ iowrite16(0x0, devpriv->bar1_mem + MF6X4_ADCTRL_R);
+
+ return insn->n;
+}
+
+static int mf6x4_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint32_t gpioc;
+ int i;
+
+ /* Enable instantaneous update of converters outputs + Enable DACs */
+ gpioc = ioread32(devpriv->gpioc_R);
+ iowrite32((gpioc & ~MF6X4_GPIOC_LDAC) | MF6X4_GPIOC_DACEN,
+ devpriv->gpioc_R);
+
+ for (i = 0; i < insn->n; i++) {
+ iowrite16(data[i] & MF6X4_DA_M,
+ devpriv->bar1_mem + MF6X4_DAC_R(chan));
+
+ devpriv->ao_readback[chan] = data[i];
+ }
+
+ return insn->n;
+}
+
+static int mf6x4_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct mf6x4_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int mf6x4_auto_attach(struct comedi_device *dev, unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct mf6x4_board *board = NULL;
+ struct mf6x4_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(mf6x4_boards))
+ board = &mf6x4_boards[context];
+ else
+ return -ENODEV;
+
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->bar0_mem = pci_ioremap_bar(pcidev, board->bar_nums[0]);
+ if (!devpriv->bar0_mem)
+ return -ENODEV;
+
+ devpriv->bar1_mem = pci_ioremap_bar(pcidev, board->bar_nums[1]);
+ if (!devpriv->bar1_mem)
+ return -ENODEV;
+
+ devpriv->bar2_mem = pci_ioremap_bar(pcidev, board->bar_nums[2]);
+ if (!devpriv->bar2_mem)
+ return -ENODEV;
+
+ if (board == &mf6x4_boards[BOARD_MF634])
+ devpriv->gpioc_R = devpriv->bar2_mem + MF634_GPIOC_R;
+ else
+ devpriv->gpioc_R = devpriv->bar0_mem + MF624_GPIOC_R;
+
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* ADC */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 8;
+ s->maxdata = 0x3fff; /* 14 bits ADC */
+ s->range_table = &range_bipolar10;
+ s->insn_read = mf6x4_ai_insn_read;
+
+ /* DAC */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0x3fff; /* 14 bits DAC */
+ s->range_table = &range_bipolar10;
+ s->insn_write = mf6x4_ao_insn_write;
+ s->insn_read = mf6x4_ao_insn_read;
+
+ /* DIN */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf6x4_di_insn_bits;
+
+ /* DOUT */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf6x4_do_insn_bits;
+
+ return 0;
+}
+
+static void mf6x4_detach(struct comedi_device *dev)
+{
+ struct mf6x4_private *devpriv = dev->private;
+
+ if (devpriv->bar0_mem)
+ iounmap(devpriv->bar0_mem);
+ if (devpriv->bar1_mem)
+ iounmap(devpriv->bar1_mem);
+ if (devpriv->bar2_mem)
+ iounmap(devpriv->bar2_mem);
+
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver mf6x4_driver = {
+ .driver_name = "mf6x4",
+ .module = THIS_MODULE,
+ .auto_attach = mf6x4_auto_attach,
+ .detach = mf6x4_detach,
+};
+
+static int mf6x4_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &mf6x4_driver, id->driver_data);
+}
+
+static const struct pci_device_id mf6x4_pci_table[] = {
+ { PCI_VDEVICE(HUMUSOFT, 0x0634), BOARD_MF634 },
+ { PCI_VDEVICE(HUMUSOFT, 0x0624), BOARD_MF624 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, mf6x4_pci_table);
+
+static struct pci_driver mf6x4_pci_driver = {
+ .name = "mf6x4",
+ .id_table = mf6x4_pci_table,
+ .probe = mf6x4_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(mf6x4_driver, mf6x4_pci_driver);
+
+MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>");
+MODULE_DESCRIPTION("Comedi MF634 and MF624 DAQ cards 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 00000000000..19c029acbc9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mite.c
@@ -0,0 +1,643 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+ 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 */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "mite.h"
+
+#define TOP_OF_PAGE(x) ((x)|(~(PAGE_MASK)))
+
+struct mite_struct *mite_alloc(struct pci_dev *pcidev)
+{
+ struct mite_struct *mite;
+ unsigned int i;
+
+ mite = kzalloc(sizeof(*mite), GFP_KERNEL);
+ if (mite) {
+ spin_lock_init(&mite->lock);
+ mite->pcidev = 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;
+ }
+ }
+ return mite;
+}
+EXPORT_SYMBOL_GPL(mite_alloc);
+
+static void dump_chip_signature(u32 csigr_bits)
+{
+ pr_info("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));
+ pr_info("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));
+}
+
+static 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;
+ int i;
+ u32 csigr_bits;
+ unsigned unknown_dma_burst_bits;
+
+ pci_set_master(mite->pcidev);
+
+ mite->mite_io_addr = pci_ioremap_bar(mite->pcidev, 0);
+ if (!mite->mite_io_addr) {
+ dev_err(&mite->pcidev->dev,
+ "Failed to remap mite io memory address\n");
+ return -ENOMEM;
+ }
+ mite->mite_phys_addr = pci_resource_start(mite->pcidev, 0);
+
+ mite->daq_io_addr = pci_ioremap_bar(mite->pcidev, 1);
+ if (!mite->daq_io_addr) {
+ dev_err(&mite->pcidev->dev,
+ "Failed to remap daq io memory address\n");
+ return -ENOMEM;
+ }
+ mite->daq_phys_addr = pci_resource_start(mite->pcidev, 1);
+ length = pci_resource_len(mite->pcidev, 1);
+
+ if (use_iodwbsr_1) {
+ writel(0, mite->mite_io_addr + MITE_IODWBSR);
+ dev_info(&mite->pcidev->dev,
+ "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) {
+ dev_warn(&mite->pcidev->dev,
+ "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);
+ dev_info(&mite->pcidev->dev, "fifo size is %i.\n", mite->fifo_size);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_setup2);
+
+int mite_setup(struct mite_struct *mite)
+{
+ return mite_setup2(mite, 0);
+}
+EXPORT_SYMBOL_GPL(mite_setup);
+
+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)
+ mite->mite_phys_addr = 0;
+}
+EXPORT_SYMBOL_GPL(mite_unsetup);
+
+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;
+};
+EXPORT_SYMBOL_GPL(mite_alloc_ring);
+
+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);
+ }
+};
+EXPORT_SYMBOL_GPL(mite_free_ring);
+
+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_GPL(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_GPL(mite_release_channel);
+
+void mite_dma_arm(struct mite_channel *mite_chan)
+{
+ struct mite_struct *mite = mite_chan->mite;
+ int chor;
+ unsigned long flags;
+
+ /*
+ * 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_GPL(mite_dma_arm);
+
+/**************************************/
+
+int mite_buf_change(struct mite_dma_descriptor_ring *ring,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->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;
+
+ ring->descriptors =
+ dma_alloc_coherent(ring->hw_dev,
+ n_links * sizeof(struct mite_dma_descriptor),
+ &ring->descriptors_dma_addr, GFP_KERNEL);
+ if (!ring->descriptors) {
+ dev_err(s->device->class_dev,
+ "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_map->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_GPL(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;
+
+ /* 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:
+ pr_warn("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:
+ pr_warn("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));
+}
+EXPORT_SYMBOL_GPL(mite_prep_dma);
+
+static 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_GPL(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_GPL(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_GPL(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_GPL(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_GPL(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));
+
+ return tcr;
+}
+EXPORT_SYMBOL_GPL(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_GPL(mite_dma_disarm);
+
+int mite_sync_input_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ int count;
+ unsigned int nbytes, old_alloc_count;
+
+ old_alloc_count = async->buf_write_alloc_count;
+ /* write alloc as much as we can */
+ comedi_buf_write_alloc(s, 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) {
+ dev_warn(s->device->class_dev,
+ "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(s, count);
+ cfc_inc_scan_progress(s, count);
+ async->events |= COMEDI_CB_BLOCK;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mite_sync_input_dma);
+
+int mite_sync_output_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s)
+{
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ u32 stop_count = cmd->stop_arg * cfc_bytes_per_scan(s);
+ unsigned int old_alloc_count = async->buf_read_alloc_count;
+ u32 nbytes_ub, nbytes_lb;
+ int count;
+
+ /* read alloc as much as we can */
+ comedi_buf_read_alloc(s, async->prealloc_bufsz);
+ nbytes_lb = mite_bytes_read_from_memory_lb(mite_chan);
+ if (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 (cmd->stop_src == TRIG_COUNT && (int)(nbytes_ub - stop_count) > 0)
+ nbytes_ub = stop_count;
+ if ((int)(nbytes_ub - old_alloc_count) > 0) {
+ dev_warn(s->device->class_dev, "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(s, count);
+ async->events |= COMEDI_CB_BLOCK;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(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_GPL(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_GPL(mite_done);
+
+static int __init mite_module_init(void)
+{
+ return 0;
+}
+
+static void __exit mite_module_exit(void)
+{
+}
+
+module_init(mite_module_init);
+module_exit(mite_module_exit);
+
+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 00000000000..e6e58e989b7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mite.h
@@ -0,0 +1,430 @@
+/*
+ module/mite.h
+ Hardware driver for NI Mite PCI interface chip
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _MITE_H_
+#define _MITE_H_
+
+#include <linux/pci.h>
+#include <linux/log2.h>
+#include <linux/slab.h>
+#include "../comedidev.h"
+
+#define PCIMIO_COMPAT
+
+#define MAX_MITE_DMA_CHANNELS 8
+
+struct mite_dma_descriptor {
+ __le32 count;
+ __le32 addr;
+ __le32 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 pci_dev *pcidev;
+ resource_size_t mite_phys_addr;
+ void __iomem *mite_io_addr;
+ resource_size_t daq_phys_addr;
+ void __iomem *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;
+};
+
+struct mite_struct *mite_alloc(struct pci_dev *pcidev);
+
+static inline void mite_free(struct mite_struct *mite)
+{
+ kfree(mite);
+}
+
+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;
+};
+
+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);
+struct mite_dma_descriptor_ring *mite_alloc_ring(struct mite_struct *mite);
+void mite_free_ring(struct mite_dma_descriptor_ring *ring);
+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_subdevice *s);
+int mite_sync_output_dma(struct mite_channel *mite_chan,
+ struct comedi_subdevice *s);
+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_subdevice *s);
+
+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;
+
+ BUG_ON(size == 0);
+ order = ilog2(size);
+ 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;
+
+ if (retry_limit)
+ value = 1 + ilog2(retry_limit);
+ if (value > 0x7)
+ value = 0x7;
+ 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 00000000000..f770400a0e8
--- /dev/null
+++ b/drivers/staging/comedi/drivers/mpc624.c
@@ -0,0 +1,361 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: mpc624
+Description: Micro/sys MPC-624 PC/104 board
+Devices: [Micro/sys] MPC-624 (mpc624)
+Author: Stanislaw Raczynski <sraczynski@op.pl>
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+/* 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 mpc624_private {
+
+ /* set by mpc624_attach() from driver's parameters */
+ unsigned long int ulConvertionRate;
+};
+
+/* -------------------------------------------------------------------------- */
+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_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + MPC624_ADC);
+ if ((status & MPC624_ADBUSY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int mpc624_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct mpc624_private *devpriv = dev->private;
+ int n, i;
+ unsigned long int data_in, data_out;
+ int ret;
+
+ /*
+ * WARNING:
+ * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc
+ */
+ outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH);
+
+ 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 */
+ ret = comedi_timeout(dev, s, insn, mpc624_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* 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)
+ dev_dbg(dev->class_dev,
+ "EOC bit is set (data_in=%lu)!", data_in);
+ if (data_in & MPC624_DMY_BIT)
+ dev_dbg(dev->class_dev,
+ "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 mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct mpc624_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], MPC624_SIZE);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ switch (it->options[1]) {
+ case 0:
+ devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz;
+ break;
+ case 1:
+ devpriv->ulConvertionRate = MPC624_SPEED_1_76_kHz;
+ break;
+ case 2:
+ devpriv->ulConvertionRate = MPC624_SPEED_880_Hz;
+ break;
+ case 3:
+ devpriv->ulConvertionRate = MPC624_SPEED_440_Hz;
+ break;
+ case 4:
+ devpriv->ulConvertionRate = MPC624_SPEED_220_Hz;
+ break;
+ case 5:
+ devpriv->ulConvertionRate = MPC624_SPEED_110_Hz;
+ break;
+ case 6:
+ devpriv->ulConvertionRate = MPC624_SPEED_55_Hz;
+ break;
+ case 7:
+ devpriv->ulConvertionRate = MPC624_SPEED_27_5_Hz;
+ break;
+ case 8:
+ devpriv->ulConvertionRate = MPC624_SPEED_13_75_Hz;
+ break;
+ case 9:
+ devpriv->ulConvertionRate = MPC624_SPEED_6_875_Hz;
+ break;
+ default:
+ devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ 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;
+ }
+
+ switch (it->options[1]) {
+ case 0:
+ s->range_table = &range_mpc624_bipolar1;
+ break;
+ default:
+ s->range_table = &range_mpc624_bipolar10;
+ }
+ s->len_chanlist = 1;
+ s->insn_read = mpc624_ai_rinsn;
+
+ return 0;
+}
+
+static struct comedi_driver mpc624_driver = {
+ .driver_name = "mpc624",
+ .module = THIS_MODULE,
+ .attach = mpc624_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(mpc624_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/multiq3.c b/drivers/staging/comedi/drivers/multiq3.c
new file mode 100644
index 00000000000..b74b9e9bfd4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/multiq3.c
@@ -0,0 +1,310 @@
+/*
+ comedi/drivers/multiq3.c
+ Hardware driver for Quanser Consulting MultiQ-3 board
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute 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.
+ */
+/*
+Driver: multiq3
+Description: Quanser Consulting MultiQ-3
+Author: Anders Blomdell <anders.blomdell@control.lth.se>
+Status: works
+Devices: [Quanser Consulting] MultiQ-3 (multiq3)
+
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#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
+
+struct multiq3_private {
+ unsigned int ao_readback[2];
+};
+
+static int multiq3_ai_status(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + MULTIQ3_STATUS);
+ if (status & context)
+ return 0;
+ return -EBUSY;
+}
+
+static int multiq3_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n;
+ int chan;
+ unsigned int hi, lo;
+ int ret;
+
+ chan = CR_CHAN(insn->chanspec);
+ outw(MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3),
+ dev->iobase + MULTIQ3_CONTROL);
+
+ ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
+ MULTIQ3_STATUS_EOC);
+ if (ret)
+ return ret;
+
+ for (n = 0; n < insn->n; n++) {
+ outw(0, dev->iobase + MULTIQ3_AD_CS);
+
+ ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
+ MULTIQ3_STATUS_EOC_I);
+ if (ret)
+ return ret;
+
+ 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)
+{
+ struct multiq3_private *devpriv = dev->private;
+ 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)
+{
+ struct multiq3_private *devpriv = dev->private;
+ 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)
+{
+ data[1] = inw(dev->iobase + MULTIQ3_DIGIN_PORT);
+
+ return insn->n;
+}
+
+static int multiq3_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + MULTIQ3_DIGOUT_PORT);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+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)
+{
+ struct comedi_subdevice *s = &dev->subdevices[4];
+ int chan;
+
+ for (chan = 0; chan < s->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);
+ }
+}
+
+static int multiq3_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct multiq3_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], MULTIQ3_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ 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 struct comedi_driver multiq3_driver = {
+ .driver_name = "multiq3",
+ .module = THIS_MODULE,
+ .attach = multiq3_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(multiq3_driver);
+
+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 00000000000..c8b1fa793a3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_6527.c
@@ -0,0 +1,476 @@
+/*
+ * ni_6527.c
+ * Comedi driver for National Instruments PCI-6527
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: ni_6527
+ * Description: National Instruments 6527
+ * Devices: (National Instruments) PCI-6527 [pci-6527]
+ * (National Instruments) PXI-6527 [pxi-6527]
+ * Author: David A. Schleef <ds@schleef.org>
+ * Updated: Sat, 25 Jan 2003 13:24:40 -0800
+ * Status: works
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/*
+ * PCI BAR1 - Register memory map
+ *
+ * Manuals (available from ftp://ftp.natinst.com/support/manuals)
+ * 370106b.pdf 6527 Register Level Programmer Manual
+ */
+#define NI6527_DI_REG(x) (0x00 + (x))
+#define NI6527_DO_REG(x) (0x03 + (x))
+#define NI6527_ID_REG 0x06
+#define NI6527_CLR_REG 0x07
+#define NI6527_CLR_EDGE (1 << 3)
+#define NI6527_CLR_OVERFLOW (1 << 2)
+#define NI6527_CLR_FILT (1 << 1)
+#define NI6527_CLR_INTERVAL (1 << 0)
+#define NI6527_CLR_IRQS (NI6527_CLR_EDGE | NI6527_CLR_OVERFLOW)
+#define NI6527_CLR_RESET_FILT (NI6527_CLR_FILT | NI6527_CLR_INTERVAL)
+#define NI6527_FILT_INTERVAL_REG(x) (0x08 + (x))
+#define NI6527_FILT_ENA_REG(x) (0x0c + (x))
+#define NI6527_STATUS_REG 0x14
+#define NI6527_STATUS_IRQ (1 << 2)
+#define NI6527_STATUS_OVERFLOW (1 << 1)
+#define NI6527_STATUS_EDGE (1 << 0)
+#define NI6527_CTRL_REG 0x15
+#define NI6527_CTRL_FALLING (1 << 4)
+#define NI6527_CTRL_RISING (1 << 3)
+#define NI6527_CTRL_IRQ (1 << 2)
+#define NI6527_CTRL_OVERFLOW (1 << 1)
+#define NI6527_CTRL_EDGE (1 << 0)
+#define NI6527_CTRL_DISABLE_IRQS 0
+#define NI6527_CTRL_ENABLE_IRQS (NI6527_CTRL_FALLING | \
+ NI6527_CTRL_RISING | \
+ NI6527_CTRL_IRQ | NI6527_CTRL_EDGE)
+#define NI6527_RISING_EDGE_REG(x) (0x18 + (x))
+#define NI6527_FALLING_EDGE_REG(x) (0x20 + (x))
+
+enum ni6527_boardid {
+ BOARD_PCI6527,
+ BOARD_PXI6527,
+};
+
+struct ni6527_board {
+ const char *name;
+};
+
+static const struct ni6527_board ni6527_boards[] = {
+ [BOARD_PCI6527] = {
+ .name = "pci-6527",
+ },
+ [BOARD_PXI6527] = {
+ .name = "pxi-6527",
+ },
+};
+
+struct ni6527_private {
+ void __iomem *mmio_base;
+ unsigned int filter_interval;
+ unsigned int filter_enable;
+};
+
+static void ni6527_set_filter_interval(struct comedi_device *dev,
+ unsigned int val)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ if (val != devpriv->filter_interval) {
+ writeb(val & 0xff, mmio + NI6527_FILT_INTERVAL_REG(0));
+ writeb((val >> 8) & 0xff, mmio + NI6527_FILT_INTERVAL_REG(1));
+ writeb((val >> 16) & 0x0f, mmio + NI6527_FILT_INTERVAL_REG(2));
+
+ writeb(NI6527_CLR_INTERVAL, mmio + NI6527_CLR_REG);
+
+ devpriv->filter_interval = val;
+ }
+}
+
+static void ni6527_set_filter_enable(struct comedi_device *dev,
+ unsigned int val)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ writeb(val & 0xff, mmio + NI6527_FILT_ENA_REG(0));
+ writeb((val >> 8) & 0xff, mmio + NI6527_FILT_ENA_REG(1));
+ writeb((val >> 16) & 0xff, mmio + NI6527_FILT_ENA_REG(2));
+}
+
+static int ni6527_di_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni6527_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int interval;
+
+ switch (data[0]) {
+ case INSN_CONFIG_FILTER:
+ /*
+ * The deglitch filter interval is specified in nanoseconds.
+ * The hardware supports intervals in 200ns increments. Round
+ * the user values up and return the actual interval.
+ */
+ interval = (data[1] + 100) / 200;
+ data[1] = interval * 200;
+
+ if (interval) {
+ ni6527_set_filter_interval(dev, interval);
+ devpriv->filter_enable |= 1 << chan;
+ } else {
+ devpriv->filter_enable &= ~(1 << chan);
+ }
+ ni6527_set_filter_enable(dev, devpriv->filter_enable);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ni6527_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+ unsigned int val;
+
+ val = readb(mmio + NI6527_DI_REG(0));
+ val |= (readb(mmio + NI6527_DI_REG(1)) << 8);
+ val |= (readb(mmio + NI6527_DI_REG(2)) << 16);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int ni6527_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /* Outputs are inverted */
+ unsigned int val = s->state ^ 0xffffff;
+
+ if (mask & 0x0000ff)
+ writeb(val & 0xff, mmio + NI6527_DO_REG(0));
+ if (mask & 0x00ff00)
+ writeb((val >> 8) & 0xff, mmio + NI6527_DO_REG(1));
+ if (mask & 0xff0000)
+ writeb((val >> 16) & 0xff, mmio + NI6527_DO_REG(2));
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static irqreturn_t ni6527_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct ni6527_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ void __iomem *mmio = devpriv->mmio_base;
+ unsigned int status;
+
+ status = readb(mmio + NI6527_STATUS_REG);
+ if (!(status & NI6527_STATUS_IRQ))
+ return IRQ_NONE;
+
+ if (status & NI6527_STATUS_EDGE) {
+ comedi_buf_put(s, 0);
+ s->async->events |= COMEDI_CB_EOS;
+ comedi_event(dev, s);
+ }
+
+ writeb(NI6527_CLR_IRQS, mmio + NI6527_CLR_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int ni6527_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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 ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ writeb(NI6527_CLR_IRQS, mmio + NI6527_CLR_REG);
+ writeb(NI6527_CTRL_ENABLE_IRQS, mmio + NI6527_CTRL_REG);
+
+ return 0;
+}
+
+static int ni6527_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ writeb(NI6527_CTRL_DISABLE_IRQS, mmio + NI6527_CTRL_REG);
+
+ return 0;
+}
+
+static int ni6527_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static void ni6527_set_edge_detection(struct comedi_device *dev,
+ unsigned int rising,
+ unsigned int falling)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ /* enable rising-edge detection channels */
+ writeb(rising & 0xff, mmio + NI6527_RISING_EDGE_REG(0));
+ writeb((rising >> 8) & 0xff, mmio + NI6527_RISING_EDGE_REG(1));
+ writeb((rising >> 16) & 0xff, mmio + NI6527_RISING_EDGE_REG(2));
+
+ /* enable falling-edge detection channels */
+ writeb(falling & 0xff, mmio + NI6527_FALLING_EDGE_REG(0));
+ writeb((falling >> 8) & 0xff, mmio + NI6527_FALLING_EDGE_REG(1));
+ writeb((falling >> 16) & 0xff, mmio + NI6527_FALLING_EDGE_REG(2));
+}
+
+static int ni6527_intr_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ switch (data[0]) {
+ case INSN_CONFIG_CHANGE_NOTIFY:
+ /* check_insn_config_length() does not check this instruction */
+ if (insn->n != 3)
+ return -EINVAL;
+ ni6527_set_edge_detection(dev, data[1], data[2]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static void ni6527_reset(struct comedi_device *dev)
+{
+ struct ni6527_private *devpriv = dev->private;
+ void __iomem *mmio = devpriv->mmio_base;
+
+ /* disable deglitch filters on all channels */
+ ni6527_set_filter_enable(dev, 0);
+
+ writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT,
+ mmio + NI6527_CLR_REG);
+ writeb(NI6527_CTRL_DISABLE_IRQS, mmio + NI6527_CTRL_REG);
+}
+
+static int ni6527_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni6527_board *board = NULL;
+ struct ni6527_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni6527_boards))
+ board = &ni6527_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->mmio_base = pci_ioremap_bar(pcidev, 1);
+ if (!devpriv->mmio_base)
+ return -ENOMEM;
+
+ /* make sure this is actually a 6527 device */
+ if (readb(devpriv->mmio_base + NI6527_ID_REG) != 0x27)
+ return -ENODEV;
+
+ ni6527_reset(dev);
+
+ ret = request_irq(pcidev->irq, ni6527_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_config = ni6527_di_insn_config;
+ s->insn_bits = ni6527_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = ni6527_do_insn_bits;
+
+ /* Edge detection interrupt subdevice */
+ s = &dev->subdevices[2];
+ if (dev->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_config = ni6527_intr_insn_config;
+ s->insn_bits = ni6527_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni6527_intr_cmdtest;
+ s->do_cmd = ni6527_intr_cmd;
+ s->cancel = ni6527_intr_cancel;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ return 0;
+}
+
+static void ni6527_detach(struct comedi_device *dev)
+{
+ struct ni6527_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->mmio_base)
+ ni6527_reset(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni6527_driver = {
+ .driver_name = "ni_6527",
+ .module = THIS_MODULE,
+ .auto_attach = ni6527_auto_attach,
+ .detach = ni6527_detach,
+};
+
+static int ni6527_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni6527_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni6527_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 },
+ { PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni6527_pci_table);
+
+static struct pci_driver ni6527_pci_driver = {
+ .name = "ni_6527",
+ .id_table = ni6527_pci_table,
+ .probe = ni6527_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527");
+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 00000000000..9a139d6b8ef
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_65xx.c
@@ -0,0 +1,778 @@
+/*
+ comedi/drivers/ni_6514.c
+ driver for National Instruments PCI-6514
+
+ Copyright (C) 2006 Jon Grierson <jd@renko.co.uk>
+ Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: ni_65xx
+Description: National Instruments 65xx static dio boards
+Author: Jon Grierson <jd@renko.co.uk>,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+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
+
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.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
+
+enum ni_65xx_boardid {
+ BOARD_PCI6509,
+ BOARD_PXI6509,
+ BOARD_PCI6510,
+ BOARD_PCI6511,
+ BOARD_PXI6511,
+ BOARD_PCI6512,
+ BOARD_PXI6512,
+ BOARD_PCI6513,
+ BOARD_PXI6513,
+ BOARD_PCI6514,
+ BOARD_PXI6514,
+ BOARD_PCI6515,
+ BOARD_PXI6515,
+ BOARD_PCI6516,
+ BOARD_PCI6517,
+ BOARD_PCI6518,
+ BOARD_PCI6519,
+ BOARD_PCI6520,
+ BOARD_PCI6521,
+ BOARD_PXI6521,
+ BOARD_PCI6528,
+ BOARD_PXI6528,
+};
+
+struct ni_65xx_board {
+ 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[] = {
+ [BOARD_PCI6509] = {
+ .name = "pci-6509",
+ .num_dio_ports = 12,
+ },
+ [BOARD_PXI6509] = {
+ .name = "pxi-6509",
+ .num_dio_ports = 12,
+ },
+ [BOARD_PCI6510] = {
+ .name = "pci-6510",
+ .num_di_ports = 4,
+ },
+ [BOARD_PCI6511] = {
+ .name = "pci-6511",
+ .num_di_ports = 8,
+ },
+ [BOARD_PXI6511] = {
+ .name = "pxi-6511",
+ .num_di_ports = 8,
+ },
+ [BOARD_PCI6512] = {
+ .name = "pci-6512",
+ .num_do_ports = 8,
+ },
+ [BOARD_PXI6512] = {
+ .name = "pxi-6512",
+ .num_do_ports = 8,
+ },
+ [BOARD_PCI6513] = {
+ .name = "pci-6513",
+ .num_do_ports = 8,
+ .invert_outputs = 1,
+ },
+ [BOARD_PXI6513] = {
+ .name = "pxi-6513",
+ .num_do_ports = 8,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6514] = {
+ .name = "pci-6514",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PXI6514] = {
+ .name = "pxi-6514",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6515] = {
+ .name = "pci-6515",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PXI6515] = {
+ .name = "pxi-6515",
+ .num_di_ports = 4,
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6516] = {
+ .name = "pci-6516",
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6517] = {
+ .name = "pci-6517",
+ .num_do_ports = 4,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6518] = {
+ .name = "pci-6518",
+ .num_di_ports = 2,
+ .num_do_ports = 2,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6519] = {
+ .name = "pci-6519",
+ .num_di_ports = 2,
+ .num_do_ports = 2,
+ .invert_outputs = 1,
+ },
+ [BOARD_PCI6520] = {
+ .name = "pci-6520",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PCI6521] = {
+ .name = "pci-6521",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PXI6521] = {
+ .name = "pxi-6521",
+ .num_di_ports = 1,
+ .num_do_ports = 1,
+ },
+ [BOARD_PCI6528] = {
+ .name = "pci-6528",
+ .num_di_ports = 3,
+ .num_do_ports = 3,
+ },
+ [BOARD_PXI6528] = {
+ .name = "pxi-6528",
+ .num_di_ports = 3,
+ .num_do_ports = 3,
+ },
+};
+
+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;
+}
+
+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];
+};
+
+struct ni_65xx_subdevice_private {
+ unsigned base_port;
+};
+
+static inline struct ni_65xx_subdevice_private *sprivate(struct comedi_subdevice
+ *subdev)
+{
+ return subdev->private;
+}
+
+static int ni_65xx_config_filter(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct ni_65xx_private *devpriv = dev->private;
+ 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 != devpriv->filter_interval) {
+ writeb(interval,
+ devpriv->mite->daq_io_addr +
+ Filter_Interval);
+ devpriv->filter_interval = interval;
+ }
+
+ devpriv->filter_enable[port] |=
+ 1 << (chan % ni_65xx_channels_per_port);
+ } else {
+ devpriv->filter_enable[port] &=
+ ~(1 << (chan % ni_65xx_channels_per_port));
+ }
+
+ writeb(devpriv->filter_enable[port],
+ devpriv->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)
+{
+ struct ni_65xx_private *devpriv = dev->private;
+ 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;
+ devpriv->dio_direction[port] = COMEDI_OUTPUT;
+ writeb(0, devpriv->mite->daq_io_addr + Port_Select(port));
+ return 1;
+ break;
+ case INSN_CONFIG_DIO_INPUT:
+ if (s->type != COMEDI_SUBD_DIO)
+ return -EINVAL;
+ devpriv->dio_direction[port] = COMEDI_INPUT;
+ writeb(1, devpriv->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] = devpriv->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)
+{
+ const struct ni_65xx_board *board = comedi_board(dev);
+ struct ni_65xx_private *devpriv = dev->private;
+ int base_bitfield_channel;
+ unsigned read_bits = 0;
+ int last_port_offset = ni_65xx_port_by_channel(s->n_chan - 1);
+ int port_offset;
+
+ base_bitfield_channel = CR_CHAN(insn->chanspec);
+ for (port_offset = ni_65xx_port_by_channel(base_bitfield_channel);
+ port_offset <= last_port_offset; port_offset++) {
+ unsigned port = sprivate(s)->base_port + port_offset;
+ int base_port_channel = port_offset * ni_65xx_channels_per_port;
+ unsigned port_mask, port_data, port_read_bits;
+ int bitshift = base_port_channel - base_bitfield_channel;
+
+ if (bitshift >= 32)
+ break;
+ port_mask = data[0];
+ port_data = data[1];
+ 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;
+ devpriv->output_bits[port] &= ~port_mask;
+ devpriv->output_bits[port] |=
+ port_data & port_mask;
+ bits = devpriv->output_bits[port];
+ if (board->invert_outputs)
+ bits = ~bits;
+ writeb(bits,
+ devpriv->mite->daq_io_addr +
+ Port_Data(port));
+ }
+ port_read_bits =
+ readb(devpriv->mite->daq_io_addr + Port_Data(port));
+ if (s->type == COMEDI_SUBD_DO && board->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 ni_65xx_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ 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, 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;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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 ni_65xx_private *devpriv = dev->private;
+
+ 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 ni_65xx_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_65xx_private *devpriv = dev->private;
+
+ writeb(0x00, devpriv->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)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int ni_65xx_intr_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_65xx_private *devpriv = dev->private;
+
+ 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(0x10));
+ writeb(data[1] >> 16,
+ devpriv->mite->daq_io_addr +
+ Rising_Edge_Detection_Enable(0x20));
+ writeb(data[1] >> 24,
+ devpriv->mite->daq_io_addr +
+ Rising_Edge_Detection_Enable(0x30));
+
+ 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(0x10));
+ writeb(data[2] >> 16,
+ devpriv->mite->daq_io_addr +
+ Falling_Edge_Detection_Enable(0x20));
+ writeb(data[2] >> 24,
+ devpriv->mite->daq_io_addr +
+ Falling_Edge_Detection_Enable(0x30));
+
+ return 2;
+}
+
+static int ni_65xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_65xx_board *board = NULL;
+ struct ni_65xx_private *devpriv;
+ struct ni_65xx_subdevice_private *spriv;
+ struct comedi_subdevice *s;
+ unsigned i;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni_65xx_boards))
+ board = &ni_65xx_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup(devpriv->mite);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, "error setting up mite\n");
+ return ret;
+ }
+
+ dev->irq = mite_irq(devpriv->mite);
+ dev_info(dev->class_dev, "board: %s, ID=0x%02x", dev->board_name,
+ readb(devpriv->mite->daq_io_addr + ID_Register));
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ if (board->num_di_ports) {
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan =
+ board->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;
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+ spriv->base_port = 0;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[1];
+ if (board->num_do_ports) {
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan =
+ board->num_do_ports * ni_65xx_channels_per_port;
+ s->range_table = &range_digital;
+ s->maxdata = 1;
+ s->insn_bits = ni_65xx_dio_insn_bits;
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+ spriv->base_port = board->num_di_ports;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ if (board->num_dio_ports) {
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan =
+ board->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;
+ spriv = comedi_alloc_spriv(s, sizeof(*spriv));
+ if (!spriv)
+ return -ENOMEM;
+ spriv->base_port = 0;
+ for (i = 0; i < board->num_dio_ports; ++i) {
+ /* configure all ports for input */
+ writeb(0x1,
+ devpriv->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->len_chanlist = 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); ++i) {
+ writeb(0x00,
+ devpriv->mite->daq_io_addr + Filter_Enable(i));
+ if (board->invert_outputs)
+ writeb(0x01,
+ devpriv->mite->daq_io_addr + Port_Data(i));
+ else
+ writeb(0x00,
+ devpriv->mite->daq_io_addr + Port_Data(i));
+ }
+ writeb(ClrEdge | ClrOverflow,
+ devpriv->mite->daq_io_addr + Clear_Register);
+ writeb(0x00,
+ devpriv->mite->daq_io_addr + Master_Interrupt_Control);
+
+ /* Set filter interval to 0 (32bit reg) */
+ writeb(0x00000000, devpriv->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;
+ dev_warn(dev->class_dev, "irq not available\n");
+ }
+
+ return 0;
+}
+
+static void ni_65xx_detach(struct comedi_device *dev)
+{
+ struct ni_65xx_private *devpriv = dev->private;
+
+ 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) {
+ if (devpriv->mite) {
+ mite_unsetup(devpriv->mite);
+ mite_free(devpriv->mite);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_65xx_driver = {
+ .driver_name = "ni_65xx",
+ .module = THIS_MODULE,
+ .auto_attach = ni_65xx_auto_attach,
+ .detach = ni_65xx_detach,
+};
+
+static int ni_65xx_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_65xx_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_65xx_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1710), BOARD_PXI6509 },
+ { PCI_VDEVICE(NI, 0x7085), BOARD_PCI6509 },
+ { PCI_VDEVICE(NI, 0x7086), BOARD_PXI6528 },
+ { PCI_VDEVICE(NI, 0x7087), BOARD_PCI6515 },
+ { PCI_VDEVICE(NI, 0x7088), BOARD_PCI6514 },
+ { PCI_VDEVICE(NI, 0x70a9), BOARD_PCI6528 },
+ { PCI_VDEVICE(NI, 0x70c3), BOARD_PCI6511 },
+ { PCI_VDEVICE(NI, 0x70c8), BOARD_PCI6513 },
+ { PCI_VDEVICE(NI, 0x70c9), BOARD_PXI6515 },
+ { PCI_VDEVICE(NI, 0x70cc), BOARD_PCI6512 },
+ { PCI_VDEVICE(NI, 0x70cd), BOARD_PXI6514 },
+ { PCI_VDEVICE(NI, 0x70d1), BOARD_PXI6513 },
+ { PCI_VDEVICE(NI, 0x70d2), BOARD_PXI6512 },
+ { PCI_VDEVICE(NI, 0x70d3), BOARD_PXI6511 },
+ { PCI_VDEVICE(NI, 0x7124), BOARD_PCI6510 },
+ { PCI_VDEVICE(NI, 0x7125), BOARD_PCI6516 },
+ { PCI_VDEVICE(NI, 0x7126), BOARD_PCI6517 },
+ { PCI_VDEVICE(NI, 0x7127), BOARD_PCI6518 },
+ { PCI_VDEVICE(NI, 0x7128), BOARD_PCI6519 },
+ { PCI_VDEVICE(NI, 0x718b), BOARD_PCI6521 },
+ { PCI_VDEVICE(NI, 0x718c), BOARD_PXI6521 },
+ { PCI_VDEVICE(NI, 0x71c5), BOARD_PCI6520 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table);
+
+static struct pci_driver ni_65xx_pci_driver = {
+ .name = "ni_65xx",
+ .id_table = ni_65xx_pci_table,
+ .probe = ni_65xx_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_65xx_driver, ni_65xx_pci_driver);
+
+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 00000000000..634cde83a02
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_660x.c
@@ -0,0 +1,1240 @@
+/*
+ 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.
+*/
+
+/*
+ * Driver: ni_660x
+ * Description: National Instruments 660x counter/timer boards
+ * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602,
+ * PXI-6608, PXI-6624
+ * Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ * Herman.Bruyninckx@mech.kuleuven.ac.be,
+ * Wim.Meeussen@mech.kuleuven.ac.be,
+ * Klaas.Gadeyne@mech.kuleuven.ac.be,
+ * Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Updated: Fri, 15 Mar 2013 10:47:56 +0000
+ * 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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.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 {
+ NI660X_G0_INT_ACK,
+ NI660X_G0_STATUS,
+ NI660X_G1_INT_ACK,
+ NI660X_G1_STATUS,
+ NI660X_G01_STATUS,
+ NI660X_G0_CMD,
+ NI660X_STC_DIO_PARALLEL_INPUT,
+ NI660X_G1_CMD,
+ NI660X_G0_HW_SAVE,
+ NI660X_G1_HW_SAVE,
+ NI660X_STC_DIO_OUTPUT,
+ NI660X_STC_DIO_CONTROL,
+ NI660X_G0_SW_SAVE,
+ NI660X_G1_SW_SAVE,
+ NI660X_G0_MODE,
+ NI660X_G01_STATUS1,
+ NI660X_G1_MODE,
+ NI660X_STC_DIO_SERIAL_INPUT,
+ NI660X_G0_LOADA,
+ NI660X_G01_STATUS2,
+ NI660X_G0_LOADB,
+ NI660X_G1_LOADA,
+ NI660X_G1_LOADB,
+ NI660X_G0_INPUT_SEL,
+ NI660X_G1_INPUT_SEL,
+ NI660X_G0_AUTO_INC,
+ NI660X_G1_AUTO_INC,
+ NI660X_G01_RESET,
+ NI660X_G0_INT_ENA,
+ NI660X_G1_INT_ENA,
+ NI660X_G0_CNT_MODE,
+ NI660X_G1_CNT_MODE,
+ NI660X_G0_GATE2,
+ NI660X_G1_GATE2,
+ NI660X_G0_DMA_CFG,
+ NI660X_G0_DMA_STATUS,
+ NI660X_G1_DMA_CFG,
+ NI660X_G1_DMA_STATUS,
+ NI660X_G2_INT_ACK,
+ NI660X_G2_STATUS,
+ NI660X_G3_INT_ACK,
+ NI660X_G3_STATUS,
+ NI660X_G23_STATUS,
+ NI660X_G2_CMD,
+ NI660X_G3_CMD,
+ NI660X_G2_HW_SAVE,
+ NI660X_G3_HW_SAVE,
+ NI660X_G2_SW_SAVE,
+ NI660X_G3_SW_SAVE,
+ NI660X_G2_MODE,
+ NI660X_G23_STATUS1,
+ NI660X_G3_MODE,
+ NI660X_G2_LOADA,
+ NI660X_G23_STATUS2,
+ NI660X_G2_LOADB,
+ NI660X_G3_LOADA,
+ NI660X_G3_LOADB,
+ NI660X_G2_INPUT_SEL,
+ NI660X_G3_INPUT_SEL,
+ NI660X_G2_AUTO_INC,
+ NI660X_G3_AUTO_INC,
+ NI660X_G23_RESET,
+ NI660X_G2_INT_ENA,
+ NI660X_G3_INT_ENA,
+ NI660X_G2_CNT_MODE,
+ NI660X_G3_CNT_MODE,
+ NI660X_G3_GATE2,
+ NI660X_G2_GATE2,
+ NI660X_G2_DMA_CFG,
+ NI660X_G2_DMA_STATUS,
+ NI660X_G3_DMA_CFG,
+ NI660X_G3_DMA_STATUS,
+ NI660X_DIO32_INPUT,
+ NI660X_DIO32_OUTPUT,
+ NI660X_CLK_CFG,
+ NI660X_GLOBAL_INT_STATUS,
+ NI660X_DMA_CFG,
+ NI660X_GLOBAL_INT_CFG,
+ NI660X_IO_CFG_0_1,
+ NI660X_IO_CFG_2_3,
+ NI660X_IO_CFG_4_5,
+ NI660X_IO_CFG_6_7,
+ NI660X_IO_CFG_8_9,
+ NI660X_IO_CFG_10_11,
+ NI660X_IO_CFG_12_13,
+ NI660X_IO_CFG_14_15,
+ NI660X_IO_CFG_16_17,
+ NI660X_IO_CFG_18_19,
+ NI660X_IO_CFG_20_21,
+ NI660X_IO_CFG_22_23,
+ NI660X_IO_CFG_24_25,
+ NI660X_IO_CFG_26_27,
+ NI660X_IO_CFG_28_29,
+ NI660X_IO_CFG_30_31,
+ NI660X_IO_CFG_32_33,
+ NI660X_IO_CFG_34_35,
+ NI660X_IO_CFG_36_37,
+ NI660X_IO_CFG_38_39,
+ NI660X_NUM_REGS,
+};
+
+static inline unsigned IOConfigReg(unsigned pfi_channel)
+{
+ unsigned reg = NI660X_IO_CFG_0_1 + pfi_channel / 2;
+ BUG_ON(reg > NI660X_IO_CFG_38_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[NI660X_NUM_REGS] = {
+ {"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_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 };
+
+enum ni_660x_boardid {
+ BOARD_PCI6601,
+ BOARD_PCI6602,
+ BOARD_PXI6602,
+ BOARD_PXI6608,
+ BOARD_PXI6624
+};
+
+struct ni_660x_board {
+ const char *name;
+ unsigned n_chips; /* total number of TIO chips */
+};
+
+static const struct ni_660x_board ni_660x_boards[] = {
+ [BOARD_PCI6601] = {
+ .name = "PCI-6601",
+ .n_chips = 1,
+ },
+ [BOARD_PCI6602] = {
+ .name = "PCI-6602",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6602] = {
+ .name = "PXI-6602",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6608] = {
+ .name = "PXI-6608",
+ .n_chips = 2,
+ },
+ [BOARD_PXI6624] = {
+ .name = "PXI-6624",
+ .n_chips = 2,
+ },
+};
+
+#define NI_660X_MAX_NUM_CHIPS 2
+#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip)
+
+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 unsigned ni_660x_num_counters(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = comedi_board(dev);
+
+ return board->n_chips * counters_per_chip;
+}
+
+static enum ni_660x_register ni_gpct_to_660x_register(enum ni_gpct_register reg)
+{
+ switch (reg) {
+ case NITIO_G0_AUTO_INC:
+ return NI660X_G0_AUTO_INC;
+ case NITIO_G1_AUTO_INC:
+ return NI660X_G1_AUTO_INC;
+ case NITIO_G2_AUTO_INC:
+ return NI660X_G2_AUTO_INC;
+ case NITIO_G3_AUTO_INC:
+ return NI660X_G3_AUTO_INC;
+ case NITIO_G0_CMD:
+ return NI660X_G0_CMD;
+ case NITIO_G1_CMD:
+ return NI660X_G1_CMD;
+ case NITIO_G2_CMD:
+ return NI660X_G2_CMD;
+ case NITIO_G3_CMD:
+ return NI660X_G3_CMD;
+ case NITIO_G0_HW_SAVE:
+ return NI660X_G0_HW_SAVE;
+ case NITIO_G1_HW_SAVE:
+ return NI660X_G1_HW_SAVE;
+ case NITIO_G2_HW_SAVE:
+ return NI660X_G2_HW_SAVE;
+ case NITIO_G3_HW_SAVE:
+ return NI660X_G3_HW_SAVE;
+ case NITIO_G0_SW_SAVE:
+ return NI660X_G0_SW_SAVE;
+ case NITIO_G1_SW_SAVE:
+ return NI660X_G1_SW_SAVE;
+ case NITIO_G2_SW_SAVE:
+ return NI660X_G2_SW_SAVE;
+ case NITIO_G3_SW_SAVE:
+ return NI660X_G3_SW_SAVE;
+ case NITIO_G0_MODE:
+ return NI660X_G0_MODE;
+ case NITIO_G1_MODE:
+ return NI660X_G1_MODE;
+ case NITIO_G2_MODE:
+ return NI660X_G2_MODE;
+ case NITIO_G3_MODE:
+ return NI660X_G3_MODE;
+ case NITIO_G0_LOADA:
+ return NI660X_G0_LOADA;
+ case NITIO_G1_LOADA:
+ return NI660X_G1_LOADA;
+ case NITIO_G2_LOADA:
+ return NI660X_G2_LOADA;
+ case NITIO_G3_LOADA:
+ return NI660X_G3_LOADA;
+ case NITIO_G0_LOADB:
+ return NI660X_G0_LOADB;
+ case NITIO_G1_LOADB:
+ return NI660X_G1_LOADB;
+ case NITIO_G2_LOADB:
+ return NI660X_G2_LOADB;
+ case NITIO_G3_LOADB:
+ return NI660X_G3_LOADB;
+ case NITIO_G0_INPUT_SEL:
+ return NI660X_G0_INPUT_SEL;
+ case NITIO_G1_INPUT_SEL:
+ return NI660X_G1_INPUT_SEL;
+ case NITIO_G2_INPUT_SEL:
+ return NI660X_G2_INPUT_SEL;
+ case NITIO_G3_INPUT_SEL:
+ return NI660X_G3_INPUT_SEL;
+ case NITIO_G01_STATUS:
+ return NI660X_G01_STATUS;
+ case NITIO_G23_STATUS:
+ return NI660X_G23_STATUS;
+ case NITIO_G01_RESET:
+ return NI660X_G01_RESET;
+ case NITIO_G23_RESET:
+ return NI660X_G23_RESET;
+ case NITIO_G01_STATUS1:
+ return NI660X_G01_STATUS1;
+ case NITIO_G23_STATUS1:
+ return NI660X_G23_STATUS1;
+ case NITIO_G01_STATUS2:
+ return NI660X_G01_STATUS2;
+ case NITIO_G23_STATUS2:
+ return NI660X_G23_STATUS2;
+ case NITIO_G0_CNT_MODE:
+ return NI660X_G0_CNT_MODE;
+ case NITIO_G1_CNT_MODE:
+ return NI660X_G1_CNT_MODE;
+ case NITIO_G2_CNT_MODE:
+ return NI660X_G2_CNT_MODE;
+ case NITIO_G3_CNT_MODE:
+ return NI660X_G3_CNT_MODE;
+ case NITIO_G0_GATE2:
+ return NI660X_G0_GATE2;
+ case NITIO_G1_GATE2:
+ return NI660X_G1_GATE2;
+ case NITIO_G2_GATE2:
+ return NI660X_G2_GATE2;
+ case NITIO_G3_GATE2:
+ return NI660X_G3_GATE2;
+ case NITIO_G0_DMA_CFG:
+ return NI660X_G0_DMA_CFG;
+ case NITIO_G0_DMA_STATUS:
+ return NI660X_G0_DMA_STATUS;
+ case NITIO_G1_DMA_CFG:
+ return NI660X_G1_DMA_CFG;
+ case NITIO_G1_DMA_STATUS:
+ return NI660X_G1_DMA_STATUS;
+ case NITIO_G2_DMA_CFG:
+ return NI660X_G2_DMA_CFG;
+ case NITIO_G2_DMA_STATUS:
+ return NI660X_G2_DMA_STATUS;
+ case NITIO_G3_DMA_CFG:
+ return NI660X_G3_DMA_CFG;
+ case NITIO_G3_DMA_STATUS:
+ return NI660X_G3_DMA_STATUS;
+ case NITIO_G0_INT_ACK:
+ return NI660X_G0_INT_ACK;
+ case NITIO_G1_INT_ACK:
+ return NI660X_G1_INT_ACK;
+ case NITIO_G2_INT_ACK:
+ return NI660X_G2_INT_ACK;
+ case NITIO_G3_INT_ACK:
+ return NI660X_G3_INT_ACK;
+ case NITIO_G0_STATUS:
+ return NI660X_G0_STATUS;
+ case NITIO_G1_STATUS:
+ return NI660X_G1_STATUS;
+ case NITIO_G2_STATUS:
+ return NI660X_G2_STATUS;
+ case NITIO_G3_STATUS:
+ return NI660X_G3_STATUS;
+ case NITIO_G0_INT_ENA:
+ return NI660X_G0_INT_ENA;
+ case NITIO_G1_INT_ENA:
+ return NI660X_G1_INT_ENA;
+ case NITIO_G2_INT_ENA:
+ return NI660X_G2_INT_ENA;
+ case NITIO_G3_INT_ENA:
+ return NI660X_G3_INT_ENA;
+ default:
+ BUG();
+ return 0;
+ }
+}
+
+static inline void ni_660x_write_register(struct comedi_device *dev,
+ unsigned chip, unsigned bits,
+ enum ni_660x_register reg)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ void __iomem *write_address =
+ devpriv->mite->daq_io_addr + GPCT_OFFSET[chip] +
+ registerData[reg].offset;
+
+ switch (registerData[reg].size) {
+ case DATA_2B:
+ writew(bits, write_address);
+ break;
+ case DATA_4B:
+ writel(bits, write_address);
+ break;
+ default:
+ BUG();
+ break;
+ }
+}
+
+static inline unsigned ni_660x_read_register(struct comedi_device *dev,
+ unsigned chip,
+ enum ni_660x_register reg)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ void __iomem *read_address =
+ devpriv->mite->daq_io_addr + GPCT_OFFSET[chip] +
+ registerData[reg].offset;
+
+ switch (registerData[reg].size) {
+ case DATA_2B:
+ return readw(read_address);
+ break;
+ case DATA_4B:
+ return readl(read_address);
+ break;
+ default:
+ 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);
+ unsigned chip = counter->chip_index;
+
+ ni_660x_write_register(dev, chip, 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);
+ unsigned chip = counter->chip_index;
+
+ return ni_660x_read_register(dev, chip, ni_660x_register);
+}
+
+static inline struct mite_dma_descriptor_ring *mite_ring(struct ni_660x_private
+ *priv,
+ struct ni_gpct
+ *counter)
+{
+ unsigned chip = counter->chip_index;
+
+ return priv->mite_rings[chip][counter->counter_index];
+}
+
+static inline void ni_660x_set_dma_channel(struct comedi_device *dev,
+ unsigned mite_channel,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned chip = counter->chip_index;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ devpriv->dma_configuration_soft_copies[chip] &=
+ ~dma_select_mask(mite_channel);
+ devpriv->dma_configuration_soft_copies[chip] |=
+ dma_select_bits(mite_channel, counter->counter_index);
+ ni_660x_write_register(dev, chip,
+ devpriv->dma_configuration_soft_copies[chip] |
+ dma_reset_bit(mite_channel), NI660X_DMA_CFG);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags);
+}
+
+static inline void ni_660x_unset_dma_channel(struct comedi_device *dev,
+ unsigned mite_channel,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned chip = counter->chip_index;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags);
+ devpriv->dma_configuration_soft_copies[chip] &=
+ ~dma_select_mask(mite_channel);
+ devpriv->dma_configuration_soft_copies[chip] |=
+ dma_select_bits(mite_channel, dma_selection_none);
+ ni_660x_write_register(dev, chip,
+ devpriv->dma_configuration_soft_copies[chip],
+ NI660X_DMA_CFG);
+ mmiowb();
+ spin_unlock_irqrestore(&devpriv->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)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned long flags;
+ struct mite_channel *mite_chan;
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ BUG_ON(counter->mite_chan);
+ mite_chan = mite_request_channel(devpriv->mite,
+ mite_ring(devpriv, counter));
+ 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(counter, mite_chan);
+ ni_660x_set_dma_channel(dev, mite_chan->channel, counter);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+ return 0;
+}
+
+static void ni_660x_release_mite_channel(struct comedi_device *dev,
+ struct ni_gpct *counter)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->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(&devpriv->mite_channel_lock, flags);
+}
+
+static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ 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);
+
+ return ni_tio_cmd(dev, s);
+}
+
+static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ 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 chip)
+{
+ unsigned bits = 0;
+
+ /*
+ * 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 (chip)
+ bits = CounterSwap;
+
+ ni_660x_write_register(dev, chip, bits, NI660X_CLK_CFG);
+}
+
+static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+
+ ni_tio_handle_interrupt(counter, s);
+ cfc_handle_events(dev, s);
+}
+
+static irqreturn_t ni_660x_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct ni_660x_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ unsigned i;
+ unsigned long flags;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&devpriv->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(&devpriv->interrupt_lock, flags);
+ return IRQ_HANDLED;
+}
+
+static int ni_660x_input_poll(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ struct ni_gpct *counter = s->private;
+ unsigned long flags;
+
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&devpriv->interrupt_lock, flags);
+ mite_sync_input_dma(counter->mite_chan, s);
+ spin_unlock_irqrestore(&devpriv->interrupt_lock, flags);
+ return comedi_buf_read_n_available(s);
+}
+
+static int ni_660x_buf_change(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned long new_size)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ struct ni_gpct *counter = s->private;
+ int ret;
+
+ ret = mite_buf_change(mite_ring(devpriv, counter), s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ni_660x_allocate_private(struct comedi_device *dev)
+{
+ struct ni_660x_private *devpriv;
+ unsigned i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->mite_channel_lock);
+ spin_lock_init(&devpriv->interrupt_lock);
+ spin_lock_init(&devpriv->soft_reg_copy_lock);
+ for (i = 0; i < NUM_PFI_CHANNELS; ++i)
+ devpriv->pfi_output_selects[i] = pfi_output_select_counter;
+
+ return 0;
+}
+
+static int ni_660x_alloc_mite_rings(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = comedi_board(dev);
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+ unsigned j;
+
+ for (i = 0; i < board->n_chips; ++i) {
+ for (j = 0; j < counters_per_chip; ++j) {
+ devpriv->mite_rings[i][j] =
+ mite_alloc_ring(devpriv->mite);
+ if (devpriv->mite_rings[i][j] == NULL)
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static void ni_660x_free_mite_rings(struct comedi_device *dev)
+{
+ const struct ni_660x_board *board = comedi_board(dev);
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+ unsigned j;
+
+ for (i = 0; i < board->n_chips; ++i) {
+ for (j = 0; j < counters_per_chip; ++j)
+ mite_free_ring(devpriv->mite_rings[i][j]);
+ }
+}
+
+static void init_tio_chip(struct comedi_device *dev, int chipset)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned i;
+
+ /* init dma configuration register */
+ devpriv->dma_configuration_soft_copies[chipset] = 0;
+ for (i = 0; i < MAX_DMA_CHANNEL; ++i) {
+ devpriv->dma_configuration_soft_copies[chipset] |=
+ dma_select_bits(i, dma_selection_none) & dma_select_mask(i);
+ }
+ ni_660x_write_register(dev, chipset,
+ devpriv->dma_configuration_soft_copies[chipset],
+ NI660X_DMA_CFG);
+ for (i = 0; i < NUM_PFI_CHANNELS; ++i)
+ ni_660x_write_register(dev, chipset, 0, IOConfigReg(i));
+}
+
+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, NI660X_DIO32_OUTPUT);
+ }
+ /* on return, data[1] contains the value of the digital
+ * input and output lines. */
+ data[1] = (ni_660x_read_register(dev, 0, NI660X_DIO32_INPUT) >>
+ base_bitfield_channel);
+
+ return insn->n;
+}
+
+static void ni_660x_select_pfi_output(struct comedi_device *dev,
+ unsigned pfi_channel,
+ unsigned output_select)
+{
+ const struct ni_660x_board *board = comedi_board(dev);
+ 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->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)
+{
+ struct ni_660x_private *devpriv = dev->private;
+
+ 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;
+ }
+
+ devpriv->pfi_output_selects[chan] = source;
+ if (devpriv->pfi_direction_bits & (((uint64_t) 1) << chan))
+ ni_660x_select_pfi_output(dev, chan,
+ devpriv->pfi_output_selects[chan]);
+ return 0;
+}
+
+static int ni_660x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_660x_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint64_t bit = 1ULL << chan;
+ unsigned int val;
+ int ret;
+
+ switch (data[0]) {
+ case INSN_CONFIG_DIO_OUTPUT:
+ devpriv->pfi_direction_bits |= bit;
+ ni_660x_select_pfi_output(dev, chan,
+ devpriv->pfi_output_selects[chan]);
+ break;
+
+ case INSN_CONFIG_DIO_INPUT:
+ devpriv->pfi_direction_bits &= ~bit;
+ ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z);
+ break;
+
+ case INSN_CONFIG_DIO_QUERY:
+ data[1] = (devpriv->pfi_direction_bits & bit) ? COMEDI_OUTPUT
+ : COMEDI_INPUT;
+ break;
+
+ case INSN_CONFIG_SET_ROUTING:
+ ret = ni_660x_set_pfi_routing(dev, chan, data[1]);
+ if (ret)
+ return ret;
+ break;
+
+ case INSN_CONFIG_GET_ROUTING:
+ data[1] = devpriv->pfi_output_selects[chan];
+ break;
+
+ case INSN_CONFIG_FILTER:
+ val = ni_660x_read_register(dev, 0, IOConfigReg(chan));
+ val &= ~pfi_input_select_mask(chan);
+ val |= pfi_input_select_bits(chan, data[1]);
+ ni_660x_write_register(dev, 0, val, IOConfigReg(chan));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return insn->n;
+}
+
+static int ni_660x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_660x_board *board = NULL;
+ struct ni_660x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ unsigned i;
+ unsigned global_interrupt_config_bits;
+
+ if (context < ARRAY_SIZE(ni_660x_boards))
+ board = &ni_660x_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = ni_660x_allocate_private(dev);
+ if (ret < 0)
+ return ret;
+ devpriv = dev->private;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup2(devpriv->mite, 1);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, "error setting up mite\n");
+ return ret;
+ }
+
+ ret = ni_660x_alloc_mite_rings(dev);
+ if (ret < 0)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2 + NI_660X_MAX_NUM_COUNTERS);
+ if (ret)
+ return ret;
+
+ 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;
+ /* 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, NI660X_STC_DIO_CONTROL);
+
+ devpriv->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 (devpriv->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_tio_insn_read;
+ s->insn_write = ni_tio_insn_write;
+ s->insn_config = ni_tio_insn_config;
+ s->do_cmd = &ni_660x_cmd;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni_tio_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 = &devpriv->counter_dev->counters[i];
+
+ devpriv->counter_dev->counters[i].chip_index =
+ i / counters_per_chip;
+ devpriv->counter_dev->counters[i].counter_index =
+ i % counters_per_chip;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+ }
+ for (i = 0; i < board->n_chips; ++i)
+ init_tio_chip(dev, i);
+
+ for (i = 0; i < ni_660x_num_counters(dev); ++i)
+ ni_tio_init_counter(&devpriv->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->n_chips; ++i)
+ set_tio_counterswap(dev, i);
+
+ ret = request_irq(mite_irq(devpriv->mite), ni_660x_interrupt,
+ IRQF_SHARED, "ni_660x", dev);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, " irq not available\n");
+ return ret;
+ }
+ dev->irq = mite_irq(devpriv->mite);
+ global_interrupt_config_bits = Global_Int_Enable_Bit;
+ if (board->n_chips > 1)
+ global_interrupt_config_bits |= Cascade_Int_Enable_Bit;
+ ni_660x_write_register(dev, 0, global_interrupt_config_bits,
+ NI660X_GLOBAL_INT_CFG);
+
+ return 0;
+}
+
+static void ni_660x_detach(struct comedi_device *dev)
+{
+ struct ni_660x_private *devpriv = dev->private;
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ if (devpriv->counter_dev)
+ ni_gpct_device_destroy(devpriv->counter_dev);
+ if (devpriv->mite) {
+ ni_660x_free_mite_rings(dev);
+ mite_unsetup(devpriv->mite);
+ mite_free(devpriv->mite);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_660x_driver = {
+ .driver_name = "ni_660x",
+ .module = THIS_MODULE,
+ .auto_attach = ni_660x_auto_attach,
+ .detach = ni_660x_detach,
+};
+
+static int ni_660x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_660x_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_660x_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1310), BOARD_PCI6602 },
+ { PCI_VDEVICE(NI, 0x1360), BOARD_PXI6602 },
+ { PCI_VDEVICE(NI, 0x2c60), BOARD_PCI6601 },
+ { PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 },
+ { PCI_VDEVICE(NI, 0x1e40), BOARD_PXI6624 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_660x_pci_table);
+
+static struct pci_driver ni_660x_pci_driver = {
+ .name = "ni_660x",
+ .id_table = ni_660x_pci_table,
+ .probe = ni_660x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_660x_driver, ni_660x_pci_driver);
+
+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 00000000000..1002ceacfdc
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_670x.c
@@ -0,0 +1,300 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: ni_670x
+Description: National Instruments 670x
+Author: Bart Joris <bjoris@advalvas.be>
+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 <bjoris@advalvas.be> Last updated on 20/08/2001
+
+ Manuals:
+
+ 322110a.pdf PCI/PXI-6704 User Manual
+ 322110b.pdf PCI/PXI-6703/6704 User Manual
+
+*/
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+
+#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
+
+enum ni_670x_boardid {
+ BOARD_PCI6703,
+ BOARD_PXI6704,
+ BOARD_PCI6704,
+};
+
+struct ni_670x_board {
+ const char *name;
+ unsigned short ao_chans;
+};
+
+static const struct ni_670x_board ni_670x_boards[] = {
+ [BOARD_PCI6703] = {
+ .name = "PCI-6703",
+ .ao_chans = 16,
+ },
+ [BOARD_PXI6704] = {
+ .name = "PXI-6704",
+ .ao_chans = 32,
+ },
+ [BOARD_PCI6704] = {
+ .name = "PCI-6704",
+ .ao_chans = 32,
+ },
+};
+
+struct ni_670x_private {
+
+ struct mite_struct *mite;
+ int boardtype;
+ int dio;
+ unsigned int ao_readback[32];
+};
+
+static int ni_670x_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct ni_670x_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_670x_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_670x_private *devpriv = dev->private;
+ void __iomem *io_addr = devpriv->mite->daq_io_addr +
+ DIO_PORT0_DATA_OFFSET;
+
+ if (comedi_dio_update_state(s, data))
+ writel(s->state, io_addr);
+
+ data[1] = readl(io_addr);
+
+ return insn->n;
+}
+
+static int ni_670x_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_670x_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writel(s->io_bits, devpriv->mite->daq_io_addr + DIO_PORT0_DIR_OFFSET);
+
+ return insn->n;
+}
+
+static int ni_670x_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_670x_board *thisboard = NULL;
+ struct ni_670x_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (context < ARRAY_SIZE(ni_670x_boards))
+ thisboard = &ni_670x_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup(devpriv->mite);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, "error setting up mite\n");
+ return ret;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ 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);
+
+ return 0;
+}
+
+static void ni_670x_detach(struct comedi_device *dev)
+{
+ struct ni_670x_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+
+ if (dev->n_subdevices) {
+ s = &dev->subdevices[0];
+ if (s)
+ kfree(s->range_table_list);
+ }
+ if (devpriv && devpriv->mite) {
+ mite_unsetup(devpriv->mite);
+ mite_free(devpriv->mite);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_670x_driver = {
+ .driver_name = "ni_670x",
+ .module = THIS_MODULE,
+ .auto_attach = ni_670x_auto_attach,
+ .detach = ni_670x_detach,
+};
+
+static int ni_670x_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_670x_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_670x_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1290), BOARD_PCI6704 },
+ { PCI_VDEVICE(NI, 0x1920), BOARD_PXI6704 },
+ { PCI_VDEVICE(NI, 0x2c90), BOARD_PCI6703 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_670x_pci_table);
+
+static struct pci_driver ni_670x_pci_driver = {
+ .name = "ni_670x",
+ .id_table = ni_670x_pci_table,
+ .probe = ni_670x_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_670x_driver, ni_670x_pci_driver);
+
+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 00000000000..5bd19494dbf
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_at_a2150.c
@@ -0,0 +1,821 @@
+/*
+ comedi/drivers/ni_at_a2150.c
+ Driver for National Instruments AT-A2150 boards
+ Copyright (C) 2001, 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include "../comedidev.h"
+
+#include <linux/io.h>
+
+#include <asm/dma.h>
+
+#include "8253.h"
+#include "comedi_fc.h"
+
+#define A2150_SIZE 28
+#define A2150_DMA_BUFFER_SIZE 0xff00 /* size in bytes of dma buffer */
+
+/* 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, {
+ BIP_RANGE(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,
+ },
+};
+
+struct a2150_private {
+
+ volatile unsigned int count; /* number of data points left to be taken */
+ unsigned int dma; /* dma channel */
+ uint16_t *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 */
+};
+
+static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s);
+
+static int a2150_get_timing(struct comedi_device *dev, unsigned int *period,
+ int flags);
+static int a2150_set_chanlist(struct comedi_device *dev,
+ unsigned int start_channel,
+ unsigned int num_channels);
+/* 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 a2150_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+ unsigned int max_points, num_points, residue, leftover;
+ unsigned short dpnt;
+ static const int sample_size = sizeof(devpriv->dma_buffer[0]);
+
+ if (!dev->attached) {
+ comedi_error(dev, "premature interrupt");
+ return IRQ_HANDLED;
+ }
+ /* initialize async here to make sure s is not NULL */
+ async = s->async;
+ 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");
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+ }
+
+ if ((status & DMA_TC_BIT) == 0) {
+ comedi_error(dev, "caught non-dma interrupt? Aborting.");
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(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 */
+ 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;
+
+ cfc_handle_events(dev, s);
+
+ /* clear interrupt */
+ outw(0x00, dev->iobase + DMA_TC_CLEAR_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct a2150_private *devpriv = dev->private;
+
+ /* 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_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ if (cmd->chanlist_len == 2 && (chan0 == 1 || chan0 == 3)) {
+ dev_dbg(dev->class_dev,
+ "length 2 chanlist must be channels 0,1 or channels 2,3\n");
+ return -EINVAL;
+ }
+
+ if (cmd->chanlist_len == 3) {
+ dev_dbg(dev->class_dev,
+ "chanlist must have 1,2 or 4 channels\n");
+ return -EINVAL;
+ }
+
+ for (i = 1; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ if (chan != (chan0 + i)) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must be consecutive channels, counting upwards\n");
+ return -EINVAL;
+ }
+
+ if (chan == 2)
+ aref0 = aref;
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "channels 0/1 and 2/3 must have the same analog reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int a2150_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct a2150_board *thisboard = comedi_board(dev);
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ thisboard->ai_speed);
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ a2150_get_timing(dev, &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= a2150_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int a2150_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct a2150_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned long timer_base = dev->iobase + I8253_BASE_REG;
+ unsigned long lock_flags;
+ unsigned int old_config_bits = devpriv->config_bits;
+ unsigned int trigger_bits;
+
+ 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_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY);
+ i8254_write(timer_base, 0, 2, 72);
+
+ /* 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);
+
+ return 0;
+}
+
+static int a2150_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + STATUS_REG);
+ if (status & FNE_BIT)
+ return 0;
+ return -EBUSY;
+}
+
+static int a2150_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct a2150_private *devpriv = dev->private;
+ unsigned int n;
+ int ret;
+
+ /* 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 < 36; n++) {
+ ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ inw(dev->iobase + FIFO_DATA_REG);
+ }
+
+ /* read data */
+ for (n = 0; n < insn->n; n++) {
+ ret = comedi_timeout(dev, s, insn, a2150_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[n] = inw(dev->iobase + FIFO_DATA_REG);
+ 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)
+{
+ const struct a2150_board *thisboard = comedi_board(dev);
+ struct a2150_private *devpriv = dev->private;
+ 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)
+{
+ struct a2150_private *devpriv = dev->private;
+
+ 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;
+}
+
+/* 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)
+{
+ const struct a2150_board *thisboard;
+ struct a2150_private *devpriv;
+ struct comedi_subdevice *s;
+ unsigned int irq = it->options[1];
+ unsigned int dma = it->options[2];
+ static const int timeout = 2000;
+ int i;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], A2150_SIZE);
+ if (ret)
+ return ret;
+
+ dev->board_ptr = a2150_boards + a2150_probe(dev);
+ thisboard = comedi_board(dev);
+ dev->board_name = thisboard->name;
+
+ if ((irq >= 3 && irq <= 7) || (irq >= 9 && irq <= 12) ||
+ irq == 14 || irq == 15) {
+ ret = request_irq(irq, a2150_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ devpriv->irq_dma_bits |= IRQ_LVL_BITS(irq);
+ dev->irq = irq;
+ }
+ }
+
+ if (dev->irq && dma <= 7 && dma != 4) {
+ ret = request_dma(dma, dev->board_name);
+ if (ret == 0) {
+ devpriv->dma = dma;
+ devpriv->dma_buffer = kmalloc(A2150_DMA_BUFFER_SIZE,
+ GFP_KERNEL | GFP_DMA);
+ if (!devpriv->dma_buffer)
+ return -ENOMEM;
+
+ disable_dma(dma);
+ set_dma_mode(dma, DMA_MODE_READ);
+
+ devpriv->irq_dma_bits |= DMA_CHAN_BITS(dma);
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_OTHER;
+ s->n_chan = 4;
+ s->maxdata = 0xffff;
+ s->range_table = &range_a2150;
+ s->insn_read = a2150_ai_rinsn;
+ if (dev->irq && devpriv->dma) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmd = a2150_ai_cmd;
+ s->do_cmdtest = a2150_ai_cmdtest;
+ 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 void a2150_detach(struct comedi_device *dev)
+{
+ struct a2150_private *devpriv = dev->private;
+
+ if (dev->iobase)
+ outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG);
+ if (devpriv) {
+ if (devpriv->dma)
+ free_dma(devpriv->dma);
+ kfree(devpriv->dma_buffer);
+ }
+ comedi_legacy_detach(dev);
+};
+
+static struct comedi_driver ni_at_a2150_driver = {
+ .driver_name = "ni_at_a2150",
+ .module = THIS_MODULE,
+ .attach = a2150_attach,
+ .detach = a2150_detach,
+};
+module_comedi_driver(ni_at_a2150_driver);
+
+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 00000000000..c93b47bcca5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_at_ao.c
@@ -0,0 +1,409 @@
+/*
+ * 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 <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: ni_at_ao
+ * Description: National Instruments AT-AO-6/10
+ * Devices: (National Instruments) AT-AO-6 [at-ao-6]
+ * (National Instruments) AT-AO-10 [at-ao-10]
+ * Status: should work
+ * Author: David A. Schleef <ds@schleef.org>
+ * 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
+ */
+
+#include <linux/module.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+
+/*
+ * Register map
+ *
+ * Register-level programming information can be found in NI
+ * document 320379.pdf.
+ */
+#define ATAO_DIO_REG 0x00
+#define ATAO_CFG2_REG 0x02
+#define ATAO_CFG2_CALLD_NOP (0 << 14)
+#define ATAO_CFG2_CALLD(x) ((((x) >> 3) + 1) << 14)
+#define ATAO_CFG2_FFRTEN (1 << 13)
+#define ATAO_CFG2_DACS(x) (1 << (((x) / 2) + 8))
+#define ATAO_CFG2_LDAC(x) (1 << (((x) / 2) + 3))
+#define ATAO_CFG2_PROMEN (1 << 2)
+#define ATAO_CFG2_SCLK (1 << 1)
+#define ATAO_CFG2_SDATA (1 << 0)
+#define ATAO_CFG3_REG 0x04
+#define ATAO_CFG3_DMAMODE (1 << 6)
+#define ATAO_CFG3_CLKOUT (1 << 5)
+#define ATAO_CFG3_RCLKEN (1 << 4)
+#define ATAO_CFG3_DOUTEN2 (1 << 3)
+#define ATAO_CFG3_DOUTEN1 (1 << 2)
+#define ATAO_CFG3_EN2_5V (1 << 1)
+#define ATAO_CFG3_SCANEN (1 << 0)
+#define ATAO_82C53_BASE 0x06
+#define ATAO_CFG1_REG 0x0a
+#define ATAO_CFG1_EXTINT2EN (1 << 15)
+#define ATAO_CFG1_EXTINT1EN (1 << 14)
+#define ATAO_CFG1_CNTINT2EN (1 << 13)
+#define ATAO_CFG1_CNTINT1EN (1 << 12)
+#define ATAO_CFG1_TCINTEN (1 << 11)
+#define ATAO_CFG1_CNT1SRC (1 << 10)
+#define ATAO_CFG1_CNT2SRC (1 << 9)
+#define ATAO_CFG1_FIFOEN (1 << 8)
+#define ATAO_CFG1_GRP2WR (1 << 7)
+#define ATAO_CFG1_EXTUPDEN (1 << 6)
+#define ATAO_CFG1_DMARQ (1 << 5)
+#define ATAO_CFG1_DMAEN (1 << 4)
+#define ATAO_CFG1_CH(x) (((x) & 0xf) << 0)
+#define ATAO_STATUS_REG 0x0a
+#define ATAO_STATUS_FH (1 << 6)
+#define ATAO_STATUS_FE (1 << 5)
+#define ATAO_STATUS_FF (1 << 4)
+#define ATAO_STATUS_INT2 (1 << 3)
+#define ATAO_STATUS_INT1 (1 << 2)
+#define ATAO_STATUS_TCINT (1 << 1)
+#define ATAO_STATUS_PROMOUT (1 << 0)
+#define ATAO_FIFO_WRITE_REG 0x0c
+#define ATAO_FIFO_CLEAR_REG 0x0c
+#define ATAO_AO_REG(x) (0x0c + ((x) * 2))
+
+/* registers with _2_ are accessed when GRP2WR is set in CFG1 */
+#define ATAO_2_DMATCCLR_REG 0x00
+#define ATAO_2_INT1CLR_REG 0x02
+#define ATAO_2_INT2CLR_REG 0x04
+#define ATAO_2_RTSISHFT_REG 0x06
+#define ATAO_2_RTSISHFT_RSI (1 << 0)
+#define ATAO_2_RTSISTRB_REG 0x07
+
+struct atao_board {
+ const char *name;
+ int n_ao_chans;
+};
+
+static const struct atao_board atao_boards[] = {
+ {
+ .name = "at-ao-6",
+ .n_ao_chans = 6,
+ }, {
+ .name = "at-ao-10",
+ .n_ao_chans = 10,
+ },
+};
+
+struct atao_private {
+ unsigned short cfg1;
+ unsigned short cfg3;
+
+ /* Used for AO readback */
+ unsigned int ao_readback[10];
+
+ /* Used for caldac readback */
+ unsigned char caldac[21];
+};
+
+static void atao_select_reg_group(struct comedi_device *dev, int group)
+{
+ struct atao_private *devpriv = dev->private;
+
+ if (group)
+ devpriv->cfg1 |= ATAO_CFG1_GRP2WR;
+ else
+ devpriv->cfg1 &= ~ATAO_CFG1_GRP2WR;
+ outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG);
+}
+
+static int atao_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ if (chan == 0)
+ atao_select_reg_group(dev, 1);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ devpriv->ao_readback[chan] = val;
+
+ /* munge offset binary (unsigned) to two's complement */
+ val = comedi_offset_munge(s, val);
+ outw(val, dev->iobase + ATAO_AO_REG(chan));
+ }
+
+ if (chan == 0)
+ atao_select_reg_group(dev, 0);
+
+ return insn->n;
+}
+
+static int atao_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int atao_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + ATAO_DIO_REG);
+
+ data[1] = inw(dev->iobase + ATAO_DIO_REG);
+
+ return insn->n;
+}
+
+static int atao_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ if (s->io_bits & 0x0f)
+ devpriv->cfg3 |= ATAO_CFG3_DOUTEN1;
+ else
+ devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN1;
+ if (s->io_bits & 0xf0)
+ devpriv->cfg3 |= ATAO_CFG3_DOUTEN2;
+ else
+ devpriv->cfg3 &= ~ATAO_CFG3_DOUTEN2;
+
+ outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG);
+
+ return insn->n;
+}
+
+/*
+ * There are three DAC8800 TrimDACs on the board. These are 8-channel,
+ * 8-bit DACs that are used to calibrate the Analog Output channels.
+ * The factory default calibration values are stored in the EEPROM.
+ * The TrimDACs, and EEPROM addresses, are mapped as:
+ *
+ * Channel EEPROM Description
+ * ----------------- ------ -----------------------------------
+ * 0 - DAC0 Chan 0 0x30 AO Channel 0 Offset
+ * 1 - DAC0 Chan 1 0x31 AO Channel 0 Gain
+ * 2 - DAC0 Chan 2 0x32 AO Channel 1 Offset
+ * 3 - DAC0 Chan 3 0x33 AO Channel 1 Gain
+ * 4 - DAC0 Chan 4 0x34 AO Channel 2 Offset
+ * 5 - DAC0 Chan 5 0x35 AO Channel 2 Gain
+ * 6 - DAC0 Chan 6 0x36 AO Channel 3 Offset
+ * 7 - DAC0 Chan 7 0x37 AO Channel 3 Gain
+ * 8 - DAC1 Chan 0 0x38 AO Channel 4 Offset
+ * 9 - DAC1 Chan 1 0x39 AO Channel 4 Gain
+ * 10 - DAC1 Chan 2 0x3a AO Channel 5 Offset
+ * 11 - DAC1 Chan 3 0x3b AO Channel 5 Gain
+ * 12 - DAC1 Chan 4 0x3c 2.5V Offset
+ * 13 - DAC1 Chan 5 0x3d AO Channel 6 Offset (at-ao-10 only)
+ * 14 - DAC1 Chan 6 0x3e AO Channel 6 Gain (at-ao-10 only)
+ * 15 - DAC1 Chan 7 0x3f AO Channel 7 Offset (at-ao-10 only)
+ * 16 - DAC2 Chan 0 0x40 AO Channel 7 Gain (at-ao-10 only)
+ * 17 - DAC2 Chan 1 0x41 AO Channel 8 Offset (at-ao-10 only)
+ * 18 - DAC2 Chan 2 0x42 AO Channel 8 Gain (at-ao-10 only)
+ * 19 - DAC2 Chan 3 0x43 AO Channel 9 Offset (at-ao-10 only)
+ * 20 - DAC2 Chan 4 0x44 AO Channel 9 Gain (at-ao-10 only)
+ * DAC2 Chan 5 0x45 Reserved
+ * DAC2 Chan 6 0x46 Reserved
+ * DAC2 Chan 7 0x47 Reserved
+ */
+static int atao_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int bitstring;
+ unsigned int val;
+ int bit;
+
+ if (insn->n == 0)
+ return 0;
+
+ devpriv->caldac[chan] = data[insn->n - 1] & s->maxdata;
+
+ /* write the channel and last data value to the caldac */
+ bitstring = ((chan & 0x7) << 8) | devpriv->caldac[chan];
+
+ /* clock the bitstring to the caldac; MSB -> LSB */
+ for (bit = 1 << 10; bit; bit >>= 1) {
+ val = (bit & bitstring) ? ATAO_CFG2_SDATA : 0;
+
+ outw(val, dev->iobase + ATAO_CFG2_REG);
+ outw(val | ATAO_CFG2_SCLK, dev->iobase + ATAO_CFG2_REG);
+ }
+
+ /* strobe the caldac to load the value */
+ outw(ATAO_CFG2_CALLD(chan), dev->iobase + ATAO_CFG2_REG);
+ outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG);
+
+ return insn->n;
+}
+
+static int atao_calib_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->caldac[chan];
+
+ return insn->n;
+}
+
+static void atao_reset(struct comedi_device *dev)
+{
+ struct atao_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + ATAO_82C53_BASE;
+
+ /* This is the reset sequence described in the manual */
+
+ devpriv->cfg1 = 0;
+ outw(devpriv->cfg1, dev->iobase + ATAO_CFG1_REG);
+
+ /* Put outputs of counter 1 and counter 2 in a high state */
+ i8254_set_mode(timer_base, 0, 0, I8254_MODE4 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE4 | I8254_BINARY);
+ i8254_write(timer_base, 0, 0, 0x0003);
+
+ outw(ATAO_CFG2_CALLD_NOP, dev->iobase + ATAO_CFG2_REG);
+
+ devpriv->cfg3 = 0;
+ outw(devpriv->cfg3, dev->iobase + ATAO_CFG3_REG);
+
+ inw(dev->iobase + ATAO_FIFO_CLEAR_REG);
+
+ atao_select_reg_group(dev, 1);
+ outw(0, dev->iobase + ATAO_2_INT1CLR_REG);
+ outw(0, dev->iobase + ATAO_2_INT2CLR_REG);
+ outw(0, dev->iobase + ATAO_2_DMATCCLR_REG);
+ atao_select_reg_group(dev, 0);
+}
+
+static int atao_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct atao_board *board = comedi_board(dev);
+ struct atao_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x20);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ao_chans;
+ s->maxdata = 0x0fff;
+ s->range_table = it->options[3] ? &range_unipolar10 : &range_bipolar10;
+ s->insn_write = atao_ao_insn_write;
+ s->insn_read = atao_ao_insn_read;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[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 = atao_dio_insn_bits;
+ s->insn_config = atao_dio_insn_config;
+
+ /* caldac subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_CALIB;
+ s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL;
+ s->n_chan = (board->n_ao_chans * 2) + 1;
+ s->maxdata = 0xff;
+ s->insn_read = atao_calib_insn_read;
+ s->insn_write = atao_calib_insn_write;
+
+ /* EEPROM subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_UNUSED;
+
+ atao_reset(dev);
+
+ return 0;
+}
+
+static struct comedi_driver ni_at_ao_driver = {
+ .driver_name = "ni_at_ao",
+ .module = THIS_MODULE,
+ .attach = atao_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &atao_boards[0].name,
+ .offset = sizeof(struct atao_board),
+ .num_names = ARRAY_SIZE(atao_boards),
+};
+module_comedi_driver(ni_at_ao_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for NI AT-AO-6/10 boards");
+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 00000000000..d03935257b9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_atmio.c
@@ -0,0 +1,494 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <trux@truxton.com>
+
+ 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 <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include <linux/isapnp.h>
+
+#include "ni_stc.h"
+#include "8255.h"
+
+#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
+
+};
+
+/* 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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);
+
+#include "ni_mio_common.c"
+
+static int ni_isapnp_find_board(struct pnp_dev **dev)
+{
+ struct pnp_dev *isapnp_dev = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(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 == ARRAY_SIZE(ni_boards))
+ return -ENODEV;
+ *dev = isapnp_dev;
+ return 0;
+}
+
+static int ni_getboardtype(struct comedi_device *dev)
+{
+ int device_id = ni_read_eeprom(dev, 511);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(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;
+}
+
+static int ni_atmio_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct ni_board_struct *boardtype;
+ struct ni_private *devpriv;
+ struct pnp_dev *isapnp_dev;
+ int ret;
+ unsigned long iobase;
+ int board;
+ unsigned int irq;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+ devpriv = dev->private;
+
+ 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;
+ }
+
+ ret = comedi_request_region(dev, iobase, NI_SIZE);
+ if (ret)
+ return ret;
+
+ /* get board type */
+
+ board = ni_getboardtype(dev);
+ if (board < 0)
+ return -EIO;
+
+ dev->board_ptr = ni_boards + board;
+ boardtype = comedi_board(dev);
+
+ 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);
+ if (ret < 0)
+ return ret;
+
+
+ return 0;
+}
+
+static void ni_atmio_detach(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ mio_common_detach(dev);
+ comedi_legacy_detach(dev);
+ if (devpriv->isapnp_dev)
+ pnp_device_detach(devpriv->isapnp_dev);
+}
+
+static struct comedi_driver ni_atmio_driver = {
+ .driver_name = "ni_atmio",
+ .module = THIS_MODULE,
+ .attach = ni_atmio_attach,
+ .detach = ni_atmio_detach,
+};
+module_comedi_driver(ni_atmio_driver);
diff --git a/drivers/staging/comedi/drivers/ni_atmio16d.c b/drivers/staging/comedi/drivers/ni_atmio16d.c
new file mode 100644
index 00000000000..6ad27f50c6e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_atmio16d.c
@@ -0,0 +1,781 @@
+/*
+ comedi/drivers/ni_atmio16d.c
+ Hardware driver for National Instruments AT-MIO16D board
+ Copyright (C) 2000 Chris R. Baugher <baugher@enteract.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.
+ */
+/*
+Driver: ni_atmio16d
+Description: National Instruments AT-MIO-16D
+Author: Chris R. Baugher <baugher@enteract.com>
+Status: unknown
+Devices: [National Instruments] AT-MIO-16 (atmio16), AT-MIO-16D (atmio16d)
+*/
+/*
+ * I must give credit here to Michal Dobes <dobes@tesnet.cz> 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 <linux/module.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#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 */
+
+struct atmio16_board_t {
+
+ const char *name;
+ int has_8255;
+};
+
+/* 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)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ 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->read_subdev;
+
+ comedi_buf_put(s, 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;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) {
+ /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ } else {
+#if 0
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+#endif
+ }
+
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000);
+#if 0
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, SLOWEST_TIMER);
+#endif
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else { /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+static int atmio16d_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int timer, base_clock;
+ unsigned int sample_count, tmp, chan, gain;
+ int i;
+
+ /* 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;
+}
+
+static int atmio16d_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + STAT_REG);
+ if (status & STAT_AD_CONVAVAIL)
+ return 0;
+ if (status & STAT_AD_OVERFLOW) {
+ outw(0, dev->iobase + AD_CLEAR_REG);
+ return -EOVERFLOW;
+ }
+ return -EBUSY;
+}
+
+static int atmio16d_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ int i;
+ int chan;
+ int gain;
+ int ret;
+
+ 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 */
+ ret = comedi_timeout(dev, s, insn, atmio16d_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* 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;
+ }
+
+ return i;
+}
+
+static int atmio16d_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ int i;
+
+ 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)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ int i;
+ int chan;
+ int d;
+
+ 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 (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + MIO_16_DIG_OUT_REG);
+
+ data[1] = inw(dev->iobase + MIO_16_DIG_IN_REG);
+
+ return insn->n;
+}
+
+static int atmio16d_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct atmio16d_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ 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 insn->n;
+}
+
+/*
+ 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)
+{
+ const struct atmio16_board_t *board = comedi_board(dev);
+ struct atmio16d_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], ATMIO16D_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* reset the atmio16d hardware */
+ reset_atmio16d(dev);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], atmio16d_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* 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];
+ /* 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 = atmio16d_ai_insn_read;
+ 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;
+ }
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 16;
+ s->do_cmdtest = atmio16d_ai_cmdtest;
+ s->do_cmd = atmio16d_ai_cmd;
+ s->cancel = atmio16d_ai_cancel;
+ }
+
+ /* ao subdevice */
+ s = &dev->subdevices[1];
+ 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 = &dev->subdevices[2];
+ 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 = &dev->subdevices[3];
+ if (board->has_8255) {
+ ret = subdev_8255_init(dev, s, NULL, dev->iobase);
+ if (ret)
+ return ret;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+/* don't yet know how to deal with counter/timers */
+#if 0
+ s = &dev->subdevices[4];
+ /* do */
+ s->type = COMEDI_SUBD_TIMER;
+ s->n_chan = 0;
+ s->maxdata = 0
+#endif
+
+ return 0;
+}
+
+static void atmio16d_detach(struct comedi_device *dev)
+{
+ reset_atmio16d(dev);
+ comedi_legacy_detach(dev);
+}
+
+static const struct atmio16_board_t atmio16_boards[] = {
+ {
+ .name = "atmio16",
+ .has_8255 = 0,
+ }, {
+ .name = "atmio16d",
+ .has_8255 = 1,
+ },
+};
+
+static struct comedi_driver atmio16d_driver = {
+ .driver_name = "atmio16",
+ .module = THIS_MODULE,
+ .attach = atmio16d_attach,
+ .detach = atmio16d_detach,
+ .board_name = &atmio16_boards[0].name,
+ .num_names = ARRAY_SIZE(atmio16_boards),
+ .offset = sizeof(struct atmio16_board_t),
+};
+module_comedi_driver(atmio16d_driver);
+
+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 00000000000..728bf7f14f7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_daq_700.c
@@ -0,0 +1,267 @@
+/*
+ * comedi/drivers/ni_daq_700.c
+ * Driver for DAQCard-700 DIO/AI
+ * copied from 8255
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+Driver: ni_daq_700
+Description: National Instruments PCMCIA DAQCard-700 DIO only
+Author: Fred Brooks <nsaspook@nsaspook.com>,
+ based on ni_daq_dio24 by Daniel Vecino Castel <dvecino@able.es>
+Devices: [National Instruments] PCMCIA DAQ-Card-700 (ni_daq_700)
+Status: works
+Updated: Wed, 19 Sep 2012 12:07:20 +0000
+
+The daqcard-700 appears in Comedi as a digital I/O subdevice (0) with
+16 channels and a analog input subdevice (1) with 16 single-ended channels.
+
+Digital: The channel 0 corresponds to the daqcard-700's output
+port, bit 0; channel 8 corresponds to the input port, bit 0.
+
+Digital direction configuration: channels 0-7 output, 8-15 input (8225 device
+emu as port A output, port B input, port C N/A).
+
+Analog: The input range is 0 to 4095 for -10 to +10 volts
+IRQ is assigned but not used.
+
+Version 0.1 Original DIO only driver
+Version 0.2 DIO and basic AI analog input support on 16 se channels
+
+Manuals: Register level: http://www.ni.com/pdf/manuals/340698.pdf
+ User Manual: http://www.ni.com/pdf/manuals/320676d.pdf
+*/
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+/* daqcard700 registers */
+#define DIO_W 0x04 /* WO 8bit */
+#define DIO_R 0x05 /* RO 8bit */
+#define CMD_R1 0x00 /* WO 8bit */
+#define CMD_R2 0x07 /* RW 8bit */
+#define CMD_R3 0x05 /* W0 8bit */
+#define STA_R1 0x00 /* RO 8bit */
+#define STA_R2 0x01 /* RO 8bit */
+#define ADFIFO_R 0x02 /* RO 16bit */
+#define ADCLEAR_R 0x01 /* WO 8bit */
+#define CDA_R0 0x08 /* RW 8bit */
+#define CDA_R1 0x09 /* RW 8bit */
+#define CDA_R2 0x0A /* RW 8bit */
+#define CMO_R 0x0B /* RO 8bit */
+#define TIC_R 0x06 /* WO 8bit */
+
+static int daq700_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0xff)
+ outb(s->state & 0xff, dev->iobase + DIO_W);
+ }
+
+ val = s->state & 0xff;
+ val |= inb(dev->iobase + DIO_R) << 8;
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int daq700_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /* The DIO channels are not configurable, fix the io_bits */
+ s->io_bits = 0x00ff;
+
+ return insn->n;
+}
+
+static int daq700_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + STA_R2);
+ if ((status & 0x03))
+ return -EOVERFLOW;
+ status = inb(dev->iobase + STA_R1);
+ if ((status & 0x02))
+ return -ENODATA;
+ if ((status & 0x11) == 0x01)
+ return 0;
+ return -EBUSY;
+}
+
+static int daq700_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ int n, chan;
+ int d;
+ int ret;
+
+ chan = CR_CHAN(insn->chanspec);
+ /* write channel to multiplexer */
+ /* set mask scan bit high to disable scanning */
+ outb(chan | 0x80, dev->iobase + CMD_R1);
+ /* mux needs 2us to really settle [Fred Brooks]. */
+ udelay(2);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion with out0 L to H */
+ outb(0x00, dev->iobase + CMD_R2); /* enable ADC conversions */
+ outb(0x30, dev->iobase + CMO_R); /* mode 0 out0 L, from H */
+ /* mode 1 out0 H, L to H, start conversion */
+ outb(0x32, dev->iobase + CMO_R);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, daq700_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ d = inw(dev->iobase + ADFIFO_R);
+ /* mangle the data as necessary */
+ /* Bipolar Offset Binary: 0 to 4095 for -10 to +10 */
+ d &= 0x0fff;
+ d ^= 0x0800;
+ data[n] = d;
+ }
+ return n;
+}
+
+/*
+ * Data acquisition is enabled.
+ * The counter 0 output is high.
+ * The I/O connector pin CLK1 drives counter 1 source.
+ * Multiple-channel scanning is disabled.
+ * All interrupts are disabled.
+ * The analog input range is set to +-10 V
+ * The analog input mode is single-ended.
+ * The analog input circuitry is initialized to channel 0.
+ * The A/D FIFO is cleared.
+ */
+static void daq700_ai_config(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned long iobase = dev->iobase;
+
+ outb(0x80, iobase + CMD_R1); /* disable scanning, ADC to chan 0 */
+ outb(0x00, iobase + CMD_R2); /* clear all bits */
+ outb(0x00, iobase + CMD_R3); /* set +-10 range */
+ outb(0x32, iobase + CMO_R); /* config counter mode1, out0 to H */
+ outb(0x00, iobase + TIC_R); /* clear counter interrupt */
+ outb(0x00, iobase + ADCLEAR_R); /* clear the ADC FIFO */
+ inw(iobase + ADFIFO_R); /* read 16bit junk from FIFO to clear */
+}
+
+static int daq700_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ /* DAQCard-700 dio */
+ s = &dev->subdevices[0];
+ 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->insn_bits = daq700_dio_insn_bits;
+ s->insn_config = daq700_dio_insn_config;
+ s->io_bits = 0x00ff;
+
+ /* DAQCard-700 ai */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AI;
+ /* we support single-ended (ground) */
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = 16;
+ s->maxdata = (1 << 12) - 1;
+ s->range_table = &range_bipolar10;
+ s->insn_read = daq700_ai_rinsn;
+ daq700_ai_config(dev, s);
+
+ return 0;
+}
+
+static struct comedi_driver daq700_driver = {
+ .driver_name = "ni_daq_700",
+ .module = THIS_MODULE,
+ .auto_attach = daq700_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int daq700_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &daq700_driver);
+}
+
+static const struct pcmcia_device_id daq700_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4743),
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, daq700_cs_ids);
+
+static struct pcmcia_driver daq700_cs_driver = {
+ .name = "ni_daq_700",
+ .owner = THIS_MODULE,
+ .id_table = daq700_cs_ids,
+ .probe = daq700_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(daq700_driver, daq700_cs_driver);
+
+MODULE_AUTHOR("Fred Brooks <nsaspook@nsaspook.com>");
+MODULE_DESCRIPTION(
+ "Comedi driver for National Instruments PCMCIA DAQCard-700 DIO/AI");
+MODULE_VERSION("0.2.00");
+MODULE_LICENSE("GPL");
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 00000000000..925e82c65b2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_daq_dio24.c
@@ -0,0 +1,99 @@
+/*
+ comedi/drivers/ni_daq_dio24.c
+ Driver for National Instruments PCMCIA DAQ-Card DIO-24
+ Copyright (C) 2002 Daniel Vecino Castel <dvecino@able.es>
+
+ 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
+ <dahinds@users.sourceforge.net>. 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.
+*/
+/*
+Driver: ni_daq_dio24
+Description: National Instruments PCMCIA DAQ-Card DIO-24
+Author: Daniel Vecino Castel <dvecino@able.es>
+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.
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#include "8255.h"
+
+static int dio24_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ link->config_flags |= CONF_AUTO_SET_IO;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* 8255 dio */
+ s = &dev->subdevices[0];
+ ret = subdev_8255_init(dev, s, NULL, dev->iobase);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver driver_dio24 = {
+ .driver_name = "ni_daq_dio24",
+ .module = THIS_MODULE,
+ .auto_attach = dio24_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int dio24_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_dio24);
+}
+
+static const struct pcmcia_device_id dio24_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x475c), /* daqcard-dio24 */
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, dio24_cs_ids);
+
+static struct pcmcia_driver dio24_cs_driver = {
+ .name = "ni_daq_dio24",
+ .owner = THIS_MODULE,
+ .id_table = dio24_cs_ids,
+ .probe = dio24_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_dio24, dio24_cs_driver);
+
+MODULE_AUTHOR("Daniel Vecino Castel <dvecino@able.es>");
+MODULE_DESCRIPTION(
+ "Comedi driver for National Instruments PCMCIA DAQ-Card DIO-24");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc.c b/drivers/staging/comedi/drivers/ni_labpc.c
new file mode 100644
index 00000000000..3e3f940fa57
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc.c
@@ -0,0 +1,1515 @@
+/*
+ * comedi/drivers/ni_labpc.c
+ * Driver for National Instruments Lab-PC series boards and compatibles
+ * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: ni_labpc
+ * Description: National Instruments Lab-PC (& compatibles)
+ * Devices: (National Instruments) Lab-PC-1200 [lab-pc-1200]
+ * (National Instruments) Lab-PC-1200AI [lab-pc-1200ai]
+ * (National Instruments) Lab-PC+ [lab-pc+]
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * Configuration options - ISA boards:
+ * [0] - I/O port base address
+ * [1] - IRQ (optional, required for timed or externally triggered
+ * conversions)
+ * [2] - DMA channel (optional)
+ *
+ * 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.
+ *
+ * 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)
+ * 320502b (lab-pc+)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+#include "8253.h"
+#include "8255.h"
+#include "comedi_fc.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+#define LABPC_SIZE 0x20 /* size of ISA io region */
+
+enum scan_mode {
+ MODE_SINGLE_CHAN,
+ MODE_SINGLE_CHAN_INTERVAL,
+ MODE_MULT_CHAN_UP,
+ MODE_MULT_CHAN_DOWN,
+};
+
+static const struct comedi_lrange range_labpc_plus_ai = {
+ 16, {
+ 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)
+ }
+};
+
+static const struct comedi_lrange range_labpc_1200_ai = {
+ 14, {
+ 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)
+ }
+};
+
+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 __iomem *)address);
+}
+
+static inline void labpc_writeb(unsigned int byte, unsigned long address)
+{
+ writeb(byte, (void __iomem *)address);
+}
+
+#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISA)
+static const struct labpc_boardinfo labpc_boards[] = {
+ {
+ .name = "lab-pc-1200",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ }, {
+ .name = "lab-pc-1200ai",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .is_labpc1200 = 1,
+ }, {
+ .name = "lab-pc+",
+ .ai_speed = 12000,
+ .has_ao = 1,
+ },
+};
+#endif
+
+static void labpc_counter_load(struct comedi_device *dev,
+ unsigned long base_address,
+ unsigned int counter_number,
+ unsigned int count,
+ unsigned int mode)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+
+ if (board->has_mmio) {
+ void __iomem *mmio_base = (void __iomem *)base_address;
+
+ i8254_mm_set_mode(mmio_base, 0, counter_number, mode);
+ i8254_mm_write(mmio_base, 0, counter_number, count);
+ } else {
+ i8254_set_mode(base_address, 0, counter_number, mode);
+ i8254_write(base_address, 0, counter_number, count);
+ }
+}
+
+static void labpc_counter_set_mode(struct comedi_device *dev,
+ unsigned long base_address,
+ unsigned int counter_number,
+ unsigned int mode)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+
+ if (board->has_mmio) {
+ void __iomem *mmio_base = (void __iomem *)base_address;
+
+ i8254_mm_set_mode(mmio_base, 0, counter_number, mode);
+ } else {
+ i8254_set_mode(base_address, 0, counter_number, mode);
+ }
+}
+
+static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+ devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG);
+ devpriv->write_byte(devpriv->cmd2, dev->iobase + CMD2_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ devpriv->cmd3 = 0;
+ devpriv->write_byte(devpriv->cmd3, dev->iobase + CMD3_REG);
+
+ return 0;
+}
+
+static void labpc_ai_set_chan_and_gain(struct comedi_device *dev,
+ enum scan_mode mode,
+ unsigned int chan,
+ unsigned int range,
+ unsigned int aref)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+
+ if (board->is_labpc1200) {
+ /*
+ * The LabPC-1200 boards do not have a gain
+ * of '0x10'. Skip the range values that would
+ * result in this gain.
+ */
+ range += (range > 0) + (range > 7);
+ }
+
+ /* munge channel bits for differential/scan disabled mode */
+ if ((mode == MODE_SINGLE_CHAN || mode == MODE_SINGLE_CHAN_INTERVAL) &&
+ aref == AREF_DIFF)
+ chan *= 2;
+ devpriv->cmd1 = CMD1_MA(chan);
+ devpriv->cmd1 |= CMD1_GAIN(range);
+
+ devpriv->write_byte(devpriv->cmd1, dev->iobase + CMD1_REG);
+}
+
+static void labpc_setup_cmd6_reg(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ enum scan_mode mode,
+ enum transfer_type xfer,
+ unsigned int range,
+ unsigned int aref,
+ bool ena_intr)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+
+ if (!board->is_labpc1200)
+ return;
+
+ /* reference inputs to ground or common? */
+ if (aref != AREF_GROUND)
+ devpriv->cmd6 |= CMD6_NRSE;
+ else
+ devpriv->cmd6 &= ~CMD6_NRSE;
+
+ /* bipolar or unipolar range? */
+ if (comedi_range_is_unipolar(s, range))
+ devpriv->cmd6 |= CMD6_ADCUNI;
+ else
+ devpriv->cmd6 &= ~CMD6_ADCUNI;
+
+ /* interrupt on fifo half full? */
+ if (xfer == fifo_half_full_transfer)
+ devpriv->cmd6 |= CMD6_HFINTEN;
+ else
+ devpriv->cmd6 &= ~CMD6_HFINTEN;
+
+ /* enable interrupt on counter a1 terminal count? */
+ if (ena_intr)
+ devpriv->cmd6 |= CMD6_DQINTEN;
+ else
+ devpriv->cmd6 &= ~CMD6_DQINTEN;
+
+ /* are we scanning up or down through channels? */
+ if (mode == MODE_MULT_CHAN_UP)
+ devpriv->cmd6 |= CMD6_SCANUP;
+ else
+ devpriv->cmd6 &= ~CMD6_SCANUP;
+
+ devpriv->write_byte(devpriv->cmd6, dev->iobase + CMD6_REG);
+}
+
+static unsigned int labpc_read_adc_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int lsb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG);
+ unsigned int msb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG);
+
+ return (msb << 8) | lsb;
+}
+
+static void labpc_clear_adc_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ devpriv->write_byte(0x1, dev->iobase + ADC_FIFO_CLEAR_REG);
+ labpc_read_adc_fifo(dev);
+}
+
+static int labpc_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG);
+ if (devpriv->stat1 & STAT1_DAVAIL)
+ return 0;
+ return -EBUSY;
+}
+
+static int labpc_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ int ret;
+ int i;
+
+ /* disable timed conversions, interrupt generation and dma */
+ labpc_cancel(dev, s);
+
+ labpc_ai_set_chan_and_gain(dev, MODE_SINGLE_CHAN, chan, range, aref);
+
+ labpc_setup_cmd6_reg(dev, s, MODE_SINGLE_CHAN, fifo_not_empty_transfer,
+ range, aref, false);
+
+ /* setup cmd4 register */
+ devpriv->cmd4 = 0;
+ devpriv->cmd4 |= CMD4_ECLKRCV;
+ /* single-ended/differential */
+ if (aref == AREF_DIFF)
+ devpriv->cmd4 |= CMD4_SEDIFF;
+ devpriv->write_byte(devpriv->cmd4, dev->iobase + CMD4_REG);
+
+ /* initialize pacer counter to prevent any problems */
+ labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG,
+ 0, I8254_MODE2);
+
+ labpc_clear_adc_fifo(dev);
+
+ for (i = 0; i < insn->n; i++) {
+ /* trigger conversion */
+ devpriv->write_byte(0x1, dev->iobase + ADC_START_CONVERT_REG);
+
+ ret = comedi_timeout(dev, s, insn, labpc_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = labpc_read_adc_fifo(dev);
+ }
+
+ return insn->n;
+}
+
+static bool labpc_use_continuous_mode(const struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ if (mode == MODE_SINGLE_CHAN || cmd->scan_begin_src == TRIG_FOLLOW)
+ return true;
+
+ return false;
+}
+
+static unsigned int labpc_ai_convert_period(const struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ if (cmd->convert_src != TRIG_TIMER)
+ return 0;
+
+ if (mode == 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,
+ enum scan_mode mode, unsigned int ns)
+{
+ if (cmd->convert_src != TRIG_TIMER)
+ return;
+
+ if (mode == 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,
+ enum scan_mode mode)
+{
+ if (cmd->scan_begin_src != TRIG_TIMER)
+ return 0;
+
+ if (mode == 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,
+ enum scan_mode mode, unsigned int ns)
+{
+ if (cmd->scan_begin_src != TRIG_TIMER)
+ return;
+
+ if (mode == MODE_SINGLE_CHAN && cmd->convert_src == TRIG_TIMER)
+ return;
+
+ cmd->scan_begin_arg = ns;
+}
+
+/* figures out what counter values to use based on command */
+static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd,
+ enum scan_mode mode)
+{
+ struct labpc_private *devpriv = dev->private;
+ /* 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;
+ unsigned int scan_period;
+ unsigned int convert_period;
+
+ /*
+ * if both convert and scan triggers are TRIG_TIMER, then they
+ * both rely on counter b0
+ */
+ convert_period = labpc_ai_convert_period(cmd, mode);
+ scan_period = labpc_ai_scan_period(cmd, mode);
+ if (convert_period && scan_period) {
+ /*
+ * pick the lowest b0 divisor value we can (for maximum input
+ * clock speed on convert and scan counters)
+ */
+ devpriv->divisor_b0 = (scan_period - 1) /
+ (I8254_OSC_BASE_2MHZ * 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 = I8254_OSC_BASE_2MHZ * 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 =
+ (convert_period + (base_period / 2)) / base_period;
+ devpriv->divisor_b1 =
+ (scan_period + (base_period / 2)) / base_period;
+ break;
+ case TRIG_ROUND_UP:
+ devpriv->divisor_a0 =
+ (convert_period + (base_period - 1)) / base_period;
+ devpriv->divisor_b1 =
+ (scan_period + (base_period - 1)) / base_period;
+ break;
+ case TRIG_ROUND_DOWN:
+ devpriv->divisor_a0 = convert_period / base_period;
+ devpriv->divisor_b1 = scan_period / 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, mode,
+ base_period * devpriv->divisor_a0);
+ labpc_set_ai_scan_period(cmd, mode,
+ base_period * devpriv->divisor_b1);
+ /*
+ * if only one TRIG_TIMER is used, we can employ the generic
+ * cascaded timing functions
+ */
+ } else if (scan_period) {
+ /*
+ * calculate cascaded counter values
+ * that give desired scan timing
+ */
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+ &devpriv->divisor_b1,
+ &devpriv->divisor_b0,
+ &scan_period, cmd->flags);
+ labpc_set_ai_scan_period(cmd, mode, scan_period);
+ } else if (convert_period) {
+ /*
+ * calculate cascaded counter values
+ * that give desired conversion timing
+ */
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+ &devpriv->divisor_a0,
+ &devpriv->divisor_b0,
+ &convert_period, cmd->flags);
+ labpc_set_ai_convert_period(cmd, mode, convert_period);
+ }
+}
+
+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;
+
+ pr_err("ni_labpc: bug! cannot determine AI scan mode\n");
+ return 0;
+}
+
+static int labpc_ai_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ enum scan_mode mode = labpc_ai_scan_mode(cmd);
+ unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+ unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+ unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
+ int i;
+
+ if (mode == MODE_SINGLE_CHAN)
+ return 0;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+ switch (mode) {
+ case MODE_SINGLE_CHAN:
+ break;
+ case MODE_SINGLE_CHAN_INTERVAL:
+ if (chan != chan0) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ case MODE_MULT_CHAN_UP:
+ if (chan != i) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ case MODE_MULT_CHAN_DOWN:
+ if (chan != (cmd->chanlist_len - i - 1)) {
+ dev_dbg(dev->class_dev,
+ "channel scanning order specified in chanlist is not supported by hardware\n");
+ return -EINVAL;
+ }
+ break;
+ }
+
+ if (range != range0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same range\n");
+ return -EINVAL;
+ }
+
+ if (aref != aref0) {
+ dev_dbg(dev->class_dev,
+ "entries in chanlist must all have the same reference\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int labpc_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ int err = 0;
+ int tmp, tmp2;
+ unsigned int stop_mask;
+ enum scan_mode mode;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+
+ stop_mask = TRIG_COUNT | TRIG_NONE;
+ if (board->is_labpc1200)
+ stop_mask |= TRIG_EXT;
+ err |= cfc_check_trigger_src(&cmd->stop_src, stop_mask);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* 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: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* start_arg value is ignored */
+ break;
+ }
+
+ if (!cmd->chanlist_len)
+ err |= -EINVAL;
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+
+ /* make sure scan timing is not too fast */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ cmd->convert_arg * cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ai_speed * cmd->chanlist_len);
+ }
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ 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;
+ mode = labpc_ai_scan_mode(cmd);
+ labpc_adc_timing(dev, cmd, mode);
+ if (tmp != cmd->convert_arg || tmp2 != cmd->scan_begin_arg)
+ err++;
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= labpc_ai_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ enum scan_mode mode = labpc_ai_scan_mode(cmd);
+ unsigned int chanspec = (mode == MODE_MULT_CHAN_UP)
+ ? cmd->chanlist[cmd->chanlist_len - 1]
+ : cmd->chanlist[0];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ enum transfer_type xfer;
+ unsigned long flags;
+
+ /* make sure board is disabled before setting up acquisition */
+ labpc_cancel(dev, s);
+
+ /* 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
+ */
+ labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG,
+ 1, 3, I8254_MODE0);
+ } else {
+ /* just put counter a1 in mode 0 to set its output low */
+ labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG,
+ 1, I8254_MODE0);
+ }
+
+ /* figure out what method we will use to transfer data */
+ if (labpc_have_dma_chan(dev) &&
+ /* dma unsafe at RT priority,
+ * and too much setup time for TRIG_WAKE_EOS */
+ (cmd->flags & (TRIG_WAKE_EOS | TRIG_RT)) == 0)
+ xfer = isa_dma_transfer;
+ else if (/* pc-plus has no fifo-half full interrupt */
+ board->is_labpc1200 &&
+ /* 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;
+
+ labpc_ai_set_chan_and_gain(dev, mode, chan, range, aref);
+
+ labpc_setup_cmd6_reg(dev, s, mode, xfer, range, aref,
+ (cmd->stop_src == TRIG_EXT));
+
+ /* manual says to set scan enable bit on second pass */
+ if (mode == MODE_MULT_CHAN_UP || mode == MODE_MULT_CHAN_DOWN) {
+ devpriv->cmd1 |= CMD1_SCANEN;
+ /* 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->cmd1, dev->iobase + CMD1_REG);
+ }
+
+ devpriv->write_byte(cmd->chanlist_len,
+ dev->iobase + INTERVAL_COUNT_REG);
+ /* load count */
+ devpriv->write_byte(0x1, dev->iobase + INTERVAL_STROBE_REG);
+
+ if (cmd->convert_src == TRIG_TIMER ||
+ cmd->scan_begin_src == TRIG_TIMER) {
+ /* set up pacing */
+ labpc_adc_timing(dev, cmd, mode);
+ /* load counter b0 in mode 3 */
+ labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG,
+ 0, devpriv->divisor_b0, I8254_MODE3);
+ }
+ /* set up conversion pacing */
+ if (labpc_ai_convert_period(cmd, mode)) {
+ /* load counter a0 in mode 2 */
+ labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG,
+ 0, devpriv->divisor_a0, I8254_MODE2);
+ } else {
+ /* initialize pacer counter to prevent any problems */
+ labpc_counter_set_mode(dev, dev->iobase + COUNTER_A_BASE_REG,
+ 0, I8254_MODE2);
+ }
+
+ /* set up scan pacing */
+ if (labpc_ai_scan_period(cmd, mode)) {
+ /* load counter b1 in mode 2 */
+ labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG,
+ 1, devpriv->divisor_b1, I8254_MODE2);
+ }
+
+ labpc_clear_adc_fifo(dev);
+
+ if (xfer == isa_dma_transfer)
+ labpc_setup_dma(dev, s);
+
+ /* enable error interrupts */
+ devpriv->cmd3 |= CMD3_ERRINTEN;
+ /* enable fifo not empty interrupt? */
+ if (xfer == fifo_not_empty_transfer)
+ devpriv->cmd3 |= CMD3_FIFOINTEN;
+ devpriv->write_byte(devpriv->cmd3, dev->iobase + CMD3_REG);
+
+ /* setup any external triggering/pacing (cmd4 register) */
+ devpriv->cmd4 = 0;
+ if (cmd->convert_src != TRIG_EXT)
+ devpriv->cmd4 |= CMD4_ECLKRCV;
+ /* 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, mode)) {
+ devpriv->cmd4 |= CMD4_INTSCAN;
+ if (cmd->scan_begin_src == TRIG_EXT)
+ devpriv->cmd4 |= CMD4_EOIRCV;
+ }
+ /* single-ended/differential */
+ if (aref == AREF_DIFF)
+ devpriv->cmd4 |= CMD4_SEDIFF;
+ devpriv->write_byte(devpriv->cmd4, dev->iobase + CMD4_REG);
+
+ /* startup acquisition */
+
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* use 2 cascaded counters for pacing */
+ devpriv->cmd2 |= CMD2_TBSEL;
+
+ devpriv->cmd2 &= ~(CMD2_SWTRIG | CMD2_HWTRIG | CMD2_PRETRIG);
+ if (cmd->start_src == TRIG_EXT)
+ devpriv->cmd2 |= CMD2_HWTRIG;
+ else
+ devpriv->cmd2 |= CMD2_SWTRIG;
+ if (cmd->stop_src == TRIG_EXT)
+ devpriv->cmd2 |= (CMD2_HWTRIG | CMD2_PRETRIG);
+
+ devpriv->write_byte(devpriv->cmd2, dev->iobase + CMD2_REG);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ return 0;
+}
+
+/* read all available samples from ai fifo */
+static int labpc_drain_fifo(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_async *async = dev->read_subdev->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned short data;
+ const int timeout = 10000;
+ unsigned int i;
+
+ devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG);
+
+ for (i = 0; (devpriv->stat1 & STAT1_DAVAIL) && i < timeout;
+ i++) {
+ /* quit if we have all the data we want */
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->count == 0)
+ break;
+ devpriv->count--;
+ }
+ data = labpc_read_adc_fifo(dev);
+ cfc_write_to_buffer(dev->read_subdev, data);
+ devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG);
+ }
+ if (i == timeout) {
+ comedi_error(dev, "ai timeout, fifo never empties");
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ return -1;
+ }
+
+ return 0;
+}
+
+/* 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)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv->current_transfer == isa_dma_transfer)
+ labpc_drain_dma(dev);
+
+ labpc_drain_fifo(dev);
+}
+
+/* interrupt service routine */
+static irqreturn_t labpc_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async;
+ struct comedi_cmd *cmd;
+
+ if (!dev->attached) {
+ comedi_error(dev, "premature interrupt");
+ return IRQ_HANDLED;
+ }
+
+ async = s->async;
+ cmd = &async->cmd;
+
+ /* read board status */
+ devpriv->stat1 = devpriv->read_byte(dev->iobase + STAT1_REG);
+ if (board->is_labpc1200)
+ devpriv->stat2 = devpriv->read_byte(dev->iobase + STAT2_REG);
+
+ if ((devpriv->stat1 & (STAT1_GATA0 | STAT1_CNTINT | STAT1_OVERFLOW |
+ STAT1_OVERRUN | STAT1_DAVAIL)) == 0
+ && (devpriv->stat2 & STAT2_OUTA1) == 0
+ && (devpriv->stat2 & STAT2_FIFONHF)) {
+ return IRQ_NONE;
+ }
+
+ if (devpriv->stat1 & STAT1_OVERRUN) {
+ /* clear error interrupt */
+ devpriv->write_byte(0x1, dev->iobase + ADC_FIFO_CLEAR_REG);
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+ comedi_error(dev, "overrun");
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->current_transfer == isa_dma_transfer)
+ labpc_handle_dma_status(dev);
+ else
+ labpc_drain_fifo(dev);
+
+ if (devpriv->stat1 & STAT1_CNTINT) {
+ comedi_error(dev, "handled timer interrupt?");
+ /* clear it */
+ devpriv->write_byte(0x1, dev->iobase + TIMER_CLEAR_REG);
+ }
+
+ if (devpriv->stat1 & STAT1_OVERFLOW) {
+ /* clear error interrupt */
+ devpriv->write_byte(0x1, dev->iobase + ADC_FIFO_CLEAR_REG);
+ async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(dev, s);
+ comedi_error(dev, "overflow");
+ return IRQ_HANDLED;
+ }
+ /* handle external stop trigger */
+ if (cmd->stop_src == TRIG_EXT) {
+ if (devpriv->stat2 & STAT2_OUTA1) {
+ labpc_drain_dregs(dev);
+ async->events |= COMEDI_CB_EOA;
+ }
+ }
+
+ /* TRIG_COUNT end of acquisition */
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (devpriv->count == 0)
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int labpc_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+ 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->cmd2 &= ~CMD2_LDAC(channel);
+ devpriv->write_byte(devpriv->cmd2, dev->iobase + CMD2_REG);
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+
+ /* set range */
+ if (board->is_labpc1200) {
+ range = CR_RANGE(insn->chanspec);
+ if (comedi_range_is_unipolar(s, range))
+ devpriv->cmd6 |= CMD6_DACUNI(channel);
+ else
+ devpriv->cmd6 &= ~CMD6_DACUNI(channel);
+ /* write to register */
+ devpriv->write_byte(devpriv->cmd6, dev->iobase + CMD6_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;
+}
+
+static int labpc_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+static int labpc_8255_mmio(int dir, int port, int data, unsigned long iobase)
+{
+ if (dir) {
+ writeb(data, (void __iomem *)(iobase + port));
+ return 0;
+ } else {
+ return readb((void __iomem *)(iobase + port));
+ }
+}
+
+/* lowlevel write to eeprom/dac */
+static void labpc_serial_out(struct comedi_device *dev, unsigned int value,
+ unsigned int value_width)
+{
+ struct labpc_private *devpriv = dev->private;
+ int i;
+
+ for (i = 1; i <= value_width; i++) {
+ /* clear serial clock */
+ devpriv->cmd5 &= ~CMD5_SCLK;
+ /* send bits most significant bit first */
+ if (value & (1 << (value_width - i)))
+ devpriv->cmd5 |= CMD5_SDATA;
+ else
+ devpriv->cmd5 &= ~CMD5_SDATA;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ /* set clock to load bit */
+ devpriv->cmd5 |= CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ }
+}
+
+/* lowlevel read from eeprom */
+static unsigned int labpc_serial_in(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ 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->cmd5 |= CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ /* clear clock bit */
+ devpriv->cmd5 &= ~CMD5_SCLK;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ /* read bits most significant bit first */
+ udelay(1);
+ devpriv->stat2 = devpriv->read_byte(dev->iobase + STAT2_REG);
+ if (devpriv->stat2 & STAT2_PROMOUT)
+ value |= 1 << (value_width - i);
+ }
+
+ return value;
+}
+
+static unsigned int labpc_eeprom_read(struct comedi_device *dev,
+ unsigned int address)
+{
+ struct labpc_private *devpriv = dev->private;
+ 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->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_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->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ return value;
+}
+
+static unsigned int labpc_eeprom_read_status(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ 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->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_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->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ return value;
+}
+
+static int labpc_eeprom_write(struct comedi_device *dev,
+ unsigned int address, unsigned int value)
+{
+ struct labpc_private *devpriv = dev->private;
+ 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->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ devpriv->cmd5 |= (CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ /* send write_enable instruction */
+ labpc_serial_out(dev, write_enable_instruction, write_length);
+ devpriv->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ /* send write instruction */
+ devpriv->cmd5 |= CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_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->cmd5 &= ~CMD5_EEPROMCS;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ /* disable read/write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+
+ return 0;
+}
+
+/* writes to 8 bit calibration dacs */
+static void write_caldac(struct comedi_device *dev, unsigned int channel,
+ unsigned int value)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (value == devpriv->caldac[channel])
+ return;
+ devpriv->caldac[channel] = value;
+
+ /* clear caldac load bit and make sure we don't write to eeprom */
+ devpriv->cmd5 &= ~(CMD5_CALDACLD | CMD5_EEPROMCS | CMD5_WRTPRT);
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_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->cmd5 |= CMD5_CALDACLD;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ devpriv->cmd5 &= ~CMD5_CALDACLD;
+ udelay(1);
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+}
+
+static int labpc_calib_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * Only write the last data value to the caldac. Preceding
+ * data would be overwritten anyway.
+ */
+ if (insn->n > 0)
+ write_caldac(dev, chan, data[insn->n - 1]);
+
+ return insn->n;
+}
+
+static int labpc_calib_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->caldac[chan];
+
+ return insn->n;
+}
+
+static int labpc_eeprom_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+
+ /* only allow writes to user area of eeprom */
+ if (chan < 16 || chan > 127)
+ return -EINVAL;
+
+ /*
+ * Only write the last data value to the eeprom. Preceding
+ * data would be overwritten anyway.
+ */
+ if (insn->n > 0) {
+ ret = labpc_eeprom_write(dev, chan, data[insn->n - 1]);
+ if (ret)
+ return ret;
+ }
+
+ return insn->n;
+}
+
+static int labpc_eeprom_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct labpc_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->eeprom_data[chan];
+
+ return insn->n;
+}
+
+int labpc_common_attach(struct comedi_device *dev,
+ unsigned int irq, unsigned long isr_flags)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ if (board->has_mmio) {
+ 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->cmd1, dev->iobase + CMD1_REG);
+ devpriv->write_byte(devpriv->cmd2, dev->iobase + CMD2_REG);
+ devpriv->write_byte(devpriv->cmd3, dev->iobase + CMD3_REG);
+ devpriv->write_byte(devpriv->cmd4, dev->iobase + CMD4_REG);
+ if (board->is_labpc1200) {
+ devpriv->write_byte(devpriv->cmd5, dev->iobase + CMD5_REG);
+ devpriv->write_byte(devpriv->cmd6, dev->iobase + CMD6_REG);
+ }
+
+ if (irq) {
+ ret = request_irq(irq, labpc_interrupt, isr_flags,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
+ s->n_chan = 8;
+ s->len_chanlist = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = board->is_labpc1200
+ ? &range_labpc_1200_ai : &range_labpc_plus_ai;
+ s->insn_read = labpc_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = labpc_ai_cmd;
+ s->do_cmdtest = labpc_ai_cmdtest;
+ s->cancel = labpc_cancel;
+ }
+
+ /* analog output */
+ s = &dev->subdevices[1];
+ if (board->has_ao) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = NUM_AO_CHAN;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_labpc_ao;
+ s->insn_read = labpc_ao_insn_read;
+ s->insn_write = labpc_ao_insn_write;
+
+ /* initialize analog outputs to a known value */
+ for (i = 0; i < s->n_chan; i++) {
+ short lsb, msb;
+
+ 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];
+ ret = subdev_8255_init(dev, s,
+ (board->has_mmio) ? labpc_8255_mmio : NULL,
+ dev->iobase + DIO_BASE_REG);
+ if (ret)
+ return ret;
+
+ /* calibration subdevices for boards that have one */
+ s = &dev->subdevices[3];
+ if (board->is_labpc1200) {
+ 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_insn_read;
+ s->insn_write = labpc_calib_insn_write;
+
+ 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 (board->is_labpc1200) {
+ 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_insn_read;
+ s->insn_write = labpc_eeprom_insn_write;
+
+ for (i = 0; i < s->n_chan; i++)
+ devpriv->eeprom_data[i] = labpc_eeprom_read(dev, i);
+ } else
+ s->type = COMEDI_SUBD_UNUSED;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(labpc_common_attach);
+
+#if IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISA)
+static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct labpc_private *devpriv;
+ unsigned int irq = it->options[1];
+ unsigned int dma_chan = it->options[2];
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], LABPC_SIZE);
+ if (ret)
+ return ret;
+
+ ret = labpc_common_attach(dev, irq, 0);
+ if (ret)
+ return ret;
+
+ if (dev->irq)
+ labpc_init_dma_chan(dev, dma_chan);
+
+ return 0;
+}
+
+static void labpc_detach(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv)
+ labpc_free_dma_chan(dev);
+
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver labpc_driver = {
+ .driver_name = "ni_labpc",
+ .module = THIS_MODULE,
+ .attach = labpc_attach,
+ .detach = labpc_detach,
+ .num_names = ARRAY_SIZE(labpc_boards),
+ .board_name = &labpc_boards[0].name,
+ .offset = sizeof(struct labpc_boardinfo),
+};
+module_comedi_driver(labpc_driver);
+#else
+static int __init labpc_common_init(void)
+{
+ return 0;
+}
+module_init(labpc_common_init);
+
+static void __exit labpc_common_exit(void)
+{
+}
+module_exit(labpc_common_exit);
+#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 00000000000..486589fa6fd
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc.h
@@ -0,0 +1,90 @@
+/*
+ ni_labpc.h
+
+ Header for ni_labpc.c and ni_labpc_cs.c
+
+ Copyright (C) 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#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 transfer_type { fifo_not_empty_transfer, fifo_half_full_transfer,
+ isa_dma_transfer
+};
+
+struct labpc_boardinfo {
+ const char *name;
+ int ai_speed; /* maximum input speed in ns */
+ unsigned ai_scan_up:1; /* can auto scan up in ai channels */
+ unsigned has_ao:1; /* has analog outputs */
+ unsigned is_labpc1200:1; /* has extra regs compared to pc+ */
+ unsigned has_mmio:1; /* uses memory mapped io */
+};
+
+struct labpc_private {
+ struct mite_struct *mite; /* for mite chip on pci-1200 */
+ /* number of data points left to be taken */
+ 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 */
+ unsigned int cmd1;
+ unsigned int cmd2;
+ unsigned int cmd3;
+ unsigned int cmd4;
+ unsigned int cmd5;
+ unsigned int cmd6;
+ /* store last read of board status registers */
+ unsigned int stat1;
+ unsigned int stat2;
+ /*
+ * 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 */
+ phys_addr_t dma_addr;
+ /* 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 int irq, unsigned long isr_flags);
+
+#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 00000000000..0a8b3223f74
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_cs.c
@@ -0,0 +1,135 @@
+/*
+ comedi/drivers/ni_labpc_cs.c
+ Driver for National Instruments daqcard-1200 boards
+ Copyright (C) 2001, 2002, 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ 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
+ <dahinds@users.sourceforge.net>. 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.
+*/
+/*
+Driver: ni_labpc_cs
+Description: National Instruments Lab-PC (& compatibles)
+Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+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)
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#include "8253.h"
+#include "8255.h"
+#include "comedi_fc.h"
+#include "ni_labpc.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+static const struct labpc_boardinfo labpc_cs_boards[] = {
+ {
+ .name = "daqcard-1200",
+ .ai_speed = 10000,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ },
+};
+
+static int labpc_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct labpc_private *devpriv;
+ int ret;
+
+ /* The ni_labpc driver needs the board_ptr */
+ dev->board_ptr = &labpc_cs_boards[0];
+
+ link->config_flags |= CONF_AUTO_SET_IO |
+ CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ if (!link->irq)
+ return -EINVAL;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ return labpc_common_attach(dev, link->irq, IRQF_SHARED);
+}
+
+static struct comedi_driver driver_labpc_cs = {
+ .driver_name = "ni_labpc_cs",
+ .module = THIS_MODULE,
+ .auto_attach = labpc_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int labpc_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_labpc_cs);
+}
+
+static const struct pcmcia_device_id labpc_cs_ids[] = {
+ PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0103), /* daqcard-1200 */
+ PCMCIA_DEVICE_NULL
+};
+MODULE_DEVICE_TABLE(pcmcia, labpc_cs_ids);
+
+static struct pcmcia_driver labpc_cs_driver = {
+ .name = "daqcard-1200",
+ .owner = THIS_MODULE,
+ .id_table = labpc_cs_ids,
+ .probe = labpc_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_labpc_cs, labpc_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for National Instruments Lab-PC");
+MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_isadma.c b/drivers/staging/comedi/drivers/ni_labpc_isadma.c
new file mode 100644
index 00000000000..d9f25fdbb72
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.c
@@ -0,0 +1,227 @@
+/*
+ * comedi/drivers/ni_labpc_isadma.c
+ * ISA DMA support for National Instruments Lab-PC series boards and
+ * compatibles.
+ *
+ * Extracted from ni_labpc.c:
+ * Copyright (C) 2001-2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute 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 <linux/module.h>
+#include <linux/slab.h>
+#include "../comedidev.h"
+
+#include <asm/dma.h>
+
+#include "comedi_fc.h"
+#include "ni_labpc.h"
+#include "ni_labpc_regs.h"
+#include "ni_labpc_isadma.h"
+
+/* size in bytes of dma buffer */
+static const int dma_buffer_size = 0xff00;
+/* 2 bytes per sample */
+static const int sample_size = 2;
+
+/* utility function that suggests a dma transfer size in bytes */
+static unsigned int labpc_suggest_transfer_size(const struct comedi_cmd *cmd)
+{
+ unsigned int size;
+ unsigned int freq;
+
+ if (cmd->convert_src == TRIG_TIMER)
+ freq = 1000000000 / cmd->convert_arg;
+ else
+ /* return some default value */
+ 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;
+}
+
+void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long irq_flags;
+
+ 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, devpriv->dma_addr);
+ /* 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);
+ /* set CMD3 bits for caller to enable DMA and interrupt */
+ devpriv->cmd3 |= (CMD3_DMAEN | CMD3_DMATCINTEN);
+}
+EXPORT_SYMBOL_GPL(labpc_setup_dma);
+
+void labpc_drain_dma(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int status;
+ unsigned long flags;
+ unsigned int max_points, num_points, residue, leftover;
+ int i;
+
+ status = devpriv->stat1;
+
+ 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 (cmd->stop_src == TRIG_COUNT && devpriv->count < num_points)
+ num_points = devpriv->count;
+
+ /* figure out how many points will be stored next time */
+ leftover = 0;
+ if (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 (cmd->stop_src == TRIG_COUNT)
+ devpriv->count -= num_points;
+
+ /* set address and count for next transfer */
+ set_dma_addr(devpriv->dma_chan, devpriv->dma_addr);
+ set_dma_count(devpriv->dma_chan, leftover * sample_size);
+ release_dma_lock(flags);
+
+ async->events |= COMEDI_CB_BLOCK;
+}
+EXPORT_SYMBOL_GPL(labpc_drain_dma);
+
+static void handle_isa_dma(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ labpc_drain_dma(dev);
+
+ enable_dma(devpriv->dma_chan);
+
+ /* clear dma tc interrupt */
+ devpriv->write_byte(0x1, dev->iobase + DMATC_CLEAR_REG);
+}
+
+void labpc_handle_dma_status(struct comedi_device *dev)
+{
+ const struct labpc_boardinfo *board = comedi_board(dev);
+ struct labpc_private *devpriv = dev->private;
+
+ /*
+ * if a dma terminal count of external stop trigger
+ * has occurred
+ */
+ if (devpriv->stat1 & STAT1_GATA0 ||
+ (board->is_labpc1200 && devpriv->stat2 & STAT2_OUTA1))
+ handle_isa_dma(dev);
+}
+EXPORT_SYMBOL_GPL(labpc_handle_dma_status);
+
+int labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan)
+{
+ struct labpc_private *devpriv = dev->private;
+ void *dma_buffer;
+ unsigned long dma_flags;
+ int ret;
+
+ if (dma_chan != 1 && dma_chan != 3)
+ return -EINVAL;
+
+ dma_buffer = kmalloc(dma_buffer_size, GFP_KERNEL | GFP_DMA);
+ if (!dma_buffer)
+ return -ENOMEM;
+
+ ret = request_dma(dma_chan, dev->board_name);
+ if (ret) {
+ kfree(dma_buffer);
+ return ret;
+ }
+
+ devpriv->dma_buffer = dma_buffer;
+ devpriv->dma_chan = dma_chan;
+ devpriv->dma_addr = virt_to_bus(devpriv->dma_buffer);
+
+ dma_flags = claim_dma_lock();
+ disable_dma(devpriv->dma_chan);
+ set_dma_mode(devpriv->dma_chan, DMA_MODE_READ);
+ release_dma_lock(dma_flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(labpc_init_dma_chan);
+
+void labpc_free_dma_chan(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ kfree(devpriv->dma_buffer);
+ devpriv->dma_buffer = NULL;
+ if (devpriv->dma_chan) {
+ free_dma(devpriv->dma_chan);
+ devpriv->dma_chan = 0;
+ }
+}
+EXPORT_SYMBOL_GPL(labpc_free_dma_chan);
+
+static int __init ni_labpc_isadma_init_module(void)
+{
+ return 0;
+}
+module_init(ni_labpc_isadma_init_module);
+
+static void __exit ni_labpc_isadma_cleanup_module(void)
+{
+}
+module_exit(ni_labpc_isadma_cleanup_module);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi NI Lab-PC ISA DMA support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_isadma.h b/drivers/staging/comedi/drivers/ni_labpc_isadma.h
new file mode 100644
index 00000000000..771af4bd5a7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_isadma.h
@@ -0,0 +1,57 @@
+/*
+ * ni_labpc ISA DMA support.
+*/
+
+#ifndef _NI_LABPC_ISADMA_H
+#define _NI_LABPC_ISADMA_H
+
+#define NI_LABPC_HAVE_ISA_DMA IS_ENABLED(CONFIG_COMEDI_NI_LABPC_ISADMA)
+
+#if NI_LABPC_HAVE_ISA_DMA
+
+static inline bool labpc_have_dma_chan(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ return (bool)devpriv->dma_chan;
+}
+
+int labpc_init_dma_chan(struct comedi_device *dev, unsigned int dma_chan);
+void labpc_free_dma_chan(struct comedi_device *dev);
+void labpc_setup_dma(struct comedi_device *dev, struct comedi_subdevice *s);
+void labpc_drain_dma(struct comedi_device *dev);
+void labpc_handle_dma_status(struct comedi_device *dev);
+
+#else
+
+static inline bool labpc_have_dma_chan(struct comedi_device *dev)
+{
+ return false;
+}
+
+static inline int labpc_init_dma_chan(struct comedi_device *dev,
+ unsigned int dma_chan)
+{
+ return -ENOTSUPP;
+}
+
+static inline void labpc_free_dma_chan(struct comedi_device *dev)
+{
+}
+
+static inline void labpc_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+}
+
+static inline void labpc_drain_dma(struct comedi_device *dev)
+{
+}
+
+static inline void labpc_handle_dma_status(struct comedi_device *dev)
+{
+}
+
+#endif
+
+#endif /* _NI_LABPC_ISADMA_H */
diff --git a/drivers/staging/comedi/drivers/ni_labpc_pci.c b/drivers/staging/comedi/drivers/ni_labpc_pci.c
new file mode 100644
index 00000000000..73959706829
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_pci.c
@@ -0,0 +1,133 @@
+/*
+ * comedi/drivers/ni_labpc_pci.c
+ * Driver for National Instruments Lab-PC PCI-1200
+ * Copyright (C) 2001, 2002, 2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: ni_labpc_pci
+ * Description: National Instruments Lab-PC PCI-1200
+ * Devices: (National Instruments) PCI-1200 [ni_pci-1200]
+ * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
+ * Status: works
+ *
+ * This is the PCI-specific support split off from the ni_labpc driver.
+ *
+ * Configuration Options: not applicable, uses PCI auto config
+ *
+ * NI manuals:
+ * 340914a (pci-1200)
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "mite.h"
+#include "ni_labpc.h"
+
+enum labpc_pci_boardid {
+ BOARD_NI_PCI1200,
+};
+
+static const struct labpc_boardinfo labpc_pci_boards[] = {
+ [BOARD_NI_PCI1200] = {
+ .name = "ni_pci-1200",
+ .ai_speed = 10000,
+ .ai_scan_up = 1,
+ .has_ao = 1,
+ .is_labpc1200 = 1,
+ .has_mmio = 1,
+ },
+};
+
+static int labpc_pci_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct labpc_boardinfo *board = NULL;
+ struct labpc_private *devpriv;
+ int ret;
+
+ if (context < ARRAY_SIZE(labpc_pci_boards))
+ board = &labpc_pci_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+ ret = mite_setup(devpriv->mite);
+ if (ret < 0)
+ return ret;
+ dev->iobase = (unsigned long)devpriv->mite->daq_io_addr;
+
+ return labpc_common_attach(dev, mite_irq(devpriv->mite), IRQF_SHARED);
+}
+
+static void labpc_pci_detach(struct comedi_device *dev)
+{
+ struct labpc_private *devpriv = dev->private;
+
+ if (devpriv && devpriv->mite) {
+ mite_unsetup(devpriv->mite);
+ mite_free(devpriv->mite);
+ }
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver labpc_pci_comedi_driver = {
+ .driver_name = "labpc_pci",
+ .module = THIS_MODULE,
+ .auto_attach = labpc_pci_auto_attach,
+ .detach = labpc_pci_detach,
+};
+
+static const struct pci_device_id labpc_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x161), BOARD_NI_PCI1200 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, labpc_pci_table);
+
+static int labpc_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &labpc_pci_comedi_driver,
+ id->driver_data);
+}
+
+static struct pci_driver labpc_pci_driver = {
+ .name = "labpc_pci",
+ .id_table = labpc_pci_table,
+ .probe = labpc_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(labpc_pci_comedi_driver, labpc_pci_driver);
+
+MODULE_DESCRIPTION("Comedi: National Instruments Lab-PC PCI-1200 driver");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_labpc_regs.h b/drivers/staging/comedi/drivers/ni_labpc_regs.h
new file mode 100644
index 00000000000..2a274a3e4e7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_labpc_regs.h
@@ -0,0 +1,75 @@
+/*
+ * ni_labpc register definitions.
+*/
+
+#ifndef _NI_LABPC_REGS_H
+#define _NI_LABPC_REGS_H
+
+/*
+ * Register map (all registers are 8-bit)
+ */
+#define STAT1_REG 0x00 /* R: Status 1 reg */
+#define STAT1_DAVAIL (1 << 0)
+#define STAT1_OVERRUN (1 << 1)
+#define STAT1_OVERFLOW (1 << 2)
+#define STAT1_CNTINT (1 << 3)
+#define STAT1_GATA0 (1 << 5)
+#define STAT1_EXTGATA0 (1 << 6)
+#define CMD1_REG 0x00 /* W: Command 1 reg */
+#define CMD1_MA(x) (((x) & 0x7) << 0)
+#define CMD1_TWOSCMP (1 << 3)
+#define CMD1_GAIN(x) (((x) & 0x7) << 4)
+#define CMD1_SCANEN (1 << 7)
+#define CMD2_REG 0x01 /* W: Command 2 reg */
+#define CMD2_PRETRIG (1 << 0)
+#define CMD2_HWTRIG (1 << 1)
+#define CMD2_SWTRIG (1 << 2)
+#define CMD2_TBSEL (1 << 3)
+#define CMD2_2SDAC0 (1 << 4)
+#define CMD2_2SDAC1 (1 << 5)
+#define CMD2_LDAC(x) (1 << (6 + (x)))
+#define CMD3_REG 0x02 /* W: Command 3 reg */
+#define CMD3_DMAEN (1 << 0)
+#define CMD3_DIOINTEN (1 << 1)
+#define CMD3_DMATCINTEN (1 << 2)
+#define CMD3_CNTINTEN (1 << 3)
+#define CMD3_ERRINTEN (1 << 4)
+#define CMD3_FIFOINTEN (1 << 5)
+#define ADC_START_CONVERT_REG 0x03 /* W: Start Convert reg */
+#define DAC_LSB_REG(x) (0x04 + 2 * (x)) /* W: DAC0/1 LSB reg */
+#define DAC_MSB_REG(x) (0x05 + 2 * (x)) /* W: DAC0/1 MSB reg */
+#define ADC_FIFO_CLEAR_REG 0x08 /* W: A/D FIFO Clear reg */
+#define ADC_FIFO_REG 0x0a /* R: A/D FIFO reg */
+#define DMATC_CLEAR_REG 0x0a /* W: DMA Interrupt Clear reg */
+#define TIMER_CLEAR_REG 0x0c /* W: Timer Interrupt Clear reg */
+#define CMD6_REG 0x0e /* W: Command 6 reg */
+#define CMD6_NRSE (1 << 0)
+#define CMD6_ADCUNI (1 << 1)
+#define CMD6_DACUNI(x) (1 << (2 + (x)))
+#define CMD6_HFINTEN (1 << 5)
+#define CMD6_DQINTEN (1 << 6)
+#define CMD6_SCANUP (1 << 7)
+#define CMD4_REG 0x0f /* W: Command 3 reg */
+#define CMD4_INTSCAN (1 << 0)
+#define CMD4_EOIRCV (1 << 1)
+#define CMD4_ECLKDRV (1 << 2)
+#define CMD4_SEDIFF (1 << 3)
+#define CMD4_ECLKRCV (1 << 4)
+#define DIO_BASE_REG 0x10 /* R/W: 8255 DIO base reg */
+#define COUNTER_A_BASE_REG 0x14 /* R/W: 8253 Counter A base reg */
+#define COUNTER_B_BASE_REG 0x18 /* R/W: 8253 Counter B base reg */
+#define CMD5_REG 0x1c /* W: Command 5 reg */
+#define CMD5_WRTPRT (1 << 2)
+#define CMD5_DITHEREN (1 << 3)
+#define CMD5_CALDACLD (1 << 4)
+#define CMD5_SCLK (1 << 5)
+#define CMD5_SDATA (1 << 6)
+#define CMD5_EEPROMCS (1 << 7)
+#define STAT2_REG 0x1d /* R: Status 2 reg */
+#define STAT2_PROMOUT (1 << 0)
+#define STAT2_OUTA1 (1 << 1)
+#define STAT2_FIFONHF (1 << 2)
+#define INTERVAL_COUNT_REG 0x1e /* W: Interval Counter Data reg */
+#define INTERVAL_STROBE_REG 0x1f /* W: Interval Counter Strobe reg */
+
+#endif /* _NI_LABPC_REGS_H */
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 00000000000..7ffdcc07ef9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_mio_common.c
@@ -0,0 +1,5740 @@
+/*
+ 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 <ds@schleef.org>
+ Copyright (C) 2002-2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+ 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
+ <trux@truxton.com>
+
+ 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.
+*/
+
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include "8255.h"
+#include "mite.h"
+#include "comedi_fc.h"
+
+/* 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, {
+ 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(20),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2),
+ UNI_RANGE(1),
+ UNI_RANGE(0.5),
+ UNI_RANGE(0.2),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_limited = {
+ 8, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.1),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(1),
+ UNI_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_limited14 = {
+ 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)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_bipolar4 = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.05)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ai_611x = {
+ 8, {
+ BIP_RANGE(50),
+ BIP_RANGE(20),
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_ai_622x = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(1),
+ BIP_RANGE(0.2)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_ai_628x = {
+ 7, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ BIP_RANGE(0.5),
+ BIP_RANGE(0.2),
+ BIP_RANGE(0.1)
+ }
+};
+
+static const struct comedi_lrange range_ni_E_ao_ext = {
+ 4, {
+ BIP_RANGE(10),
+ UNI_RANGE(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_bipolar5
+};
+
+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);
+
+#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_8255_callback(int dir, int port, int data, unsigned long arg);
+
+#ifdef PCIDMA
+static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s);
+static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s);
+#endif
+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);
+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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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
+ struct ni_private *devpriv = dev->private;
+ 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
+ struct ni_private *devpriv = dev->private;
+ 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
+ struct ni_private *devpriv = dev->private;
+ 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 */
+}
+
+#ifdef PCIDMA
+static void ni_release_gpct_mite_channel(struct comedi_device *dev,
+ unsigned gpct_index)
+{
+ struct ni_private *devpriv = dev->private;
+ 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
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ if (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ static const int timeout = 10000;
+ int i;
+
+ if (board->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 */
+ /* Wait for complete */
+ for (i = 0; i < timeout; i++) {
+ if (!(ni_readl(AIFIFO_Status_6143) & 0x10))
+ break;
+ udelay(1);
+ }
+ if (i == timeout) {
+ comedi_error(dev, "FIFO flush timeout.");
+ }
+ } else {
+ devpriv->stc_writew(dev, 1, ADC_FIFO_Clear);
+ if (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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;
+ struct ni_private *devpriv = dev->private;
+ 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)
+ 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 ni_private *devpriv = dev->private;
+ 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);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static void mite_handle_b_linkc(struct mite_struct *mite,
+ struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ 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);
+ spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags);
+}
+
+static int ni_ao_wait_for_dma_load(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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 handle_gpct_interrupt(struct comedi_device *dev,
+ unsigned short counter_index)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+
+ s = &dev->subdevices[NI_GPCT_SUBDEV(counter_index)];
+
+ ni_tio_handle_interrupt(&devpriv->counter_dev->counters[counter_index],
+ s);
+ cfc_handle_events(dev, s);
+#endif
+}
+
+static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status)
+{
+ struct ni_private *devpriv = dev->private;
+ 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 ni_private *devpriv = dev->private;
+ 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 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);
+ 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_is_subdevice_running(s)) {
+ s->async->events |=
+ COMEDI_CB_ERROR | COMEDI_CB_EOA;
+ cfc_handle_events(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);
+
+ shutdown_ai_command(dev);
+
+ s->async->events |= COMEDI_CB_ERROR;
+ if (status & (AI_Overrun_St | AI_Overflow_St))
+ s->async->events |= COMEDI_CB_OVERFLOW;
+
+ cfc_handle_events(dev, s);
+ return;
+ }
+ if (status & AI_SC_TC_St) {
+ 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);
+
+ cfc_handle_events(dev, s);
+}
+
+static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status)
+{
+ struct ni_private *devpriv = dev->private;
+ 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 ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = &dev->subdevices[NI_AO_SUBDEV];
+ /* unsigned short ack=0; */
+
+#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);
+ 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)
+ 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
+
+ cfc_handle_events(dev, s);
+}
+
+#ifndef PCIDMA
+
+static void ni_ao_fifo_load(struct comedi_device *dev,
+ struct comedi_subdevice *s, int n)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int chan;
+ int i;
+ unsigned short d;
+ u32 packed_data;
+ int range;
+ int err = 1;
+
+ chan = async->cur_chan;
+ for (i = 0; i < n; i++) {
+ err &= comedi_buf_get(s, &d);
+ if (err == 0)
+ break;
+
+ range = CR_RANGE(cmd->chanlist[chan]);
+
+ if (board->reg_type & ni_reg_6xxx_mask) {
+ packed_data = d & 0xffff;
+ /* 6711 only has 16 bit wide ao fifo */
+ if (board->reg_type != ni_reg_6711) {
+ err &= comedi_buf_get(s, &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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ int n;
+
+ n = comedi_buf_read_n_available(s);
+ if (n == 0) {
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ return 0;
+ }
+
+ n /= sizeof(short);
+ if (n > board->ao_fifo_depth / 2)
+ n = board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ int n;
+
+ /* reset fifo */
+ devpriv->stc_writew(dev, 1, DAC_FIFO_Clear);
+ if (board->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);
+ if (n == 0)
+ return 0;
+
+ n /= sizeof(short);
+ if (n > board->ao_fifo_depth)
+ n = board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ int i;
+
+ if (board->reg_type == ni_reg_611x) {
+ unsigned 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 (board->reg_type == ni_reg_6143) {
+ unsigned 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV];
+ int n;
+
+ n = board->ai_fifo_depth / 2;
+
+ ni_ai_fifo_read(dev, s, n);
+}
+#endif
+
+#ifdef PCIDMA
+static int ni_ai_drain_dma(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV];
+ unsigned short data[2];
+ u32 dl;
+ unsigned short fifo_empty;
+ int i;
+
+ if (board->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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV];
+ unsigned short data;
+ u32 dl;
+
+ if (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ struct comedi_subdevice *s = &dev->subdevices[NI_AI_SUBDEV];
+ unsigned short data;
+ u32 dl;
+
+ if (board->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 ni_private *devpriv = dev->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int length = num_bytes / bytes_per_sample(s);
+ unsigned short *array = data;
+ unsigned int *larray = data;
+ unsigned int i;
+
+ 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 %= cmd->chanlist_len;
+ }
+}
+
+#ifdef PCIDMA
+
+static int ni_ai_setup_MITE_dma(struct comedi_device *dev)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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, 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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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, s->async->prealloc_bufsz);
+
+ spin_lock_irqsave(&devpriv->mite_channel_lock, flags);
+ if (devpriv->ao_mite_chan) {
+ if (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ 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 (board->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 (board->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 (board->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 (board->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;
+ int count;
+
+ /* lock to avoid race with interrupt handler */
+ 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;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ int i, n;
+ const unsigned int mask = (1 << board->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 (board->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 (board->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 (board->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;
+}
+
+static void ni_prime_channelgain_list(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 << (board->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[board->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[board->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(board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan, range, aref;
+ unsigned int i;
+ unsigned int hi, lo;
+ unsigned offset;
+ unsigned int dither;
+
+ if (board->reg_type & ni_reg_m_series_mask) {
+ ni_m_series_load_channelgain_list(dev, n_chan, list);
+ return;
+ }
+ if (n_chan == 1 && (board->reg_type != ni_reg_611x)
+ && (board->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 (board->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 << (board->adbits - 1);
+ for (i = 0; i < n_chan; i++) {
+ if ((board->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[board->gainlkup][range];
+ if (board->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 (board->reg_type == ni_reg_611x)
+ ni_writew(CR_CHAN(list[i]) & 0x0003,
+ Calibration_Channel_Select_611x);
+ } else {
+ if (board->reg_type == ni_reg_611x)
+ aref = AREF_DIFF;
+ else if (board->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 (board->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 ((board->reg_type != ni_reg_611x)
+ && (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ return devpriv->clock_ns * (timer + 1);
+}
+
+static unsigned ni_min_ai_scan_period_ns(struct comedi_device *dev,
+ unsigned num_channels)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ switch (board->reg_type) {
+ case ni_reg_611x:
+ case ni_reg_6143:
+ /* simultaneously-sampled inputs */
+ return board->ai_speed;
+ break;
+ default:
+ /* multiplexed inputs */
+ break;
+ }
+ return board->ai_speed * num_channels;
+}
+
+static int ni_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+ unsigned int sources;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ if ((cmd->flags & CMDF_WRITE))
+ cmd->flags &= ~CMDF_WRITE;
+
+ err |= cfc_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+
+ sources = TRIG_TIMER | TRIG_EXT;
+ if (board->reg_type == ni_reg_611x ||
+ board->reg_type == ni_reg_6143)
+ sources |= TRIG_NOW;
+ err |= cfc_check_trigger_src(&cmd->convert_src, sources);
+
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ tmp = CR_CHAN(cmd->start_arg);
+
+ if (tmp > 16)
+ tmp = 16;
+ tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE));
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, tmp);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ ni_min_ai_scan_period_ns(dev, cmd->chanlist_len));
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ devpriv->clock_ns * 0xffffff);
+ } 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));
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);
+ } else { /* TRIG_OTHER */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if ((board->reg_type == ni_reg_611x)
+ || (board->reg_type == ni_reg_6143)) {
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ } else {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_speed);
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg,
+ devpriv->clock_ns * 0xffff);
+ }
+ } 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));
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, tmp);
+ } else if (cmd->convert_src == TRIG_NOW) {
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ unsigned int max_count = 0x01000000;
+
+ if (board->reg_type == ni_reg_611x)
+ max_count -= num_adc_stages_611x;
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, max_count);
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ 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 ((board->reg_type != ni_reg_611x)
+ && (board->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 ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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;
+
+ 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 || (board->reg_type == ni_reg_611x)
+ || (board->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 (board->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);
+ } else {
+ /* interrupt on nothing */
+ ni_set_bits(dev, Interrupt_A_Enable_Register, ~0, 0);
+
+ /* XXX start polling if necessary */
+ }
+
+ /* 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;
+ }
+#endif
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* AI_START1_Pulse */
+ devpriv->stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2,
+ AI_Command_2_Register);
+ s->async->inttrig = NULL;
+ } else if (cmd->start_src == TRIG_EXT) {
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = ni_ai_inttrig;
+ }
+
+ return 0;
+}
+
+static int ni_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct ni_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ 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 (board->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 (board->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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 (!board->has_analog_trig)
+ return -EINVAL;
+ if ((data[1] & 0xffff0000) != COMEDI_EV_SCAN_BEGIN) {
+ data[1] &= (COMEDI_EV_SCAN_BEGIN | 0xffff);
+ err++;
+ }
+ if (data[2] >= board->n_adchan) {
+ data[2] = board->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<b)
+ * hysteresis low mode 10 00 00 01
+ * hysteresis high mode 01 00 00 10
+ * middle mode 10 01 01 10
+ */
+
+ a = data[3];
+ b = data[4];
+ modebits = data[1] & 0xff;
+ if (modebits & 0xf0) {
+ /* two level mode */
+ if (b < a) {
+ /* swap order */
+ a = data[4];
+ b = data[3];
+ modebits =
+ ((data[1] & 0xf) << 4) | ((data[1] & 0xf0) >> 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ unsigned int length = num_bytes / sizeof(short);
+ unsigned int offset = 1 << (board->aobits - 1);
+ unsigned short *array = data;
+ unsigned int range;
+ unsigned int i;
+
+ for (i = 0; i < length; i++) {
+ range = CR_RANGE(cmd->chanlist[chan_index]);
+ if (board->ao_unipolar == 0 || (range & 1) == 0)
+ array[i] -= offset;
+#ifdef PCIDMA
+ array[i] = cpu_to_le16(array[i]);
+#endif
+ chan_index++;
+ chan_index %= 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ unsigned int range;
+ unsigned int chan;
+ unsigned int conf;
+ int i;
+ int invert = 0;
+
+ if (timed) {
+ for (i = 0; i < board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 (board->ao_unipolar) {
+ if ((range & 1) == 0) {
+ conf |= AO_Bipolar;
+ invert = (1 << (board->aobits - 1));
+ } else {
+ invert = 0;
+ }
+ if (range & 2)
+ conf |= AO_Ext_Ref;
+ } else {
+ conf |= AO_Bipolar;
+ invert = (1 << (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ if (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int invert;
+
+ ao_win_out(1 << chan, AO_Immediate_671x);
+ invert = 1 << (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ switch (data[0]) {
+ case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE:
+ switch (data[1]) {
+ case COMEDI_OUTPUT:
+ data[2] = 1 + board->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 trig_num)
+{
+ const struct ni_board_struct *board __maybe_unused = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+ int interrupt_b_bits;
+ int i;
+ static const int timeout = 1000;
+
+ if (trig_num != cmd->start_arg)
+ 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 (board->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 ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 (board->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 (board->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 (board->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 (board->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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ if ((cmd->flags & CMDF_WRITE) == 0)
+ cmd->flags |= CMDF_WRITE;
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ tmp = CR_CHAN(cmd->start_arg);
+
+ if (tmp > 18)
+ tmp = 18;
+ tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE));
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, tmp);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ board->ao_speed);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ devpriv->clock_ns * 0xffffff);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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;
+
+ return 0;
+}
+
+static int ni_ao_reset(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ /* 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 (board->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 (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ 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 insn->n;
+}
+
+static int ni_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ /* 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;
+
+ if (comedi_dio_update_state(s, data)) {
+ 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 insn->n;
+}
+
+static int ni_m_series_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ ni_writel(s->io_bits, M_Offset_DIO_Direction);
+
+ return insn->n;
+}
+
+static int ni_m_series_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_private *devpriv __maybe_unused = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ ni_writel(s->state, M_Offset_Static_Digital_Output);
+
+ data[1] = ni_readl(M_Offset_Static_Digital_Input);
+
+ return insn->n;
+}
+
+static int ni_cdio_check_chanlist(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < cmd->chanlist_len; ++i) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (chan != i)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ni_cdio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ int tmp;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ 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 |= -EINVAL;
+
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list if it exists */
+ if (cmd->chanlist && cmd->chanlist_len > 0)
+ err |= ni_cdio_check_chanlist(dev, s, cmd);
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ 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 trig_num)
+{
+#ifdef PCIDMA
+ struct ni_private *devpriv = dev->private;
+ unsigned long flags;
+#endif
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int retval = 0;
+ unsigned i;
+ const unsigned timeout = 1000;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ s->async->inttrig = NULL;
+
+ /* read alloc the entire buffer */
+ comedi_buf_read_alloc(s, 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)
+{
+ struct ni_private *devpriv __maybe_unused = dev->private;
+
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ unsigned cdio_status;
+ struct comedi_subdevice *s = &dev->subdevices[NI_DIO_SUBDEV];
+#ifdef PCIDMA
+ unsigned long flags;
+#endif
+
+ if ((board->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);
+ }
+ 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; */
+ }
+ cfc_handle_events(dev, s);
+}
+
+static int ni_serial_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ 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:
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned int status1;
+ int err = 0, count = 20;
+
+ 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);
+
+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)
+{
+ struct ni_private *devpriv = dev->private;
+ unsigned char mask, input = 0;
+
+ /* 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;
+ }
+ }
+
+ if (data_in)
+ *data_in = input;
+
+ return 0;
+}
+
+static void mio_common_detach(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->counter_dev)
+ ni_gpct_device_destroy(devpriv->counter_dev);
+ }
+}
+
+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_AUTO_INC:
+ stc_register = G_Autoincrement_Register(0);
+ break;
+ case NITIO_G1_AUTO_INC:
+ stc_register = G_Autoincrement_Register(1);
+ break;
+ case NITIO_G0_CMD:
+ stc_register = G_Command_Register(0);
+ break;
+ case NITIO_G1_CMD:
+ stc_register = G_Command_Register(1);
+ break;
+ case NITIO_G0_HW_SAVE:
+ stc_register = G_HW_Save_Register(0);
+ break;
+ case NITIO_G1_HW_SAVE:
+ stc_register = G_HW_Save_Register(1);
+ break;
+ case NITIO_G0_SW_SAVE:
+ stc_register = G_Save_Register(0);
+ break;
+ case NITIO_G1_SW_SAVE:
+ stc_register = G_Save_Register(1);
+ break;
+ case NITIO_G0_MODE:
+ stc_register = G_Mode_Register(0);
+ break;
+ case NITIO_G1_MODE:
+ stc_register = G_Mode_Register(1);
+ break;
+ case NITIO_G0_LOADA:
+ stc_register = G_Load_A_Register(0);
+ break;
+ case NITIO_G1_LOADA:
+ stc_register = G_Load_A_Register(1);
+ break;
+ case NITIO_G0_LOADB:
+ stc_register = G_Load_B_Register(0);
+ break;
+ case NITIO_G1_LOADB:
+ stc_register = G_Load_B_Register(1);
+ break;
+ case NITIO_G0_INPUT_SEL:
+ stc_register = G_Input_Select_Register(0);
+ break;
+ case NITIO_G1_INPUT_SEL:
+ stc_register = G_Input_Select_Register(1);
+ break;
+ case NITIO_G01_STATUS:
+ stc_register = G_Status_Register;
+ break;
+ case NITIO_G01_RESET:
+ stc_register = Joint_Reset_Register;
+ break;
+ case NITIO_G01_STATUS1:
+ stc_register = Joint_Status_1_Register;
+ break;
+ case NITIO_G01_STATUS2:
+ stc_register = Joint_Status_2_Register;
+ break;
+ case NITIO_G0_INT_ACK:
+ stc_register = Interrupt_A_Ack_Register;
+ break;
+ case NITIO_G1_INT_ACK:
+ stc_register = Interrupt_B_Ack_Register;
+ break;
+ case NITIO_G0_STATUS:
+ stc_register = AI_Status_1_Register;
+ break;
+ case NITIO_G1_STATUS:
+ stc_register = AO_Status_1_Register;
+ break;
+ case NITIO_G0_INT_ENA:
+ stc_register = Interrupt_A_Enable_Register;
+ break;
+ case NITIO_G1_INT_ENA:
+ 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;
+ struct ni_private *devpriv = dev->private;
+ 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_CNT_MODE:
+ ni_writew(bits, M_Offset_G0_Counting_Mode);
+ break;
+ case NITIO_G1_CNT_MODE:
+ ni_writew(bits, M_Offset_G1_Counting_Mode);
+ break;
+ case NITIO_G0_GATE2:
+ ni_writew(bits, M_Offset_G0_Second_Gate);
+ break;
+ case NITIO_G1_GATE2:
+ ni_writew(bits, M_Offset_G1_Second_Gate);
+ break;
+ case NITIO_G0_DMA_CFG:
+ ni_writew(bits, M_Offset_G0_DMA_Config);
+ break;
+ case NITIO_G1_DMA_CFG:
+ ni_writew(bits, M_Offset_G1_DMA_Config);
+ break;
+ case NITIO_G0_ABZ:
+ ni_writew(bits, M_Offset_G0_MSeries_ABZ);
+ break;
+ case NITIO_G1_ABZ:
+ ni_writew(bits, M_Offset_G1_MSeries_ABZ);
+ break;
+
+ /* 32 bit registers */
+ case NITIO_G0_LOADA:
+ case NITIO_G1_LOADA:
+ case NITIO_G0_LOADB:
+ case NITIO_G1_LOADB:
+ stc_register = ni_gpct_to_stc_register(reg);
+ devpriv->stc_writel(dev, bits, stc_register);
+ break;
+
+ /* 16 bit registers */
+ case NITIO_G0_INT_ENA:
+ 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_INT_ENA:
+ 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_RESET:
+ 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;
+ struct ni_private *devpriv = dev->private;
+ unsigned stc_register;
+
+ switch (reg) {
+ /* m-series only registers */
+ case NITIO_G0_DMA_STATUS:
+ return ni_readw(M_Offset_G0_DMA_Status);
+ case NITIO_G1_DMA_STATUS:
+ return ni_readw(M_Offset_G1_DMA_Status);
+
+ /* 32 bit registers */
+ case NITIO_G0_HW_SAVE:
+ case NITIO_G1_HW_SAVE:
+ case NITIO_G0_SW_SAVE:
+ case NITIO_G1_SW_SAVE:
+ stc_register = ni_gpct_to_stc_register(reg);
+ return devpriv->stc_readl(dev, stc_register);
+
+ /* 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ unsigned j;
+ enum ni_gpct_variant counter_variant;
+ int ret;
+
+ if (board->n_aochan > MAX_N_AO_CHAN) {
+ printk("bug! n_aochan > MAX_N_AO_CHAN\n");
+ return -EINVAL;
+ }
+
+ ret = comedi_alloc_subdevices(dev, NI_NUM_SUBDEVICES);
+ if (ret)
+ return ret;
+
+ /* analog input subdevice */
+
+ s = &dev->subdevices[NI_AI_SUBDEV];
+ dev->read_subdev = s;
+ if (board->n_adchan) {
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags =
+ SDF_READABLE | SDF_DIFF | SDF_DITHER | SDF_CMD_READ;
+ if (board->reg_type != ni_reg_611x)
+ s->subdev_flags |= SDF_GROUND | SDF_COMMON | SDF_OTHER;
+ if (board->adbits > 16)
+ s->subdev_flags |= SDF_LSAMPL;
+ if (board->reg_type & ni_reg_m_series_mask)
+ s->subdev_flags |= SDF_SOFT_CALIBRATED;
+ s->n_chan = board->n_adchan;
+ s->len_chanlist = 512;
+ s->maxdata = (1 << board->adbits) - 1;
+ s->range_table = ni_range_lkup[board->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 (board->n_aochan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_DEGLITCH | SDF_GROUND;
+ if (board->reg_type & ni_reg_m_series_mask)
+ s->subdev_flags |= SDF_SOFT_CALIBRATED;
+ s->n_chan = board->n_aochan;
+ s->maxdata = (1 << board->aobits) - 1;
+ s->range_table = board->ao_range_table;
+ s->insn_read = &ni_ao_insn_read;
+ if (board->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 (board->n_aochan) {
+ s->async_dma_dir = DMA_TO_DEVICE;
+#else
+ if (board->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 = board->n_aochan;
+ if ((board->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 ((board->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 = board->num_p0_dio_channels;
+ if (board->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 (board->has_8255) {
+ ret = subdev_8255_init(dev, s, ni_8255_callback,
+ (unsigned long)dev);
+ if (ret)
+ return ret;
+ } 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 (board->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 (board->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 (board->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 (board->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 (board->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 (board->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 = board->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 (board->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);
+ if (!devpriv->counter_dev)
+ return -ENOMEM;
+
+ /* 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;
+ s->n_chan = 3;
+ if (board->reg_type & ni_reg_m_series_mask)
+ s->maxdata = 0xffffffff;
+ else
+ s->maxdata = 0xffffff;
+ s->insn_read = ni_tio_insn_read;
+ s->insn_write = ni_tio_insn_read;
+ s->insn_config = ni_tio_insn_config;
+#ifdef PCIDMA
+ s->subdev_flags |= SDF_CMD_READ /* | SDF_CMD_WRITE */;
+ s->do_cmd = &ni_gpct_cmd;
+ s->len_chanlist = 1;
+ s->do_cmdtest = ni_tio_cmdtest;
+ s->cancel = &ni_gpct_cancel;
+ s->async_dma_dir = DMA_BIDIRECTIONAL;
+#endif
+ 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 */
+ s = &dev->subdevices[NI_AI_SUBDEV];
+ ni_ai_reset(dev, s);
+ if ((board->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 */
+ s = &dev->subdevices[NI_AO_SUBDEV];
+ ni_ao_reset(dev, s);
+
+ 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 (board->reg_type & ni_reg_6xxx_mask) {
+ ni_writeb(0, Magic_611x);
+ } else if (board->reg_type & ni_reg_m_series_mask) {
+ int channel;
+ for (channel = 0; channel < board->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);
+ }
+
+ return 0;
+}
+
+static int ni_8255_callback(int dir, int port, int data, unsigned long arg)
+{
+ struct comedi_device *dev = (struct comedi_device *)arg;
+ struct ni_private *devpriv __maybe_unused = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ data[0] = devpriv->eeprom_buffer[CR_CHAN(insn->chanspec)];
+
+ return 1;
+}
+
+static int ni_get_pwm_config(struct comedi_device *dev, unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ int i, j;
+ int n_dacs;
+ int n_chans = 0;
+ int n_bits;
+ int diffbits = 0;
+ int type;
+ int chan;
+
+ type = board->caldac[0];
+ if (type == caldac_none)
+ return;
+ n_bits = caldacs[type].n_bits;
+ for (i = 0; i < 3; i++) {
+ type = board->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 = board->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 = board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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 = board->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
+
+#ifdef PCIDMA
+static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ int retval;
+
+ 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);
+
+ return ni_tio_cmd(dev, s);
+}
+#endif
+
+#ifdef PCIDMA
+static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ 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;
+}
+#endif
+
+/*
+ *
+ * Programmable Function Inputs
+ *
+ */
+
+static int ni_m_series_set_pfi_routing(struct comedi_device *dev, unsigned chan,
+ unsigned source)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ if (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ if (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv __maybe_unused = dev->private;
+ unsigned bits;
+
+ if ((board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv __maybe_unused = dev->private;
+
+ if (!(board->reg_type & ni_reg_m_series_mask))
+ return -ENOTSUPP;
+
+ if (comedi_dio_update_state(s, data))
+ ni_writew(s->state, M_Offset_PFI_DO);
+
+ data[1] = ni_readw(M_Offset_PFI_DI);
+
+ return insn->n;
+}
+
+static int ni_pfi_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ /* 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 (board->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)
+{
+ data[1] = 0;
+
+ return insn->n;
+}
+
+/* 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ if (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ 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 (board->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 (board->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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+
+ 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 (board->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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+ 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,
+ (board->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,
+ (board->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,
+ (board->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()");
+}
+
+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
+ 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 00000000000..de421486b75
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_mio_cs.c
@@ -0,0 +1,310 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+
+#include "ni_stc.h"
+#include "8255.h"
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#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,
+ .gainlkup = ai_gain_8,
+ .ai_speed = 5000,
+ .num_p0_dio_channels = 8,
+ .caldac = { dac8800, dac8043 },
+ }, {
+ .device_id = 0x010c,
+ .name = "DAQCard-ai-16e-4",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 4000,
+ .num_p0_dio_channels = 8,
+ .caldac = { mb88341 }, /* verified */
+ }, {
+ .device_id = 0x02c4,
+ .name = "DAQCard-6062E",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_16,
+ .ai_speed = 2000,
+ .n_aochan = 2,
+ .aobits = 12,
+ .ao_fifo_depth = 2048,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1176,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug }, /* verified */
+ }, {
+ /* specs incorrect! */
+ .device_id = 0x075e,
+ .name = "DAQCard-6024E",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .aobits = 12,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ }, {
+ /* specs incorrect! */
+ .device_id = 0x0245,
+ .name = "DAQCard-6036E",
+ .n_adchan = 16,
+ .adbits = 16,
+ .ai_fifo_depth = 1024,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .aobits = 16,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 1000000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ },
+#if 0
+ {
+ .device_id = 0x0000, /* unknown */
+ .name = "DAQCard-6715",
+ .n_aochan = 8,
+ .aobits = 12,
+ .ao_671x = 8192,
+ .num_p0_dio_channels = 8,
+ .caldac = { mb88341, mb88341 },
+ },
+#endif
+};
+
+#define interrupt_pin(a) 0
+
+#define IRQ_POLARITY 1
+
+struct ni_private {
+
+ struct pcmcia_device *link;
+
+NI_PRIVATE_COMMON};
+
+/* 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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;
+}
+
+#include "ni_mio_common.c"
+
+static const void *ni_getboardtype(struct comedi_device *dev,
+ struct pcmcia_device *link)
+{
+ static const struct ni_board_struct *board;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ni_boards); i++) {
+ board = &ni_boards[i];
+ if (board->device_id == link->card_id)
+ return board;
+ }
+ return NULL;
+}
+
+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 int mio_cs_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ static const struct ni_board_struct *board;
+ struct ni_private *devpriv;
+ int ret;
+
+ board = ni_getboardtype(dev, link);
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, mio_pcmcia_config_loop);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+ ret = pcmcia_request_irq(link, ni_E_interrupt);
+ if (ret)
+ return ret;
+ dev->irq = link->irq;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+
+ devpriv = dev->private;
+ devpriv->stc_writew = mio_cs_win_out;
+ devpriv->stc_readw = mio_cs_win_in;
+ devpriv->stc_writel = win_out2;
+ devpriv->stc_readl = win_in2;
+
+ return ni_E_init(dev);
+}
+
+static void mio_cs_detach(struct comedi_device *dev)
+{
+ mio_common_detach(dev);
+ comedi_pcmcia_disable(dev);
+}
+
+static struct comedi_driver driver_ni_mio_cs = {
+ .driver_name = "ni_mio_cs",
+ .module = THIS_MODULE,
+ .auto_attach = mio_cs_auto_attach,
+ .detach = mio_cs_detach,
+};
+
+static int cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_ni_mio_cs);
+}
+
+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);
+
+static struct pcmcia_driver ni_mio_cs_driver = {
+ .name = "ni_mio_cs",
+ .owner = THIS_MODULE,
+ .id_table = ni_mio_cs_ids,
+ .probe = cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+};
+module_comedi_pcmcia_driver(driver_ni_mio_cs, ni_mio_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for National Instruments DAQCard E series");
+MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_pcidio.c b/drivers/staging/comedi/drivers/ni_pcidio.c
new file mode 100644
index 00000000000..5fc74d6ff6a
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_pcidio.c
@@ -0,0 +1,1088 @@
+/*
+ comedi/drivers/ni_pcidio.c
+ driver for National Instruments PCI-DIO-32HS
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1999,2002 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: ni_pcidio
+Description: National Instruments PCI-DIO32HS, PCI-6533
+Author: ds
+Status: works
+Devices: [National Instruments] PCI-DIO-32HS (ni_pcidio)
+ [National Instruments] PXI-6533, PCI-6533 (pxi-6533)
+ [National Instruments] PCI-6534 (pci-6534)
+Updated: Mon, 09 Jan 2012 14:27:23 +0000
+
+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
+*/
+
+#define USE_DMA
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "mite.h"
+
+#define PCI_DIO_SIZE 4096
+#define PCI_MITE_SIZE 4096
+
+/* 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
+
+/* Firmware files for PCI-6524 */
+#define FW_PCI_6534_MAIN "ni6534a.bin"
+#define FW_PCI_6534_SCARAB_DI "niscrb01.bin"
+#define FW_PCI_6534_SCARAB_DO "niscrb02.bin"
+MODULE_FIRMWARE(FW_PCI_6534_MAIN);
+MODULE_FIRMWARE(FW_PCI_6534_SCARAB_DI);
+MODULE_FIRMWARE(FW_PCI_6534_SCARAB_DO);
+
+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
+
+enum nidio_boardid {
+ BOARD_PCIDIO_32HS,
+ BOARD_PXI6533,
+ BOARD_PCI6534,
+};
+
+struct nidio_board {
+ const char *name;
+ unsigned int uses_firmware:1;
+};
+
+static const struct nidio_board nidio_boards[] = {
+ [BOARD_PCIDIO_32HS] = {
+ .name = "pci-dio-32hs",
+ },
+ [BOARD_PXI6533] = {
+ .name = "pxi-6533",
+ },
+ [BOARD_PCI6534] = {
+ .name = "pci-6534",
+ .uses_firmware = 1,
+ },
+};
+
+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;
+};
+
+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 ni_pcidio_ns_to_timer(int *nanosec, int round_mode);
+static int setup_mite_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s);
+
+static int ni_pcidio_request_di_mite_channel(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ 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)
+{
+ struct nidio96_private *devpriv = dev->private;
+ 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 ni_pcidio_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ 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);
+ 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 nidio96_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct mite_struct *mite = devpriv->mite;
+
+ /* int i, j; */
+ unsigned int auxdata = 0;
+ unsigned short data1 = 0;
+ unsigned short data2 = 0;
+ int flags;
+ int status;
+ int work = 0;
+ unsigned int m_status = 0;
+
+ /* interrupcions parasites */
+ if (!dev->attached) {
+ /* 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);
+
+ spin_lock(&devpriv->mite_channel_lock);
+ if (devpriv->di_mite_chan)
+ m_status = mite_get_status(devpriv->di_mite_chan);
+
+ 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);
+ /* XXX need to byteswap */
+ }
+ if (m_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_DRDY |
+ CHSR_DRQ1 | CHSR_MRDY)) {
+ dev_dbg(dev->class_dev,
+ "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) {
+ dev_dbg(dev->class_dev, "too much work in interrupt\n");
+ writeb(0x00,
+ devpriv->mite->daq_io_addr +
+ Master_DMA_And_Interrupt_Control);
+ break;
+ }
+
+ flags &= IntEn;
+
+ if (flags & TransferReady) {
+ while (flags & TransferReady) {
+ work++;
+ if (work > 100) {
+ dev_dbg(dev->class_dev,
+ "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(s, data1);
+ comedi_buf_put(s, data2);
+ flags = readb(devpriv->mite->daq_io_addr +
+ Group_1_Flags);
+ }
+ async->events |= COMEDI_CB_BLOCK;
+ }
+
+ if (flags & CountExpired) {
+ 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) {
+ writeb(ClearWaited,
+ devpriv->mite->daq_io_addr +
+ Group_1_First_Clear);
+ async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ break;
+ } else if (flags & PrimaryTC) {
+ writeb(ClearPrimaryTC,
+ devpriv->mite->daq_io_addr +
+ Group_1_First_Clear);
+ async->events |= COMEDI_CB_EOA;
+ } else if (flags & SecondaryTC) {
+ writeb(ClearSecondaryTC,
+ devpriv->mite->daq_io_addr +
+ Group_1_First_Clear);
+ async->events |= COMEDI_CB_EOA;
+ }
+
+ flags = readb(devpriv->mite->daq_io_addr + Group_1_Flags);
+ status = readb(devpriv->mite->daq_io_addr +
+ Interrupt_And_Window_Status);
+ }
+
+out:
+ cfc_handle_events(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;
+}
+
+static int ni_pcidio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct nidio96_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ writel(s->io_bits, devpriv->mite->daq_io_addr + Port_Pin_Directions(0));
+
+ return insn->n;
+}
+
+static int ni_pcidio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct nidio96_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ writel(s->state, devpriv->mite->daq_io_addr + Port_IO(0));
+
+ data[1] = readl(devpriv->mite->daq_io_addr + Port_IO(0));
+
+ return insn->n;
+}
+
+static int ni_pcidio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED (TIMER_BASE) /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ /* 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 |= -EINVAL;
+ }
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* no limit */
+ } else { /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ ni_pcidio_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ 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 nidio96_private *devpriv = dev->private;
+ 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;
+ }
+
+ return 0;
+}
+
+static int setup_mite_dma(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct nidio96_private *devpriv = dev->private;
+ 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, 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 trig_num)
+{
+ struct nidio96_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ 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)
+{
+ struct nidio96_private *devpriv = dev->private;
+
+ 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)
+{
+ struct nidio96_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->di_mite_ring, s);
+ 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,
+ const u8 *data, size_t data_len,
+ unsigned long context)
+{
+ struct nidio96_private *devpriv = dev->private;
+ static const int timeout = 1000;
+ int fpga_index = context;
+ int i;
+ size_t 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) {
+ dev_warn(dev->class_dev,
+ "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) {
+ dev_warn(dev->class_dev,
+ "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) {
+ dev_warn(dev->class_dev,
+ "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, NULL, 0, fpga_index);
+}
+
+static int pci_6534_reset_fpgas(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ 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)
+{
+ struct nidio96_private *devpriv = dev->private;
+
+ 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)
+{
+ struct nidio96_private *devpriv = dev->private;
+ static const char *const fw_file[3] = {
+ FW_PCI_6534_SCARAB_DI, /* loaded into scarab A for DI */
+ FW_PCI_6534_SCARAB_DO, /* loaded into scarab B for DO */
+ FW_PCI_6534_MAIN, /* loaded into main FPGA */
+ };
+ int ret;
+ int n;
+
+ ret = pci_6534_reset_fpgas(dev);
+ if (ret < 0)
+ return ret;
+ /* load main FPGA first, then the two scarabs */
+ for (n = 2; n >= 0; n--) {
+ ret = comedi_load_firmware(dev, &devpriv->mite->pcidev->dev,
+ fw_file[n],
+ pci_6534_load_fpga, n);
+ if (ret == 0 && n == 2)
+ pci_6534_init_main_fpga(dev);
+ if (ret < 0)
+ break;
+ }
+ return ret;
+}
+
+static void nidio_reset_board(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+ void __iomem *daq_mmio = devpriv->mite->daq_io_addr;
+
+ writel(0, daq_mmio + Port_IO(0));
+ writel(0, daq_mmio + Port_Pin_Directions(0));
+ writel(0, daq_mmio + Port_Pin_Mask(0));
+
+ /* disable interrupts on board */
+ writeb(0, daq_mmio + Master_DMA_And_Interrupt_Control);
+}
+
+static int nidio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct nidio_board *board = NULL;
+ struct nidio96_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ unsigned int irq;
+
+ if (context < ARRAY_SIZE(nidio_boards))
+ board = &nidio_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->mite_channel_lock);
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ ret = mite_setup(devpriv->mite);
+ if (ret < 0) {
+ dev_warn(dev->class_dev, "error setting up mite\n");
+ return ret;
+ }
+
+ devpriv->di_mite_ring = mite_alloc_ring(devpriv->mite);
+ if (devpriv->di_mite_ring == NULL)
+ return -ENOMEM;
+
+ if (board->uses_firmware) {
+ ret = pci_6534_upload_firmware(dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ nidio_reset_board(dev);
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ dev_info(dev->class_dev, "%s rev=%d\n", dev->board_name,
+ 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;
+
+ irq = mite_irq(devpriv->mite);
+ if (irq) {
+ ret = request_irq(irq, nidio_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ return 0;
+}
+
+static void nidio_detach(struct comedi_device *dev)
+{
+ struct nidio96_private *devpriv = dev->private;
+
+ 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);
+ mite_free(devpriv->mite);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver ni_pcidio_driver = {
+ .driver_name = "ni_pcidio",
+ .module = THIS_MODULE,
+ .auto_attach = nidio_auto_attach,
+ .detach = nidio_detach,
+};
+
+static int ni_pcidio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_pcidio_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_pcidio_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x1150), BOARD_PCIDIO_32HS },
+ { PCI_VDEVICE(NI, 0x12b0), BOARD_PCI6534 },
+ { PCI_VDEVICE(NI, 0x1320), BOARD_PXI6533 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_pcidio_pci_table);
+
+static struct pci_driver ni_pcidio_pci_driver = {
+ .name = "ni_pcidio",
+ .id_table = ni_pcidio_pci_table,
+ .probe = ni_pcidio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_pcidio_driver, ni_pcidio_pci_driver);
+
+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 00000000000..89300dc78e3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_pcimio.c
@@ -0,0 +1,1706 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include <linux/delay.h>
+
+#include "../comedidev.h"
+
+#include <asm/byteorder.h>
+
+#include "ni_stc.h"
+#include "mite.h"
+
+#define PCIDMA
+
+#define PCIMIO 1
+#undef ATMIO
+
+#define MAX_N_CALDACS (16+16+2)
+
+#define DRV_NAME "ni_pcimio"
+
+/* 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, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2),
+ BIP_RANGE(1),
+ RANGE(-5, 15),
+ UNI_RANGE(10),
+ RANGE(3, 7),
+ RANGE(4, 6),
+ RANGE_ext(-1, 1)
+ }
+};
+
+static const struct comedi_lrange range_ni_M_625x_ao = {
+ 3, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ RANGE_ext(-1, 1)
+ }
+};
+
+enum ni_pcimio_boardid {
+ BOARD_PCIMIO_16XE_50,
+ BOARD_PCIMIO_16XE_10,
+ BOARD_PCI6014,
+ BOARD_PXI6030E,
+ BOARD_PCIMIO_16E_1,
+ BOARD_PCIMIO_16E_4,
+ BOARD_PXI6040E,
+ BOARD_PCI6031E,
+ BOARD_PCI6032E,
+ BOARD_PCI6033E,
+ BOARD_PCI6071E,
+ BOARD_PCI6023E,
+ BOARD_PCI6024E,
+ BOARD_PCI6025E,
+ BOARD_PXI6025E,
+ BOARD_PCI6034E,
+ BOARD_PCI6035E,
+ BOARD_PCI6052E,
+ BOARD_PCI6110,
+ BOARD_PCI6111,
+ /* BOARD_PCI6115, */
+ /* BOARD_PXI6115, */
+ BOARD_PCI6711,
+ BOARD_PXI6711,
+ BOARD_PCI6713,
+ BOARD_PXI6713,
+ BOARD_PCI6731,
+ /* BOARD_PXI6731, */
+ BOARD_PCI6733,
+ BOARD_PXI6733,
+ BOARD_PXI6071E,
+ BOARD_PXI6070E,
+ BOARD_PXI6052E,
+ BOARD_PXI6031E,
+ BOARD_PCI6036E,
+ BOARD_PCI6220,
+ BOARD_PCI6221,
+ BOARD_PCI6221_37PIN,
+ BOARD_PCI6224,
+ BOARD_PXI6224,
+ BOARD_PCI6225,
+ BOARD_PXI6225,
+ BOARD_PCI6229,
+ BOARD_PCI6250,
+ BOARD_PCI6251,
+ BOARD_PCIE6251,
+ BOARD_PXIE6251,
+ BOARD_PCI6254,
+ BOARD_PCI6259,
+ BOARD_PCIE6259,
+ BOARD_PCI6280,
+ BOARD_PCI6281,
+ BOARD_PXI6281,
+ BOARD_PCI6284,
+ BOARD_PCI6289,
+ BOARD_PCI6143,
+ BOARD_PXI6143,
+};
+
+static const struct ni_board_struct ni_boards[] = {
+ [BOARD_PCIMIO_16XE_50] = {
+ .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_range_table = &range_bipolar10,
+ .ao_speed = 50000,
+ .num_p0_dio_channels = 8,
+ .caldac = { dac8800, dac8043 },
+ },
+ [BOARD_PCIMIO_16XE_10] = {
+ .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 },
+ },
+ [BOARD_PCI6014] = {
+ .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_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PXI6030E] = {
+ .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 },
+ },
+ [BOARD_PCIMIO_16E_1] = {
+ .name = "pci-mio-16e-1", /* aka pci-6070e */
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .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 },
+ },
+ [BOARD_PCIMIO_16E_4] = {
+ .name = "pci-mio-16e-4", /* aka pci-6040e */
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_16,
+ /*
+ * 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 */
+ },
+ [BOARD_PXI6040E] = {
+ .name = "pxi-6040e",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .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 },
+ },
+ [BOARD_PCI6031E] = {
+ .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 },
+ },
+ [BOARD_PCI6032E] = {
+ .name = "pci-6032e",
+ .n_adchan = 16,
+ .adbits = 16,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .num_p0_dio_channels = 8,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6033E] = {
+ .name = "pci-6033e",
+ .n_adchan = 64,
+ .adbits = 16,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_14,
+ .ai_speed = 10000,
+ .num_p0_dio_channels = 8,
+ .caldac = { dac8800, dac8043, ad8522 },
+ },
+ [BOARD_PCI6071E] = {
+ .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 },
+ },
+ [BOARD_PCI6023E] = {
+ .name = "pci-6023e",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ },
+ [BOARD_PCI6024E] = {
+ .name = "pci-6024e",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .aobits = 12,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ },
+ [BOARD_PCI6025E] = {
+ .name = "pci-6025e",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .aobits = 12,
+ .ao_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug }, /* manual is wrong */
+ .has_8255 = 1,
+ },
+ [BOARD_PXI6025E] = {
+ .name = "pxi-6025e",
+ .n_adchan = 16,
+ .adbits = 12,
+ .ai_fifo_depth = 512,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .n_aochan = 2,
+ .aobits = 12,
+ .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,
+ },
+ [BOARD_PCI6034E] = {
+ .name = "pci-6034e",
+ .n_adchan = 16,
+ .adbits = 16,
+ .ai_fifo_depth = 512,
+ .alwaysdither = 1,
+ .gainlkup = ai_gain_4,
+ .ai_speed = 5000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6035E] = {
+ .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_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6052E] = {
+ .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,
+ /* manual is wrong */
+ .caldac = { ad8804_debug, ad8804_debug, ad8522 },
+ },
+ [BOARD_PCI6110] = {
+ .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_fifo_depth = 2048,
+ .ao_speed = 250,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804, ad8804 },
+ },
+ [BOARD_PCI6111] = {
+ .name = "pci-6111",
+ .n_adchan = 2,
+ .adbits = 12,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 200,
+ .n_aochan = 2,
+ .aobits = 16,
+ .reg_type = ni_reg_611x,
+ .ao_range_table = &range_bipolar10,
+ .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 */
+ [BOARD_PCI6115] = { /* .device_id = 0x2ed0, */
+ .name = "pci-6115",
+ .n_adchan = 4,
+ .adbits = 12,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 100,
+ .n_aochan = 2,
+ .aobits = 16,
+ .ao_671x = 1,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .num_p0_dio_channels = 8,
+ .reg_611x = 1,
+ /* XXX */
+ .caldac = { ad8804_debug, ad8804_debug, ad8804_debug },
+ },
+#endif
+#if 0
+ [BOARD_PXI6115] = { /* .device_id = ????, */
+ .name = "pxi-6115",
+ .n_adchan = 4,
+ .adbits = 12,
+ .ai_fifo_depth = 8192,
+ .gainlkup = ai_gain_611x,
+ .ai_speed = 100,
+ .n_aochan = 2,
+ .aobits = 16,
+ .ao_671x = 1,
+ .ao_fifo_depth = 2048,
+ .ao_speed = 250,
+ .reg_611x = 1,
+ .num_p0_dio_channels = 8,
+ /* XXX */
+ .caldac = { ad8804_debug, ad8804_debug, ad8804_debug },
+ },
+#endif
+ [BOARD_PCI6711] = {
+ .name = "pci-6711",
+ .n_aochan = 4,
+ .aobits = 12,
+ /* data sheet says 8192, but fifo really holds 16384 samples */
+ .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 },
+ },
+ [BOARD_PXI6711] = {
+ .name = "pxi-6711",
+ .n_aochan = 4,
+ .aobits = 12,
+ .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 },
+ },
+ [BOARD_PCI6713] = {
+ .name = "pci-6713",
+ .n_aochan = 8,
+ .aobits = 12,
+ .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 },
+ },
+ [BOARD_PXI6713] = {
+ .name = "pxi-6713",
+ .n_aochan = 8,
+ .aobits = 12,
+ .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 },
+ },
+ [BOARD_PCI6731] = {
+ .name = "pci-6731",
+ .n_aochan = 4,
+ .aobits = 16,
+ .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
+ [BOARD_PXI6731] = { /* .device_id = ????, */
+ .name = "pxi-6731",
+ .n_aochan = 4,
+ .aobits = 16,
+ .ao_fifo_depth = 8192,
+ .ao_range_table = &range_bipolar10,
+ .num_p0_dio_channels = 8,
+ .reg_type = ni_reg_6711,
+ .caldac = { ad8804_debug },
+ },
+#endif
+ [BOARD_PCI6733] = {
+ .name = "pci-6733",
+ .n_aochan = 8,
+ .aobits = 16,
+ .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 },
+ },
+ [BOARD_PXI6733] = {
+ .name = "pxi-6733",
+ .n_aochan = 8,
+ .aobits = 16,
+ .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 },
+ },
+ [BOARD_PXI6071E] = {
+ .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 },
+ },
+ [BOARD_PXI6070E] = {
+ .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 },
+ },
+ [BOARD_PXI6052E] = {
+ .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 },
+ },
+ [BOARD_PXI6031E] = {
+ .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 },
+ },
+ [BOARD_PCI6036E] = {
+ .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_range_table = &range_bipolar10,
+ .ao_speed = 100000,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug },
+ },
+ [BOARD_PCI6220] = {
+ .name = "pci-6220",
+ .n_adchan = 16,
+ .adbits = 16,
+ .ai_fifo_depth = 512, /* FIXME: guess */
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .num_p0_dio_channels = 8,
+ .reg_type = ni_reg_622x,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6221] = {
+ .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_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6221_37PIN] = {
+ .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_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6224] = {
+ .name = "pci-6224",
+ .n_adchan = 32,
+ .adbits = 16,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_622x,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6224] = {
+ .name = "pxi-6224",
+ .n_adchan = 32,
+ .adbits = 16,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_622x,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_622x,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6225] = {
+ .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_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6225] = {
+ .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_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6229] = {
+ .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_bipolar10,
+ .reg_type = ni_reg_622x,
+ .ao_speed = 1200,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6250] = {
+ .name = "pci-6250",
+ .n_adchan = 16,
+ .adbits = 16,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .reg_type = ni_reg_625x,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6251] = {
+ .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_speed = 350,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCIE6251] = {
+ .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_speed = 350,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXIE6251] = {
+ .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_speed = 350,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6254] = {
+ .name = "pci-6254",
+ .n_adchan = 32,
+ .adbits = 16,
+ .ai_fifo_depth = 4095,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 800,
+ .reg_type = ni_reg_625x,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6259] = {
+ .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_speed = 350,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCIE6259] = {
+ .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_speed = 350,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6280] = {
+ .name = "pci-6280",
+ .n_adchan = 16,
+ .adbits = 18,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .ao_fifo_depth = 8191,
+ .reg_type = ni_reg_628x,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6281] = {
+ .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 = 350,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PXI6281] = {
+ .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 = 350,
+ .num_p0_dio_channels = 8,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6284] = {
+ .name = "pci-6284",
+ .n_adchan = 32,
+ .adbits = 18,
+ .ai_fifo_depth = 2047,
+ .gainlkup = ai_gain_628x,
+ .ai_speed = 1600,
+ .reg_type = ni_reg_628x,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6289] = {
+ .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 = 350,
+ .num_p0_dio_channels = 32,
+ .caldac = { caldac_none },
+ },
+ [BOARD_PCI6143] = {
+ .name = "pci-6143",
+ .n_adchan = 8,
+ .adbits = 16,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_6143,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_6143,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+ [BOARD_PXI6143] = {
+ .name = "pxi-6143",
+ .n_adchan = 8,
+ .adbits = 16,
+ .ai_fifo_depth = 1024,
+ .gainlkup = ai_gain_6143,
+ .ai_speed = 4000,
+ .reg_type = ni_reg_6143,
+ .num_p0_dio_channels = 8,
+ .caldac = { ad8804_debug, ad8804_debug },
+ },
+};
+
+struct ni_private {
+NI_PRIVATE_COMMON};
+
+/* 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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:
+ dev_dbg(dev->class_dev,
+ "%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:
+ dev_warn(dev->class_dev,
+ "%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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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:
+ dev_warn(dev->class_dev,
+ "%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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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:
+ dev_warn(dev->class_dev,
+ "%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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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:
+ dev_warn(dev->class_dev,
+ "%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_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)
+{
+ struct ni_private *devpriv = dev->private;
+ 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)
+{
+ const struct ni_board_struct *board = comedi_board(dev);
+ struct ni_private *devpriv = dev->private;
+
+ /* 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 */
+
+ /* Set the FIFO half full level */
+ ni_writel(board->ai_fifo_depth / 2, AIFIFO_Flag_6143);
+
+ /* 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);
+}
+
+static void pcimio_detach(struct comedi_device *dev)
+{
+ struct ni_private *devpriv = dev->private;
+
+ mio_common_detach(dev);
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv) {
+ 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);
+ mite_free(devpriv->mite);
+ }
+ }
+ comedi_pci_disable(dev);
+}
+
+static int pcimio_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct ni_board_struct *board = NULL;
+ struct ni_private *devpriv;
+ unsigned int irq;
+ int ret;
+
+ if (context < ARRAY_SIZE(ni_boards))
+ board = &ni_boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ ret = ni_alloc_private(dev);
+ if (ret)
+ return ret;
+ devpriv = dev->private;
+
+ devpriv->mite = mite_alloc(pcidev);
+ if (!devpriv->mite)
+ return -ENOMEM;
+
+ if (board->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;
+ }
+
+ 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 (board->reg_type & ni_reg_m_series_mask)
+ m_series_init_eeprom_buffer(dev);
+ if (board->reg_type == ni_reg_6143)
+ init_6143(dev);
+
+ irq = mite_irq(devpriv->mite);
+ if (irq) {
+ ret = request_irq(irq, ni_E_interrupt, NI_E_IRQ_FLAGS,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = irq;
+ }
+
+ ret = ni_E_init(dev);
+ 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 0;
+}
+
+static int pcimio_ai_change(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned long new_size)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->ai_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_ao_change(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned long new_size)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->ao_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_gpct0_change(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned long new_size)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->gpct_mite_ring[0], s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_gpct1_change(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned long new_size)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->gpct_mite_ring[1], s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int pcimio_dio_change(struct comedi_device *dev,
+ struct comedi_subdevice *s, unsigned long new_size)
+{
+ struct ni_private *devpriv = dev->private;
+ int ret;
+
+ ret = mite_buf_change(devpriv->cdo_mite_ring, s);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct comedi_driver ni_pcimio_driver = {
+ .driver_name = "ni_pcimio",
+ .module = THIS_MODULE,
+ .auto_attach = pcimio_auto_attach,
+ .detach = pcimio_detach,
+};
+
+static int ni_pcimio_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &ni_pcimio_driver, id->driver_data);
+}
+
+static const struct pci_device_id ni_pcimio_pci_table[] = {
+ { PCI_VDEVICE(NI, 0x0162), BOARD_PCIMIO_16XE_50 }, /* 0x1620? */
+ { PCI_VDEVICE(NI, 0x1170), BOARD_PCIMIO_16XE_10 },
+ { PCI_VDEVICE(NI, 0x1180), BOARD_PCIMIO_16E_1 },
+ { PCI_VDEVICE(NI, 0x1190), BOARD_PCIMIO_16E_4 },
+ { PCI_VDEVICE(NI, 0x11b0), BOARD_PXI6070E },
+ { PCI_VDEVICE(NI, 0x11c0), BOARD_PXI6040E },
+ { PCI_VDEVICE(NI, 0x11d0), BOARD_PXI6030E },
+ { PCI_VDEVICE(NI, 0x1270), BOARD_PCI6032E },
+ { PCI_VDEVICE(NI, 0x1330), BOARD_PCI6031E },
+ { PCI_VDEVICE(NI, 0x1340), BOARD_PCI6033E },
+ { PCI_VDEVICE(NI, 0x1350), BOARD_PCI6071E },
+ { PCI_VDEVICE(NI, 0x14e0), BOARD_PCI6110 },
+ { PCI_VDEVICE(NI, 0x14f0), BOARD_PCI6111 },
+ { PCI_VDEVICE(NI, 0x1580), BOARD_PXI6031E },
+ { PCI_VDEVICE(NI, 0x15b0), BOARD_PXI6071E },
+ { PCI_VDEVICE(NI, 0x1880), BOARD_PCI6711 },
+ { PCI_VDEVICE(NI, 0x1870), BOARD_PCI6713 },
+ { PCI_VDEVICE(NI, 0x18b0), BOARD_PCI6052E },
+ { PCI_VDEVICE(NI, 0x18c0), BOARD_PXI6052E },
+ { PCI_VDEVICE(NI, 0x2410), BOARD_PCI6733 },
+ { PCI_VDEVICE(NI, 0x2420), BOARD_PXI6733 },
+ { PCI_VDEVICE(NI, 0x2430), BOARD_PCI6731 },
+ { PCI_VDEVICE(NI, 0x2890), BOARD_PCI6036E },
+ { PCI_VDEVICE(NI, 0x28c0), BOARD_PCI6014 },
+ { PCI_VDEVICE(NI, 0x2a60), BOARD_PCI6023E },
+ { PCI_VDEVICE(NI, 0x2a70), BOARD_PCI6024E },
+ { PCI_VDEVICE(NI, 0x2a80), BOARD_PCI6025E },
+ { PCI_VDEVICE(NI, 0x2ab0), BOARD_PXI6025E },
+ { PCI_VDEVICE(NI, 0x2b80), BOARD_PXI6713 },
+ { PCI_VDEVICE(NI, 0x2b90), BOARD_PXI6711 },
+ { PCI_VDEVICE(NI, 0x2c80), BOARD_PCI6035E },
+ { PCI_VDEVICE(NI, 0x2ca0), BOARD_PCI6034E },
+ { PCI_VDEVICE(NI, 0x70aa), BOARD_PCI6229 },
+ { PCI_VDEVICE(NI, 0x70ab), BOARD_PCI6259 },
+ { PCI_VDEVICE(NI, 0x70ac), BOARD_PCI6289 },
+ { PCI_VDEVICE(NI, 0x70af), BOARD_PCI6221 },
+ { PCI_VDEVICE(NI, 0x70b0), BOARD_PCI6220 },
+ { PCI_VDEVICE(NI, 0x70b4), BOARD_PCI6250 },
+ { PCI_VDEVICE(NI, 0x70b6), BOARD_PCI6280 },
+ { PCI_VDEVICE(NI, 0x70b7), BOARD_PCI6254 },
+ { PCI_VDEVICE(NI, 0x70b8), BOARD_PCI6251 },
+ { PCI_VDEVICE(NI, 0x70bc), BOARD_PCI6284 },
+ { PCI_VDEVICE(NI, 0x70bd), BOARD_PCI6281 },
+ { PCI_VDEVICE(NI, 0x70bf), BOARD_PXI6281 },
+ { PCI_VDEVICE(NI, 0x70c0), BOARD_PCI6143 },
+ { PCI_VDEVICE(NI, 0x70f2), BOARD_PCI6224 },
+ { PCI_VDEVICE(NI, 0x70f3), BOARD_PXI6224 },
+ { PCI_VDEVICE(NI, 0x710d), BOARD_PXI6143 },
+ { PCI_VDEVICE(NI, 0x716c), BOARD_PCI6225 },
+ { PCI_VDEVICE(NI, 0x716d), BOARD_PXI6225 },
+ { PCI_VDEVICE(NI, 0x717f), BOARD_PCIE6259 },
+ { PCI_VDEVICE(NI, 0x71bc), BOARD_PCI6221_37PIN },
+ { PCI_VDEVICE(NI, 0x717d), BOARD_PCIE6251 },
+ { PCI_VDEVICE(NI, 0x72e8), BOARD_PXIE6251 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, ni_pcimio_pci_table);
+
+static struct pci_driver ni_pcimio_pci_driver = {
+ .name = "ni_pcimio",
+ .id_table = ni_pcimio_pci_table,
+ .probe = ni_pcimio_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(ni_pcimio_driver, ni_pcimio_pci_driver);
+
+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 00000000000..f0630b7897b
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_stc.h
@@ -0,0 +1,1507 @@
+/*
+ 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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+ 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 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; \
+ \
+ unsigned 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 00000000000..92691b491c2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio.c
@@ -0,0 +1,1698 @@
+/*
+ comedi/drivers/ni_tio.c
+ Support for NI general purpose counters
+
+ Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+Driver: ni_tio
+Description: National Instruments general purpose counters
+Devices:
+Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ Herman.Bruyninckx@mech.kuleuven.ac.be,
+ Wim.Meeussen@mech.kuleuven.ac.be,
+ Klaas.Gadeyne@mech.kuleuven.ac.be,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+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 <linux/module.h>
+#include <linux/slab.h>
+
+#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);
+
+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;
+
+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)
+{
+ unsigned cidx = counter->counter_index;
+
+ write_register(counter, Gi_Reset_Bit(cidx), NITIO_RESET_REG(cidx));
+}
+
+void ni_tio_init_counter(struct ni_gpct *counter)
+{
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+
+ ni_tio_reset_count_and_disarm(counter);
+
+ /* initialize counter registers */
+ counter_dev->regs[NITIO_AUTO_INC_REG(cidx)] = 0x0;
+ write_register(counter, counter_dev->regs[NITIO_AUTO_INC_REG(cidx)],
+ NITIO_AUTO_INC_REG(cidx));
+
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx),
+ ~0, Gi_Synchronize_Gate_Bit);
+
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx), ~0, 0);
+
+ counter_dev->regs[NITIO_LOADA_REG(cidx)] = 0x0;
+ write_register(counter, counter_dev->regs[NITIO_LOADA_REG(cidx)],
+ NITIO_LOADA_REG(cidx));
+
+ counter_dev->regs[NITIO_LOADB_REG(cidx)] = 0x0;
+ write_register(counter, counter_dev->regs[NITIO_LOADB_REG(cidx)],
+ NITIO_LOADB_REG(cidx));
+
+ ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), ~0, 0);
+
+ if (ni_tio_counting_mode_registers_present(counter_dev))
+ ni_tio_set_bits(counter, NITIO_CNT_MODE_REG(cidx), ~0, 0);
+
+ if (ni_tio_second_gate_registers_present(counter_dev)) {
+ counter_dev->regs[NITIO_GATE2_REG(cidx)] = 0x0;
+ write_register(counter,
+ counter_dev->regs[NITIO_GATE2_REG(cidx)],
+ NITIO_GATE2_REG(cidx));
+ }
+
+ ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), ~0, 0x0);
+
+ ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx), ~0, 0x0);
+}
+EXPORT_SYMBOL_GPL(ni_tio_init_counter);
+
+static unsigned int ni_tio_counter_status(struct ni_gpct *counter)
+{
+ unsigned cidx = counter->counter_index;
+ const unsigned bits = read_register(counter,
+ NITIO_SHARED_STATUS_REG(cidx));
+ unsigned int status = 0;
+
+ if (bits & Gi_Armed_Bit(cidx)) {
+ status |= COMEDI_COUNTER_ARMED;
+ if (bits & Gi_Counting_Bit(cidx))
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned counting_mode_reg = NITIO_CNT_MODE_REG(cidx);
+ 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 cidx = counter->counter_index;
+ 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_MODE_REG(cidx),
+ 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_CNT_MODE_REG(cidx),
+ 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_CMD_REG(cidx),
+ 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_INPUT_SEL_REG(cidx),
+ 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 cidx = counter->counter_index;
+ 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_CNT_MODE_REG(cidx),
+ 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_CMD_REG(cidx),
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+
+ 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 cidx = counter->counter_index;
+ 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_INPUT_SEL_REG(cidx),
+ 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_CNT_MODE_REG(cidx),
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned counting_mode_bits =
+ ni_tio_get_soft_copy(counter, NITIO_CNT_MODE_REG(cidx));
+ unsigned bits = 0;
+
+ if (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) &
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+ unsigned clock_source = 0;
+ unsigned i;
+ const unsigned input_select =
+ (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) &
+ 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 cidx = counter->counter_index;
+ const unsigned input_select =
+ (ni_tio_get_soft_copy(counter, NITIO_INPUT_SEL_REG(cidx)) &
+ Gi_Source_Select_Mask) >> Gi_Source_Select_Shift;
+ unsigned i;
+
+ 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 cidx = counter->counter_index;
+ 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_MODE_REG(cidx),
+ 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);
+ unsigned cidx = counter->counter_index;
+ /* 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_INPUT_SEL_REG(cidx),
+ 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);
+ unsigned cidx = counter->counter_index;
+ /* 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_INPUT_SEL_REG(cidx),
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+
+ switch (gate_index) {
+ case 0:
+ if (CR_CHAN(gate_source) == NI_GPCT_DISABLED_GATE_SELECT) {
+ ni_tio_set_bits(counter, NITIO_MODE_REG(cidx),
+ 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;
+ unsigned cidx = counter->counter_index;
+
+ if (counter_dev->variant == ni_gpct_variant_m_series) {
+ unsigned int abz_reg, shift, mask;
+
+ abz_reg = NITIO_ABZ_REG(cidx);
+ 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);
+ 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;
+ unsigned cidx = counter->counter_index;
+ const unsigned mode_bits =
+ ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx));
+ const unsigned second_gate_reg = NITIO_GATE2_REG(cidx);
+ 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_INPUT_SEL_REG(cidx)) &
+ 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 comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+
+ 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_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ const unsigned channel = CR_CHAN(insn->chanspec);
+ unsigned cidx = counter->counter_index;
+ 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_CMD_REG(cidx),
+ Gi_Save_Trace_Bit, 0);
+ ni_tio_set_bits(counter, NITIO_CMD_REG(cidx),
+ 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_SW_SAVE_REG(cidx));
+ second_read = read_register(counter, NITIO_SW_SAVE_REG(cidx));
+ if (first_read != second_read)
+ correct_read =
+ read_register(counter, NITIO_SW_SAVE_REG(cidx));
+ else
+ correct_read = first_read;
+ data[0] = correct_read;
+ return 0;
+ break;
+ case 1:
+ data[0] = counter_dev->regs[NITIO_LOADA_REG(cidx)];
+ break;
+ case 2:
+ data[0] = counter_dev->regs[NITIO_LOADB_REG(cidx)];
+ break;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_insn_read);
+
+static unsigned ni_tio_next_load_register(struct ni_gpct *counter)
+{
+ unsigned cidx = counter->counter_index;
+ const unsigned bits =
+ read_register(counter, NITIO_SHARED_STATUS_REG(cidx));
+
+ if (bits & Gi_Next_Load_Source_Bit(cidx))
+ return NITIO_LOADB_REG(cidx);
+ else
+ return NITIO_LOADA_REG(cidx);
+}
+
+int ni_tio_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ const unsigned channel = CR_CHAN(insn->chanspec);
+ unsigned cidx = counter->counter_index;
+ 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_CMD_REG(cidx),
+ 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_LOADA_REG(cidx)] = data[0];
+ write_register(counter, data[0], NITIO_LOADA_REG(cidx));
+ break;
+ case 2:
+ counter_dev->regs[NITIO_LOADB_REG(cidx)] = data[0];
+ write_register(counter, data[0], NITIO_LOADB_REG(cidx));
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ni_tio_insn_write);
+
+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);
+
+MODULE_AUTHOR("Comedi <comedi@comedi.org>");
+MODULE_DESCRIPTION("Comedi support for NI general-purpose counters");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/ni_tio.h b/drivers/staging/comedi/drivers/ni_tio.h
new file mode 100644
index 00000000000..1056bf001e5
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio.h
@@ -0,0 +1,156 @@
+/*
+ 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.
+*/
+
+#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_AUTO_INC,
+ NITIO_G1_AUTO_INC,
+ NITIO_G2_AUTO_INC,
+ NITIO_G3_AUTO_INC,
+ NITIO_G0_CMD,
+ NITIO_G1_CMD,
+ NITIO_G2_CMD,
+ NITIO_G3_CMD,
+ NITIO_G0_HW_SAVE,
+ NITIO_G1_HW_SAVE,
+ NITIO_G2_HW_SAVE,
+ NITIO_G3_HW_SAVE,
+ NITIO_G0_SW_SAVE,
+ NITIO_G1_SW_SAVE,
+ NITIO_G2_SW_SAVE,
+ NITIO_G3_SW_SAVE,
+ NITIO_G0_MODE,
+ NITIO_G1_MODE,
+ NITIO_G2_MODE,
+ NITIO_G3_MODE,
+ NITIO_G0_LOADA,
+ NITIO_G1_LOADA,
+ NITIO_G2_LOADA,
+ NITIO_G3_LOADA,
+ NITIO_G0_LOADB,
+ NITIO_G1_LOADB,
+ NITIO_G2_LOADB,
+ NITIO_G3_LOADB,
+ NITIO_G0_INPUT_SEL,
+ NITIO_G1_INPUT_SEL,
+ NITIO_G2_INPUT_SEL,
+ NITIO_G3_INPUT_SEL,
+ NITIO_G0_CNT_MODE,
+ NITIO_G1_CNT_MODE,
+ NITIO_G2_CNT_MODE,
+ NITIO_G3_CNT_MODE,
+ NITIO_G0_GATE2,
+ NITIO_G1_GATE2,
+ NITIO_G2_GATE2,
+ NITIO_G3_GATE2,
+ NITIO_G01_STATUS,
+ NITIO_G23_STATUS,
+ NITIO_G01_RESET,
+ NITIO_G23_RESET,
+ NITIO_G01_STATUS1,
+ NITIO_G23_STATUS1,
+ NITIO_G01_STATUS2,
+ NITIO_G23_STATUS2,
+ NITIO_G0_DMA_CFG,
+ NITIO_G1_DMA_CFG,
+ NITIO_G2_DMA_CFG,
+ NITIO_G3_DMA_CFG,
+ NITIO_G0_DMA_STATUS,
+ NITIO_G1_DMA_STATUS,
+ NITIO_G2_DMA_STATUS,
+ NITIO_G3_DMA_STATUS,
+ NITIO_G0_ABZ,
+ NITIO_G1_ABZ,
+ NITIO_G0_INT_ACK,
+ NITIO_G1_INT_ACK,
+ NITIO_G2_INT_ACK,
+ NITIO_G3_INT_ACK,
+ NITIO_G0_STATUS,
+ NITIO_G1_STATUS,
+ NITIO_G2_STATUS,
+ NITIO_G3_STATUS,
+ NITIO_G0_INT_ENA,
+ NITIO_G1_INT_ENA,
+ NITIO_G2_INT_ENA,
+ NITIO_G3_INT_ENA,
+ NITIO_NUM_REGS,
+};
+
+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_REGS];
+ spinlock_t regs_lock;
+};
+
+struct ni_gpct_device *
+ni_gpct_device_construct(struct comedi_device *,
+ void (*write_register)(struct ni_gpct *,
+ unsigned bits,
+ enum ni_gpct_register),
+ unsigned (*read_register)(struct ni_gpct *,
+ enum ni_gpct_register),
+ enum ni_gpct_variant,
+ unsigned num_counters);
+void ni_gpct_device_destroy(struct ni_gpct_device *);
+void ni_tio_init_counter(struct ni_gpct *);
+int ni_tio_insn_read(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_insn_config(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_insn_write(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_insn *, unsigned int *data);
+int ni_tio_cmd(struct comedi_device *, struct comedi_subdevice *);
+int ni_tio_cmdtest(struct comedi_device *, struct comedi_subdevice *,
+ struct comedi_cmd *);
+int ni_tio_cancel(struct ni_gpct *);
+void ni_tio_handle_interrupt(struct ni_gpct *, struct comedi_subdevice *);
+void ni_tio_set_mite_channel(struct ni_gpct *, struct mite_channel *);
+void ni_tio_acknowledge_and_confirm(struct ni_gpct *,
+ int *gate_error, int *tc_error,
+ int *perm_stale_data, int *stale_data);
+
+#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 00000000000..15b81b8fc5c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tio_internal.h
@@ -0,0 +1,404 @@
+/*
+ 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.
+*/
+
+#ifndef _COMEDI_NI_TIO_INTERNAL_H
+#define _COMEDI_NI_TIO_INTERNAL_H
+
+#include "ni_tio.h"
+
+#define NITIO_AUTO_INC_REG(x) (NITIO_G0_AUTO_INC + (x))
+#define NITIO_CMD_REG(x) (NITIO_G0_CMD + (x))
+#define NITIO_HW_SAVE_REG(x) (NITIO_G0_HW_SAVE + (x))
+#define NITIO_SW_SAVE_REG(x) (NITIO_G0_SW_SAVE + (x))
+#define NITIO_MODE_REG(x) (NITIO_G0_MODE + (x))
+#define NITIO_LOADA_REG(x) (NITIO_G0_LOADA + (x))
+#define NITIO_LOADB_REG(x) (NITIO_G0_LOADB + (x))
+#define NITIO_INPUT_SEL_REG(x) (NITIO_G0_INPUT_SEL + (x))
+#define NITIO_CNT_MODE_REG(x) (NITIO_G0_CNT_MODE + (x))
+#define NITIO_GATE2_REG(x) (NITIO_G0_GATE2 + (x))
+#define NITIO_SHARED_STATUS_REG(x) (NITIO_G01_STATUS + ((x) / 2))
+#define NITIO_RESET_REG(x) (NITIO_G01_RESET + ((x) / 2))
+#define NITIO_STATUS1_REG(x) (NITIO_G01_STATUS1 + ((x) / 2))
+#define NITIO_STATUS2_REG(x) (NITIO_G01_STATUS2 + ((x) / 2))
+#define NITIO_DMA_CFG_REG(x) (NITIO_G0_DMA_CFG + (x))
+#define NITIO_DMA_STATUS_REG(x) (NITIO_G0_DMA_STATUS + (x))
+#define NITIO_ABZ_REG(x) (NITIO_G0_ABZ + (x))
+#define NITIO_INT_ACK_REG(x) (NITIO_G0_INT_ACK + (x))
+#define NITIO_STATUS_REG(x) (NITIO_G0_STATUS + (x))
+#define NITIO_INT_ENA_REG(x) (NITIO_G0_INT_ENA + (x))
+
+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,
+ /* from m-series example code, not documented in 660x register level
+ * manual */
+ 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,
+ 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,
+ /* from m-series example code, not documented in 660x register level
+ * manual */
+ Gi_660x_Prescale_X2_Bit = 0x4000,
+ 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_REGS);
+ 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_REGS);
+ 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_REGS);
+ 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_REGS);
+ 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 00000000000..2557ab48cb6
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_tiocmd.c
@@ -0,0 +1,496 @@
+/*
+ comedi/drivers/ni_tiocmd.c
+ Command support for NI general purpose counters
+
+ Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+Driver: ni_tiocmd
+Description: National Instruments general purpose counters command support
+Devices:
+Author: J.P. Mellor <jpmellor@rose-hulman.edu>,
+ Herman.Bruyninckx@mech.kuleuven.ac.be,
+ Wim.Meeussen@mech.kuleuven.ac.be,
+ Klaas.Gadeyne@mech.kuleuven.ac.be,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+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 <linux/module.h>
+#include "comedi_fc.h"
+#include "ni_tio_internal.h"
+#include "mite.h"
+
+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 cidx = counter->counter_index;
+ 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_INPUT_SEL_REG(cidx),
+ 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_DMA_CFG_REG(cidx),
+ 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 trig_num)
+{
+ struct ni_gpct *counter = s->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+ int retval = 0;
+
+ BUG_ON(counter == NULL);
+
+ if (trig_num != cmd->start_src)
+ 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 comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ struct ni_gpct_device *counter_dev = counter->counter_dev;
+ unsigned cidx = counter->counter_index;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int retval = 0;
+
+ /* write alloc the entire buffer */
+ comedi_buf_write_alloc(s, 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_CMD_REG(cidx), 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);
+ break;
+ 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 comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+
+ dev_err(counter->counter_dev->dev->class_dev,
+ "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 comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ struct ni_gpct *counter = s->private;
+ unsigned cidx = counter->counter_index;
+ 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_INT_ENA_REG(cidx),
+ Gi_Gate_Interrupt_Enable_Bit(cidx),
+ Gi_Gate_Interrupt_Enable_Bit(cidx));
+ }
+ return retval;
+}
+
+int ni_tio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct ni_gpct *counter = s->private;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ int retval = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&counter->lock, flags);
+ if (counter->mite_chan == NULL) {
+ dev_err(counter->counter_dev->dev->class_dev,
+ "commands only supported with DMA. ");
+ dev_err(counter->counter_dev->dev->class_dev,
+ "Interrupt-driven commands not yet implemented.\n");
+ retval = -EIO;
+ } else {
+ retval = ni_tio_cmd_setup(s);
+ if (retval == 0) {
+ if (cmd->flags & CMDF_WRITE)
+ retval = ni_tio_output_cmd(s);
+ else
+ retval = ni_tio_input_cmd(s);
+ }
+ }
+ spin_unlock_irqrestore(&counter->lock, flags);
+ return retval;
+}
+EXPORT_SYMBOL_GPL(ni_tio_cmd);
+
+int ni_tio_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct ni_gpct *counter = s->private;
+ int err = 0;
+ unsigned int sources;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ sources = TRIG_NOW | TRIG_INT | TRIG_OTHER;
+ if (ni_tio_counting_mode_registers_present(counter->counter_dev))
+ sources |= TRIG_EXT;
+ err |= cfc_check_trigger_src(&cmd->start_src, sources);
+
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_NOW | TRIG_EXT | TRIG_OTHER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (cmd->convert_src != TRIG_NOW && cmd->scan_begin_src != TRIG_FOLLOW)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ case TRIG_OTHER:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ /* start_arg is the start_trigger passed to ni_tio_arm() */
+ break;
+ }
+
+ if (cmd->scan_begin_src != TRIG_EXT)
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src != TRIG_EXT)
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ 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 cidx = counter->counter_index;
+ 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_INT_ENA_REG(cidx),
+ Gi_Gate_Interrupt_Enable_Bit(cidx), 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)
+{
+ unsigned cidx = counter->counter_index;
+ const unsigned short gxx_status = read_register(counter,
+ NITIO_SHARED_STATUS_REG(cidx));
+ const unsigned short gi_status = read_register(counter,
+ NITIO_STATUS_REG(cidx));
+ 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(cidx)) {
+ ack |= Gi_Gate_Error_Confirm_Bit(cidx);
+ 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(cidx)) {
+ ack |= Gi_TC_Error_Confirm_Bit(cidx);
+ 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_INT_ACK_REG(cidx));
+ if (ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx)) &
+ Gi_Loading_On_Gate_Bit) {
+ if (gxx_status & Gi_Stale_Data_Bit(cidx)) {
+ if (stale_data)
+ *stale_data = 1;
+ }
+ if (read_register(counter, NITIO_STATUS2_REG(cidx)) &
+ Gi_Permanent_Stale_Bit(cidx)) {
+ dev_info(counter->counter_dev->dev->class_dev,
+ "%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 cidx = counter->counter_index;
+ 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) {
+ dev_notice(counter->counter_dev->dev->class_dev,
+ "%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_DMA_STATUS_REG(cidx)) &
+ Gi_DRQ_Error_Bit) {
+ dev_notice(counter->counter_dev->dev->class_dev,
+ "%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);
+ 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);
+
+MODULE_AUTHOR("Comedi <comedi@comedi.org>");
+MODULE_DESCRIPTION("Comedi command support for NI general-purpose counters");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl711.c b/drivers/staging/comedi/drivers/pcl711.c
new file mode 100644
index 00000000000..c38d97a9a89
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl711.c
@@ -0,0 +1,570 @@
+/*
+ * pcl711.c
+ * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ * Janne Jalkanen <jalkanen@cs.hut.fi>
+ * Eric Bunn <ebu@cs.hut.fi>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcl711
+ * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
+ * Devices: (Advantech) PCL-711 [pcl711]
+ * (Advantech) PCL-711B [pcl711b]
+ * (AdLink) ACL-8112HG [acl8112hg]
+ * (AdLink) ACL-8112DG [acl8112dg]
+ * Author: David A. Schleef <ds@schleef.org>
+ * Janne Jalkanen <jalkanen@cs.hut.fi>
+ * Eric Bunn <ebu@cs.hut.fi>
+ * Updated:
+ * Status: mostly complete
+ *
+ * Configuration Options:
+ * [0] - I/O port base
+ * [1] - IRQ, optional
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+/*
+ * I/O port register map
+ */
+#define PCL711_TIMER_BASE 0x00
+#define PCL711_AI_LSB_REG 0x04
+#define PCL711_AI_MSB_REG 0x05
+#define PCL711_AI_MSB_DRDY (1 << 4)
+#define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL711_DI_LSB_REG 0x06
+#define PCL711_DI_MSB_REG 0x07
+#define PCL711_INT_STAT_REG 0x08
+#define PCL711_INT_STAT_CLR (0 << 0) /* any value will work */
+#define PCL711_AI_GAIN_REG 0x09
+#define PCL711_AI_GAIN(x) (((x) & 0xf) << 0)
+#define PCL711_MUX_REG 0x0a
+#define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0)
+#define PCL711_MUX_CS0 (1 << 4)
+#define PCL711_MUX_CS1 (1 << 5)
+#define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1)
+#define PCL711_MODE_REG 0x0b
+#define PCL711_MODE_DEFAULT (0 << 0)
+#define PCL711_MODE_SOFTTRIG (1 << 0)
+#define PCL711_MODE_EXT (2 << 0)
+#define PCL711_MODE_EXT_IRQ (3 << 0)
+#define PCL711_MODE_PACER (4 << 0)
+#define PCL711_MODE_PACER_IRQ (6 << 0)
+#define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4)
+#define PCL711_SOFTTRIG_REG 0x0c
+#define PCL711_SOFTTRIG (0 << 0) /* any value will work */
+#define PCL711_DO_LSB_REG 0x0d
+#define PCL711_DO_MSB_REG 0x0e
+
+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)
+ }
+};
+
+struct pcl711_board {
+ const char *name;
+ int n_aichan;
+ int n_aochan;
+ int maxirq;
+ const struct comedi_lrange *ai_range_type;
+};
+
+static const struct pcl711_board boardtypes[] = {
+ {
+ .name = "pcl711",
+ .n_aichan = 8,
+ .n_aochan = 1,
+ .ai_range_type = &range_bipolar5,
+ }, {
+ .name = "pcl711b",
+ .n_aichan = 8,
+ .n_aochan = 1,
+ .maxirq = 7,
+ .ai_range_type = &range_pcl711b_ai,
+ }, {
+ .name = "acl8112hg",
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .maxirq = 15,
+ .ai_range_type = &range_acl8112hg_ai,
+ }, {
+ .name = "acl8112dg",
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .maxirq = 15,
+ .ai_range_type = &range_acl8112dg_ai,
+ },
+};
+
+struct pcl711_private {
+ unsigned int ntrig;
+ unsigned int ao_readback[2];
+ unsigned int divisor1;
+ unsigned int divisor2;
+};
+
+static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
+{
+ /*
+ * The pcl711b board uses bits in the mode register to select the
+ * interrupt. The other boards supported by this driver all use
+ * jumpers on the board.
+ *
+ * Enables the interrupt when needed on the pcl711b board. These
+ * bits do nothing on the other boards.
+ */
+ if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
+ mode |= PCL711_MODE_IRQ(dev->irq);
+
+ outb(mode, dev->iobase + PCL711_MODE_REG);
+}
+
+static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL711_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl711_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+ pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+ return 0;
+}
+
+static irqreturn_t pcl711_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcl711_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int data;
+
+ if (!dev->attached) {
+ comedi_error(dev, "spurious interrupt");
+ return IRQ_HANDLED;
+ }
+
+ data = pcl711_ai_get_sample(dev, s);
+
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+
+ if (comedi_buf_put(s, data) == 0) {
+ s->async->events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
+ } else {
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ if (cmd->stop_src == TRIG_COUNT && !(--devpriv->ntrig)) {
+ pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+ s->async->events |= COMEDI_CB_EOA;
+ }
+ }
+ comedi_event(dev, s);
+ return IRQ_HANDLED;
+}
+
+static void pcl711_set_changain(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chanspec)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int mux = 0;
+
+ outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
+
+ if (s->n_chan > 8) {
+ /* Select the correct MPC508A chip */
+ if (aref == AREF_DIFF) {
+ chan &= 0x7;
+ mux |= PCL711_MUX_DIFF;
+ } else {
+ if (chan < 8)
+ mux |= PCL711_MUX_CS0;
+ else
+ mux |= PCL711_MUX_CS1;
+ }
+ }
+ outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
+}
+
+static int pcl711_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL711_AI_MSB_REG);
+ if ((status & PCL711_AI_MSB_DRDY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcl711_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+ int i;
+
+ pcl711_set_changain(dev, s, insn->chanspec);
+
+ pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+
+ for (i = 0; i < insn->n; i++) {
+ outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = pcl711_ai_get_sample(dev, s);
+ }
+
+ return insn->n;
+}
+
+static int pcl711_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct pcl711_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_EXT) {
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ } else {
+#define MAX_SPEED 1000
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_NONE)
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4 */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static void pcl711_ai_load_counters(struct comedi_device *dev)
+{
+ struct pcl711_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE;
+
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+}
+
+static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl711_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ pcl711_set_changain(dev, s, cmd->chanlist[0]);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ if (cmd->stop_arg == 0) {
+ /* an empty acquisition */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return 0;
+ }
+ devpriv->ntrig = cmd->stop_arg;
+ }
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ pcl711_ai_load_counters(dev);
+ outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+ pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
+ } else {
+ pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
+ }
+
+ return 0;
+}
+
+static void pcl711_ao_write(struct comedi_device *dev,
+ unsigned int chan, unsigned int val)
+{
+ outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
+ outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
+}
+
+static int pcl711_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl711_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ pcl711_ao_write(dev, chan, val);
+ }
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcl711_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl711_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pcl711_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL711_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pcl711_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl711_board *board = comedi_board(dev);
+ struct pcl711_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ if (it->options[1] && it->options[1] <= board->maxirq) {
+ ret = request_irq(it->options[1], pcl711_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ if (board->n_aichan > 8)
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->n_aichan;
+ s->maxdata = 0xfff;
+ s->range_table = board->ai_range_type;
+ s->insn_read = pcl711_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = 1;
+ s->do_cmdtest = pcl711_ai_cmdtest;
+ s->do_cmd = pcl711_ai_cmd;
+ s->cancel = pcl711_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0xfff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = pcl711_ao_insn_write;
+ s->insn_read = pcl711_ao_insn_read;
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ 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 = pcl711_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ 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 = pcl711_do_insn_bits;
+
+ /* clear DAC */
+ pcl711_ao_write(dev, 0, 0x0);
+ pcl711_ao_write(dev, 1, 0x0);
+
+ return 0;
+}
+
+static struct comedi_driver pcl711_driver = {
+ .driver_name = "pcl711",
+ .module = THIS_MODULE,
+ .attach = pcl711_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl711_board),
+};
+module_comedi_driver(pcl711_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl724.c b/drivers/staging/comedi/drivers/pcl724.c
new file mode 100644
index 00000000000..8af13e790ad
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl724.c
@@ -0,0 +1,155 @@
+/*
+ * pcl724.c
+ * Comedi driver for 8255 based ISA and PC/104 DIO boards
+ *
+ * Michal Dobes <dobes@tesnet.cz>
+ */
+
+/*
+ * Driver: pcl724
+ * Description: Comedi driver for 8255 based ISA DIO boards
+ * Devices: (Advantech) PCL-724 [pcl724]
+ * (Advantech) PCL-722 [pcl722]
+ * (Advantech) PCL-731 [pcl731]
+ * (ADLink) ACL-7122 [acl7122]
+ * (ADLink) ACL-7124 [acl7124]
+ * (ADLink) PET-48DIO [pet48dio]
+ * (WinSystems) PCM-IO48 [pcmio48]
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ * Status: untested
+ *
+ * Configuration options:
+ * [0] - IO Base
+ * [1] - IRQ (not supported)
+ * [2] - number of DIO (pcl722 and acl7122 boards)
+ * 0, 144: 144 DIO configuration
+ * 1, 96: 96 DIO configuration
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#include "8255.h"
+
+#define SIZE_8255 4
+
+struct pcl724_board {
+ const char *name;
+ unsigned int io_range;
+ unsigned int can_have96:1;
+ unsigned int is_pet48:1;
+ int numofports;
+};
+
+static const struct pcl724_board boardtypes[] = {
+ {
+ .name = "pcl724",
+ .io_range = 0x04,
+ .numofports = 1, /* 24 DIO channels */
+ }, {
+ .name = "pcl722",
+ .io_range = 0x20,
+ .can_have96 = 1,
+ .numofports = 6, /* 144 (or 96) DIO channels */
+ }, {
+ .name = "pcl731",
+ .io_range = 0x08,
+ .numofports = 2, /* 48 DIO channels */
+ }, {
+ .name = "acl7122",
+ .io_range = 0x20,
+ .can_have96 = 1,
+ .numofports = 6, /* 144 (or 96) DIO channels */
+ }, {
+ .name = "acl7124",
+ .io_range = 0x04,
+ .numofports = 1, /* 24 DIO channels */
+ }, {
+ .name = "pet48dio",
+ .io_range = 0x02,
+ .is_pet48 = 1,
+ .numofports = 2, /* 48 DIO channels */
+ }, {
+ .name = "pcmio48",
+ .io_range = 0x08,
+ .numofports = 2, /* 48 DIO channels */
+ },
+};
+
+static int pcl724_8255mapped_io(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)
+{
+ const struct pcl724_board *board = comedi_board(dev);
+ struct comedi_subdevice *s;
+ unsigned long iobase;
+ unsigned int iorange;
+ int n_subdevices;
+ int ret;
+ int i;
+
+ iorange = board->io_range;
+ n_subdevices = board->numofports;
+
+ /* Handle PCL-724 in 96 DIO configuration */
+ if (board->can_have96 &&
+ (it->options[2] == 1 || it->options[2] == 96)) {
+ iorange = 0x10;
+ n_subdevices = 4;
+ }
+
+ ret = comedi_request_region(dev, it->options[0], iorange);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ if (board->is_pet48) {
+ iobase = dev->iobase + (i * 0x1000);
+ ret = subdev_8255_init(dev, s, pcl724_8255mapped_io,
+ iobase);
+ } else {
+ iobase = dev->iobase + (i * SIZE_8255);
+ ret = subdev_8255_init(dev, s, NULL, iobase);
+ }
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl724_driver = {
+ .driver_name = "pcl724",
+ .module = THIS_MODULE,
+ .attach = pcl724_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl724_board),
+};
+module_comedi_driver(pcl724_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for 8255 based ISA and PC/104 DIO boards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl726.c b/drivers/staging/comedi/drivers/pcl726.c
new file mode 100644
index 00000000000..74f6489bd12
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl726.c
@@ -0,0 +1,455 @@
+/*
+ * pcl726.c
+ * Comedi driver for 6/12-Channel D/A Output and DIO cards
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcl726
+ * Description: Advantech PCL-726 & compatibles
+ * Author: David A. Schleef <ds@schleef.org>
+ * Status: untested
+ * Devices: (Advantech) PCL-726 [pcl726]
+ * (Advantech) PCL-727 [pcl727]
+ * (Advantech) PCL-728 [pcl728]
+ * (ADLink) ACL-6126 [acl6126]
+ * (ADLink) ACL-6128 [acl6128]
+ *
+ * Configuration Options:
+ * [0] - IO Base
+ * [1] - IRQ (ACL-6126 only)
+ * [2] - D/A output range for channel 0
+ * [3] - D/A output range for channel 1
+ *
+ * Boards with > 2 analog output channels:
+ * [4] - D/A output range for channel 2
+ * [5] - D/A output range for channel 3
+ * [6] - D/A output range for channel 4
+ * [7] - D/A output range for channel 5
+ *
+ * Boards with > 6 analog output channels:
+ * [8] - D/A output range for channel 6
+ * [9] - D/A output range for channel 7
+ * [10] - D/A output range for channel 8
+ * [11] - D/A output range for channel 9
+ * [12] - D/A output range for channel 10
+ * [13] - D/A output range for channel 11
+ *
+ * For PCL-726 the D/A output ranges are:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: unknown
+ *
+ * For PCL-727:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: 4-20mA
+ *
+ * For PCL-728 and ACL-6128:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: 0-20mA
+ *
+ * For ACL-6126:
+ * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+#define PCL726_AO_MSB_REG(x) (0x00 + ((x) * 2))
+#define PCL726_AO_LSB_REG(x) (0x01 + ((x) * 2))
+#define PCL726_DO_MSB_REG 0x0c
+#define PCL726_DO_LSB_REG 0x0d
+#define PCL726_DI_MSB_REG 0x0e
+#define PCL726_DI_LSB_REG 0x0f
+
+#define PCL727_DI_MSB_REG 0x00
+#define PCL727_DI_LSB_REG 0x01
+#define PCL727_DO_MSB_REG 0x18
+#define PCL727_DO_LSB_REG 0x19
+
+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
+};
+
+struct pcl726_board {
+ const char *name;
+ unsigned long io_len;
+ unsigned int irq_mask;
+ const struct comedi_lrange *const *ao_ranges;
+ int ao_num_ranges;
+ int ao_nchan;
+ unsigned int have_dio:1;
+ unsigned int is_pcl727:1;
+};
+
+static const struct pcl726_board pcl726_boards[] = {
+ {
+ .name = "pcl726",
+ .io_len = 0x10,
+ .ao_ranges = &rangelist_726[0],
+ .ao_num_ranges = ARRAY_SIZE(rangelist_726),
+ .ao_nchan = 6,
+ .have_dio = 1,
+ }, {
+ .name = "pcl727",
+ .io_len = 0x20,
+ .ao_ranges = &rangelist_727[0],
+ .ao_num_ranges = ARRAY_SIZE(rangelist_727),
+ .ao_nchan = 12,
+ .have_dio = 1,
+ .is_pcl727 = 1,
+ }, {
+ .name = "pcl728",
+ .io_len = 0x08,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_728),
+ .ao_ranges = &rangelist_728[0],
+ .ao_nchan = 2,
+ }, {
+ .name = "acl6126",
+ .io_len = 0x10,
+ .irq_mask = 0x96e8,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_726),
+ .ao_ranges = &rangelist_726[0],
+ .ao_nchan = 6,
+ .have_dio = 1,
+ }, {
+ .name = "acl6128",
+ .io_len = 0x08,
+ .ao_num_ranges = ARRAY_SIZE(rangelist_728),
+ .ao_ranges = &rangelist_728[0],
+ .ao_nchan = 2,
+ },
+};
+
+struct pcl726_private {
+ const struct comedi_lrange *rangelist[12];
+ unsigned int ao_readback[12];
+ unsigned int cmd_running:1;
+};
+
+static int pcl726_intr_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = 0;
+ return insn->n;
+}
+
+static int pcl726_intr_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: ignored */
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int pcl726_intr_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl726_private *devpriv = dev->private;
+
+ devpriv->cmd_running = 1;
+
+ return 0;
+}
+
+static int pcl726_intr_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl726_private *devpriv = dev->private;
+
+ devpriv->cmd_running = 0;
+
+ return 0;
+}
+
+static irqreturn_t pcl726_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl726_private *devpriv = dev->private;
+
+ if (devpriv->cmd_running) {
+ pcl726_intr_cancel(dev, s);
+
+ comedi_buf_put(s, 0);
+ s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS);
+ comedi_event(dev, s);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pcl726_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl726_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ devpriv->ao_readback[chan] = val;
+
+ /* bipolar data to the DAC is two's complement */
+ if (comedi_chan_range_is_bipolar(s, chan, range))
+ val = comedi_offset_munge(s, val);
+
+ /* order is important, MSB then LSB */
+ outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan));
+ outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int pcl726_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl726_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pcl726_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct pcl726_board *board = comedi_board(dev);
+ unsigned int val;
+
+ if (board->is_pcl727) {
+ val = inb(dev->iobase + PCL727_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8);
+ } else {
+ val = inb(dev->iobase + PCL726_DI_LSB_REG);
+ val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8);
+ }
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int pcl726_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ const struct pcl726_board *board = comedi_board(dev);
+ unsigned long io = dev->iobase;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (board->is_pcl727) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, io + PCL727_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), io + PCL727_DO_MSB_REG);
+ } else {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, io + PCL726_DO_LSB_REG);
+ if (mask & 0xff00)
+ outb((s->state >> 8), io + PCL726_DO_MSB_REG);
+ }
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int pcl726_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pcl726_board *board = comedi_board(dev);
+ struct pcl726_private *devpriv;
+ struct comedi_subdevice *s;
+ int subdev;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0], board->io_len);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /*
+ * Hook up the external trigger source interrupt only if the
+ * user config option is valid and the board supports interrupts.
+ */
+ if (it->options[1] && (board->irq_mask & (1 << it->options[1]))) {
+ ret = request_irq(it->options[1], pcl726_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ /* External trigger source is from Pin-17 of CN3 */
+ dev->irq = it->options[1];
+ }
+ }
+
+ /* setup the per-channel analog output range_table_list */
+ for (i = 0; i < 12; i++) {
+ unsigned int opt = it->options[2 + i];
+
+ if (opt < board->ao_num_ranges && i < board->ao_nchan)
+ devpriv->rangelist[i] = board->ao_ranges[opt];
+ else
+ devpriv->rangelist[i] = &range_unknown;
+ }
+
+ subdev = board->have_dio ? 3 : 1;
+ if (dev->irq)
+ subdev++;
+ ret = comedi_alloc_subdevices(dev, subdev);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->ao_nchan;
+ s->maxdata = 0x0fff;
+ s->range_table_list = devpriv->rangelist;
+ s->insn_write = pcl726_ao_insn_write;
+ s->insn_read = pcl726_ao_insn_read;
+
+ if (board->have_dio) {
+ /* Digital Input subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->insn_bits = pcl726_di_insn_bits;
+ s->range_table = &range_digital;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 16;
+ s->maxdata = 1;
+ s->insn_bits = pcl726_do_insn_bits;
+ s->range_table = &range_digital;
+ }
+
+ if (dev->irq) {
+ /* Digial Input subdevice - Interrupt support */
+ s = &dev->subdevices[subdev++];
+ 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 = pcl726_intr_insn_bits;
+ s->len_chanlist = 1;
+ s->do_cmdtest = pcl726_intr_cmdtest;
+ s->do_cmd = pcl726_intr_cmd;
+ s->cancel = pcl726_intr_cancel;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl726_driver = {
+ .driver_name = "pcl726",
+ .module = THIS_MODULE,
+ .attach = pcl726_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcl726_boards[0].name,
+ .num_names = ARRAY_SIZE(pcl726_boards),
+ .offset = sizeof(struct pcl726_board),
+};
+module_comedi_driver(pcl726_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Advantech PCL-726 & compatibles");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcl730.c b/drivers/staging/comedi/drivers/pcl730.c
new file mode 100644
index 00000000000..7fb044ce399
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl730.c
@@ -0,0 +1,337 @@
+/*
+ * comedi/drivers/pcl730.c
+ * Driver for Advantech PCL-730 and clones
+ * José Luis Sánchez
+ */
+
+/*
+ * Driver: pcl730
+ * Description: Advantech PCL-730 (& compatibles)
+ * Devices: (Advantech) PCL-730 [pcl730]
+ * (ICP) ISO-730 [iso730]
+ * (Adlink) ACL-7130 [acl7130]
+ * (Advantech) PCM-3730 [pcm3730]
+ * (Advantech) PCL-725 [pcl725]
+ * (ICP) P8R8-DIO [p16r16dio]
+ * (Adlink) ACL-7225b [acl7225b]
+ * (ICP) P16R16-DIO [p16r16dio]
+ * (Advantech) PCL-733 [pcl733]
+ * (Advantech) PCL-734 [pcl734]
+ * (Diamond Systems) OPMM-1616-XT [opmm-1616-xt]
+ * (Diamond Systems) PEARL-MM-P [prearl-mm-p]
+ * Author: José Luis Sánchez (jsanchezv@teleline.es)
+ * Status: untested
+ *
+ * Configuration options:
+ * [0] - I/O port base
+ *
+ * Interrupts are not supported.
+ * The ACL-7130 card has an 8254 timer/counter not supported by this driver.
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register map
+ *
+ * The register map varies slightly depending on the board type but
+ * all registers are 8-bit.
+ *
+ * The boardinfo 'io_range' is used to allow comedi to request the
+ * proper range required by the board.
+ *
+ * The comedi_subdevice 'private' data is used to pass the register
+ * offset to the (*insn_bits) functions to read/write the correct
+ * registers.
+ *
+ * The basic register mapping looks like this:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) / inputs 0-7 (read)
+ * BASE+1 Isolated outputs 8-15 (write) / inputs 8-15 (read)
+ * BASE+2 TTL outputs 0-7 (write) / inputs 0-7 (read)
+ * BASE+3 TTL outputs 8-15 (write) / inputs 8-15 (read)
+ *
+ * The pcm3730 board does not have register BASE+1.
+ *
+ * The pcl725 and p8r8dio only have registers BASE+0 and BASE+1:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back on p8r8dio)
+ * BASE+1 Isolated inputs 0-7 (read)
+ *
+ * The acl7225b and p16r16dio boards have this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back)
+ * BASE+1 Isolated outputs 8-15 (write) (read back)
+ * BASE+2 Isolated inputs 0-7 (read)
+ * BASE+3 Isolated inputs 8-15 (read)
+ *
+ * The pcl733 and pcl733 boards have this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) or inputs 0-7 (read)
+ * BASE+1 Isolated outputs 8-15 (write) or inputs 8-15 (read)
+ * BASE+2 Isolated outputs 16-23 (write) or inputs 16-23 (read)
+ * BASE+3 Isolated outputs 24-31 (write) or inputs 24-31 (read)
+ *
+ * The opmm-1616-xt board has this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write) (read back)
+ * BASE+1 Isolated outputs 8-15 (write) (read back)
+ * BASE+2 Isolated inputs 0-7 (read)
+ * BASE+3 Isolated inputs 8-15 (read)
+ *
+ * These registers are not currently supported:
+ *
+ * BASE+2 Relay select register (write)
+ * BASE+3 Board reset control register (write)
+ * BASE+4 Interrupt control register (write)
+ * BASE+4 Change detect 7-0 status register (read)
+ * BASE+5 LED control register (write)
+ * BASE+5 Change detect 15-8 status register (read)
+ *
+ * The pearl-mm-p board has this register mapping:
+ *
+ * BASE+0 Isolated outputs 0-7 (write)
+ * BASE+1 Isolated outputs 8-15 (write)
+ */
+
+struct pcl730_board {
+ const char *name;
+ unsigned int io_range;
+ unsigned is_pcl725:1;
+ unsigned is_acl7225b:1;
+ unsigned has_readback:1;
+ unsigned has_ttl_io:1;
+ int n_subdevs;
+ int n_iso_out_chan;
+ int n_iso_in_chan;
+ int n_ttl_chan;
+};
+
+static const struct pcl730_board pcl730_boards[] = {
+ {
+ .name = "pcl730",
+ .io_range = 0x04,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "iso730",
+ .io_range = 0x04,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "acl7130",
+ .io_range = 0x08,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "pcm3730",
+ .io_range = 0x04,
+ .has_ttl_io = 1,
+ .n_subdevs = 4,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ .n_ttl_chan = 16,
+ }, {
+ .name = "pcl725",
+ .io_range = 0x02,
+ .is_pcl725 = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ }, {
+ .name = "p8r8dio",
+ .io_range = 0x02,
+ .is_pcl725 = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 8,
+ .n_iso_in_chan = 8,
+ }, {
+ .name = "acl7225b",
+ .io_range = 0x08, /* only 4 are used */
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "p16r16dio",
+ .io_range = 0x04,
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "pcl733",
+ .io_range = 0x04,
+ .n_subdevs = 1,
+ .n_iso_in_chan = 32,
+ }, {
+ .name = "pcl734",
+ .io_range = 0x04,
+ .n_subdevs = 1,
+ .n_iso_out_chan = 32,
+ }, {
+ .name = "opmm-1616-xt",
+ .io_range = 0x10,
+ .is_acl7225b = 1,
+ .has_readback = 1,
+ .n_subdevs = 2,
+ .n_iso_out_chan = 16,
+ .n_iso_in_chan = 16,
+ }, {
+ .name = "pearl-mm-p",
+ .io_range = 0x02,
+ .n_subdevs = 1,
+ .n_iso_out_chan = 16,
+ },
+};
+
+static int pcl730_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long reg = (unsigned long)s->private;
+ unsigned int mask;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ if (mask & 0x00ff)
+ outb(s->state & 0xff, dev->iobase + reg);
+ if ((mask & 0xff00) && (s->n_chan > 8))
+ outb((s->state >> 8) & 0xff, dev->iobase + reg + 1);
+ if ((mask & 0xff0000) && (s->n_chan > 16))
+ outb((s->state >> 16) & 0xff, dev->iobase + reg + 2);
+ if ((mask & 0xff000000) && (s->n_chan > 24))
+ outb((s->state >> 24) & 0xff, dev->iobase + reg + 3);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static unsigned int pcl730_get_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned long reg = (unsigned long)s->private;
+ unsigned int val;
+
+ val = inb(dev->iobase + reg);
+ if (s->n_chan > 8)
+ val |= (inb(dev->iobase + reg + 1) << 8);
+ if (s->n_chan > 16)
+ val |= (inb(dev->iobase + reg + 2) << 16);
+ if (s->n_chan > 24)
+ val |= (inb(dev->iobase + reg + 3) << 24);
+
+ return val;
+}
+
+static int pcl730_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = pcl730_get_bits(dev, s);
+
+ return insn->n;
+}
+
+static int pcl730_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ const struct pcl730_board *board = comedi_board(dev);
+ struct comedi_subdevice *s;
+ int subdev;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], board->io_range);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, board->n_subdevs);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ if (board->n_iso_out_chan) {
+ /* Isolated Digital Outputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_iso_out_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_do_insn_bits;
+ s->private = (void *)0;
+
+ /* get the initial state if supported */
+ if (board->has_readback)
+ s->state = pcl730_get_bits(dev, s);
+ }
+
+ if (board->n_iso_in_chan) {
+ /* Isolated Digital Inputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = board->n_iso_in_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_di_insn_bits;
+ s->private = board->is_acl7225b ? (void *)2 :
+ board->is_pcl725 ? (void *)1 : (void *)0;
+ }
+
+ if (board->has_ttl_io) {
+ /* TTL Digital Outputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = board->n_ttl_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_do_insn_bits;
+ s->private = (void *)2;
+
+ /* TTL Digital Inputs */
+ s = &dev->subdevices[subdev++];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = board->n_ttl_chan;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcl730_di_insn_bits;
+ s->private = (void *)2;
+ }
+
+ return 0;
+}
+
+static struct comedi_driver pcl730_driver = {
+ .driver_name = "pcl730",
+ .module = THIS_MODULE,
+ .attach = pcl730_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcl730_boards[0].name,
+ .num_names = ARRAY_SIZE(pcl730_boards),
+ .offset = sizeof(struct pcl730_board),
+};
+module_comedi_driver(pcl730_driver);
+
+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 00000000000..4c1b9470647
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl812.c
@@ -0,0 +1,1437 @@
+/*
+ * comedi/drivers/pcl812.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * 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 <dobes@tesnet.cz>
+ * 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 <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/gfp.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <asm/dma.h>
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+/* 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 */
+
+/*
+ * Register I/O map
+ */
+#define PCL812_TIMER_BASE 0x00
+#define PCL812_AI_LSB_REG 0x04
+#define PCL812_AI_MSB_REG 0x05
+#define PCL812_AI_MSB_DRDY (1 << 4)
+#define PCL812_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL812_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL812_DI_LSB_REG 0x06
+#define PCL812_DI_MSB_REG 0x07
+#define PCL812_STATUS_REG 0x08
+#define PCL812_STATUS_DRDY (1 << 5)
+#define PCL812_RANGE_REG 0x09
+#define PCL812_MUX_REG 0x0a
+#define PCL812_MUX_CHAN(x) ((x) << 0)
+#define PCL812_MUX_CS0 (1 << 4)
+#define PCL812_MUX_CS1 (1 << 5)
+#define PCL812_CTRL_REG 0x0b
+#define PCL812_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL812_CTRL_SOFT_TRIG (1 << 0)
+#define PCL812_CTRL_PACER_DMA_TRIG (2 << 0)
+#define PCL812_CTRL_PACER_EOC_TRIG (6 << 0)
+#define PCL812_SOFTTRIG_REG 0x0c
+#define PCL812_DO_LSB_REG 0x0d
+#define PCL812_DO_MSB_REG 0x0e
+
+#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)
+ }
+};
+
+struct pcl812_board {
+ const char *name;
+ int board_type;
+ int n_aichan;
+ int n_aochan;
+ unsigned int ai_ns_min;
+ const struct comedi_lrange *rangelist_ai;
+ unsigned int IRQbits;
+ unsigned int has_dma:1;
+ unsigned int has_16bit_ai:1;
+ unsigned int has_mpc508_mux:1;
+ unsigned int has_dio:1;
+};
+
+static const struct pcl812_board boardtypes[] = {
+ {
+ .name = "pcl812",
+ .board_type = boardPCL812,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 33000,
+ .rangelist_ai = &range_bipolar10,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "pcl812pg",
+ .board_type = boardPCL812PG,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 33000,
+ .rangelist_ai = &range_pcl812pg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112pg",
+ .board_type = boardPCL812PG,
+ .n_aichan = 16,
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl812pg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112dg",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "acl8112hg",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a821pgl",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 1,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b_ai,
+ .IRQbits = 0x000c,
+ .has_dio = 1,
+ }, {
+ .name = "a821pglnda",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b_ai,
+ .IRQbits = 0x000c,
+ }, {
+ .name = "a821pgh",
+ .board_type = boardA821,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 1,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_a821pgh_ai,
+ .IRQbits = 0x000c,
+ .has_dio = 1,
+ }, {
+ .name = "a822pgl",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a822pgh",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a823pgl",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 8000,
+ .rangelist_ai = &range_acl8112dg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a823pgh",
+ .board_type = boardACL8112,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 8000,
+ .rangelist_ai = &range_acl8112hg_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_dio = 1,
+ }, {
+ .name = "pcl813",
+ .board_type = boardPCL813,
+ .n_aichan = 32,
+ .rangelist_ai = &range_pcl813b_ai,
+ }, {
+ .name = "pcl813b",
+ .board_type = boardPCL813B,
+ .n_aichan = 32,
+ .rangelist_ai = &range_pcl813b_ai,
+ }, {
+ .name = "acl8113",
+ .board_type = boardACL8113,
+ .n_aichan = 32,
+ .rangelist_ai = &range_acl8113_1_ai,
+ }, {
+ .name = "iso813",
+ .board_type = boardISO813,
+ .n_aichan = 32,
+ .rangelist_ai = &range_iso813_1_ai,
+ }, {
+ .name = "acl8216",
+ .board_type = boardACL8216,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b2_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_16bit_ai = 1,
+ .has_mpc508_mux = 1,
+ .has_dio = 1,
+ }, {
+ .name = "a826pg",
+ .board_type = boardACL8216,
+ .n_aichan = 16, /* 8 differential */
+ .n_aochan = 2,
+ .ai_ns_min = 10000,
+ .rangelist_ai = &range_pcl813b2_ai,
+ .IRQbits = 0xdcfc,
+ .has_dma = 1,
+ .has_16bit_ai = 1,
+ .has_dio = 1,
+ },
+};
+
+struct pcl812_private {
+ unsigned char dma; /* >0 use dma ( usedDMA channel) */
+ unsigned char range_correction; /* =1 we must add 1 to range number */
+ unsigned int last_ai_chanspec;
+ unsigned char mode_reg_int; /* there is stored INT number for some card */
+ unsigned int ai_poll_ptr; /* how many sampes transfer poll */
+ unsigned int ai_act_scan; /* how many scans we finished */
+ unsigned int dmapages;
+ unsigned int hwdmasize;
+ unsigned long dmabuf[2]; /* PTR to DMA buf */
+ unsigned int hwdmaptr[2]; /* HW PTR to DMA buf */
+ 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 */
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int use_diff:1;
+ unsigned int use_mpc508:1;
+ unsigned int use_ext_trg:1;
+ unsigned int ai_dma:1;
+ unsigned int ai_eos:1;
+};
+
+static void pcl812_start_pacer(struct comedi_device *dev, bool load_timers)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + PCL812_TIMER_BASE;
+
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ udelay(1);
+
+ if (load_timers) {
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ }
+}
+
+static void pcl812_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int dma_flags;
+ unsigned int bytes;
+
+ /* we use EOS, so adapt DMA buffer to one scan */
+ if (devpriv->ai_eos) {
+ devpriv->dmabytestomove[0] = cfc_bytes_per_scan(s);
+ devpriv->dmabytestomove[1] = cfc_bytes_per_scan(s);
+ devpriv->dma_runs_to_end = 1;
+ } else {
+ devpriv->dmabytestomove[0] = devpriv->hwdmasize;
+ devpriv->dmabytestomove[1] = devpriv->hwdmasize;
+ if (s->async->prealloc_bufsz < devpriv->hwdmasize) {
+ devpriv->dmabytestomove[0] =
+ s->async->prealloc_bufsz;
+ devpriv->dmabytestomove[1] =
+ s->async->prealloc_bufsz;
+ }
+ if (cmd->stop_src == TRIG_NONE) {
+ devpriv->dma_runs_to_end = 1;
+ } else {
+ /* how many samples we must transfer? */
+ bytes = cmd->stop_arg * cfc_bytes_per_scan(s);
+
+ /* 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) {
+ devpriv->dmabytestomove[0] = devpriv->hwdmasize;
+ devpriv->ai_eos = 0;
+ }
+ if (devpriv->dmabytestomove[1] > devpriv->hwdmasize) {
+ devpriv->dmabytestomove[1] = devpriv->hwdmasize;
+ 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);
+}
+
+static void pcl812_ai_setup_next_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned long dma_flags;
+
+ 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);
+}
+
+static void pcl812_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chanspec, char wait)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int mux = 0;
+
+ if (chanspec == devpriv->last_ai_chanspec)
+ return;
+
+ devpriv->last_ai_chanspec = chanspec;
+
+ if (devpriv->use_mpc508) {
+ if (devpriv->use_diff) {
+ mux |= PCL812_MUX_CS0 | PCL812_MUX_CS1;
+ } else {
+ if (chan < 8)
+ mux |= PCL812_MUX_CS0;
+ else
+ mux |= PCL812_MUX_CS1;
+ }
+ }
+
+ outb(mux | PCL812_MUX_CHAN(chan), dev->iobase + PCL812_MUX_REG);
+ outb(range + devpriv->range_correction, dev->iobase + PCL812_RANGE_REG);
+
+ 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 pcl812_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL812_STATUS_REG);
+}
+
+static void pcl812_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(255, dev->iobase + PCL812_SOFTTRIG_REG);
+}
+
+static unsigned int pcl812_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL812_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL812_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl812_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ if (s->maxdata > 0x0fff) {
+ status = inb(dev->iobase + PCL812_STATUS_REG);
+ if ((status & PCL812_STATUS_DRDY) == 0)
+ return 0;
+ } else {
+ status = inb(dev->iobase + PCL812_AI_MSB_REG);
+ if ((status & PCL812_AI_MSB_DRDY) == 0)
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int pcl812_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ const struct pcl812_board *board = comedi_board(dev);
+ struct pcl812_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int flags;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+
+ if (devpriv->use_ext_trg)
+ flags = TRIG_EXT;
+ else
+ flags = TRIG_TIMER;
+ err |= cfc_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ai_ns_min);
+ else /* TRIG_EXT */
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= cfc_check_trigger_arg_min(&cmd->chanlist_len, 1);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl = 0;
+ unsigned int i;
+
+ pcl812_start_pacer(dev, false);
+
+ pcl812_ai_set_chan_range(dev, cmd->chanlist[0], 1);
+
+ if (devpriv->dma) { /* check if we can use DMA transfer */
+ devpriv->ai_dma = 1;
+ for (i = 1; i < cmd->chanlist_len; i++)
+ if (cmd->chanlist[0] != cmd->chanlist[i]) {
+ /* we cann't use DMA :-( */
+ devpriv->ai_dma = 0;
+ break;
+ }
+ } else {
+ devpriv->ai_dma = 0;
+ }
+
+ devpriv->ai_act_scan = 0;
+ devpriv->ai_poll_ptr = 0;
+ s->async->cur_chan = 0;
+
+ /* don't we want wake up every scan? */
+ if (cmd->flags & TRIG_WAKE_EOS) {
+ devpriv->ai_eos = 1;
+
+ /* DMA is useless for this situation */
+ if (cmd->chanlist_len == 1)
+ devpriv->ai_dma = 0;
+ }
+
+ if (devpriv->ai_dma)
+ pcl812_ai_setup_dma(dev, s);
+
+ switch (cmd->convert_src) {
+ case TRIG_TIMER:
+ pcl812_start_pacer(dev, true);
+ break;
+ }
+
+ if (devpriv->ai_dma)
+ ctrl |= PCL812_CTRL_PACER_DMA_TRIG;
+ else
+ ctrl |= PCL812_CTRL_PACER_EOC_TRIG;
+ outb(devpriv->mode_reg_int | ctrl, dev->iobase + PCL812_CTRL_REG);
+
+ return 0;
+}
+
+static bool pcl812_ai_next_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ s->async->cur_chan++;
+ if (s->async->cur_chan >= cmd->chanlist_len) {
+ s->async->cur_chan = 0;
+ devpriv->ai_act_scan++;
+ s->async->events |= COMEDI_CB_EOS;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ devpriv->ai_act_scan >= cmd->stop_arg) {
+ /* all data sampled */
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void pcl812_handle_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int next_chan;
+
+ if (pcl812_ai_eoc(dev, s, NULL, 0)) {
+ dev_dbg(dev->class_dev, "A/D cmd IRQ without DRDY!\n");
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return;
+ }
+
+ comedi_buf_put(s, pcl812_ai_get_sample(dev, s));
+
+ /* Set up next channel. Added by abbotti 2010-01-20, but untested. */
+ next_chan = s->async->cur_chan + 1;
+ if (next_chan >= cmd->chanlist_len)
+ next_chan = 0;
+ if (cmd->chanlist[s->async->cur_chan] != cmd->chanlist[next_chan])
+ pcl812_ai_set_chan_range(dev, cmd->chanlist[next_chan], 0);
+
+ pcl812_ai_next_chan(dev, s);
+}
+
+static void transfer_from_dma_buf(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *ptr,
+ unsigned int bufptr, unsigned int len)
+{
+ unsigned int i;
+
+ for (i = len; i; i--) {
+ comedi_buf_put(s, ptr[bufptr++]);
+
+ if (!pcl812_ai_next_chan(dev, s))
+ break;
+ }
+}
+
+static void pcl812_handle_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ int len, bufptr;
+ unsigned short *ptr;
+
+ ptr = (unsigned short *)devpriv->dmabuf[devpriv->next_dma_buf];
+ len = (devpriv->dmabytestomove[devpriv->next_dma_buf] >> 1) -
+ devpriv->ai_poll_ptr;
+
+ pcl812_ai_setup_next_dma(dev, s);
+
+ bufptr = devpriv->ai_poll_ptr;
+ devpriv->ai_poll_ptr = 0;
+
+ transfer_from_dma_buf(dev, s, ptr, bufptr, len);
+}
+
+static irqreturn_t pcl812_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl812_private *devpriv = dev->private;
+
+ if (!dev->attached) {
+ pcl812_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_dma)
+ pcl812_handle_dma(dev, s);
+ else
+ pcl812_handle_eoc(dev, s);
+
+ pcl812_ai_clear_eoc(dev);
+
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+ 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 int pcl812_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl812_private *devpriv = dev->private;
+
+ if (devpriv->ai_dma)
+ disable_dma(devpriv->dma);
+
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ pcl812_start_pacer(dev, false);
+ pcl812_ai_clear_eoc(dev);
+ return 0;
+}
+
+static int pcl812_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl812_private *devpriv = dev->private;
+ int ret = 0;
+ int i;
+
+ outb(devpriv->mode_reg_int | PCL812_CTRL_SOFT_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+
+ pcl812_ai_set_chan_range(dev, insn->chanspec, 1);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl812_ai_clear_eoc(dev);
+ pcl812_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl812_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl812_ai_get_sample(dev, s);
+ }
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ pcl812_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl812_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ outb((data[i] & 0xff),
+ dev->iobase + PCL812_AO_LSB_REG(chan));
+ outb((data[i] >> 8) & 0x0f,
+ dev->iobase + PCL812_AO_MSB_REG(chan));
+ devpriv->ao_readback[chan] = data[i];
+ }
+
+ return insn->n;
+}
+
+static int pcl812_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pcl812_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL812_DI_LSB_REG) |
+ (inb(dev->iobase + PCL812_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl812_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL812_DO_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL812_DO_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl812_reset(struct comedi_device *dev)
+{
+ const struct pcl812_board *board = comedi_board(dev);
+ struct pcl812_private *devpriv = dev->private;
+ unsigned int chan;
+
+ /* disable analog input trigger */
+ outb(devpriv->mode_reg_int | PCL812_CTRL_DISABLE_TRIG,
+ dev->iobase + PCL812_CTRL_REG);
+ pcl812_ai_clear_eoc(dev);
+
+ /* stop pacer */
+ if (board->IRQbits)
+ pcl812_start_pacer(dev, false);
+
+ /*
+ * Invalidate last_ai_chanspec then set analog input to
+ * known channel/range.
+ */
+ devpriv->last_ai_chanspec = CR_PACK(16, 0, 0);
+ pcl812_ai_set_chan_range(dev, CR_PACK(0, 0, 0), 0);
+
+ /* set analog output channels to 0V */
+ for (chan = 0; chan < board->n_aochan; chan++) {
+ outb(0, dev->iobase + PCL812_AO_LSB_REG(chan));
+ outb(0, dev->iobase + PCL812_AO_MSB_REG(chan));
+ }
+
+ /* set all digital outputs low */
+ if (board->has_dio) {
+ outb(0, dev->iobase + PCL812_DO_MSB_REG);
+ outb(0, dev->iobase + PCL812_DO_LSB_REG);
+ }
+}
+
+static void pcl812_set_ai_range_table(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it)
+{
+ const struct pcl812_board *board = comedi_board(dev);
+ struct pcl812_private *devpriv = dev->private;
+
+ /* default to the range table from the boardinfo */
+ s->range_table = board->rangelist_ai;
+
+ /* now check the user config option based on the boardtype */
+ switch (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;
+ }
+ 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;
+ }
+ 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;
+ }
+ break;
+ }
+}
+
+static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl812_board *board = comedi_board(dev);
+ struct pcl812_private *devpriv;
+ struct comedi_subdevice *s;
+ int n_subdevices;
+ int subdev;
+ int ret;
+ int i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ if ((1 << it->options[1]) & board->IRQbits) {
+ ret = request_irq(it->options[1], pcl812_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* we need an IRQ to do DMA on channel 3 or 1 */
+ if (dev->irq && board->has_dma &&
+ (it->options[2] == 3 || it->options[2] == 1)) {
+ ret = request_dma(it->options[2], dev->board_name);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "unable to request DMA channel %d\n",
+ it->options[2]);
+ return -EBUSY;
+ }
+ devpriv->dma = it->options[2];
+
+ devpriv->dmapages = 1; /* we want 8KB */
+ devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE;
+
+ for (i = 0; i < 2; i++) {
+ unsigned long dmabuf;
+
+ dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ devpriv->dmabuf[i] = dmabuf;
+ devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf);
+ }
+ }
+
+ /* differential analog inputs? */
+ switch (board->board_type) {
+ case boardA821:
+ if (it->options[2] == 1)
+ devpriv->use_diff = 1;
+ break;
+ case boardACL8112:
+ case boardACL8216:
+ if (it->options[4] == 1)
+ devpriv->use_diff = 1;
+ break;
+ }
+
+ n_subdevices = 1; /* all boardtypes have analog inputs */
+ if (board->n_aochan > 0)
+ n_subdevices++;
+ if (board->has_dio)
+ n_subdevices += 2;
+
+ ret = comedi_alloc_subdevices(dev, n_subdevices);
+ if (ret)
+ return ret;
+
+ subdev = 0;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (devpriv->use_diff) {
+ s->subdev_flags |= SDF_DIFF;
+ s->n_chan = board->n_aichan / 2;
+ } else {
+ s->subdev_flags |= SDF_GROUND;
+ s->n_chan = board->n_aichan;
+ }
+ s->maxdata = board->has_16bit_ai ? 0xffff : 0x0fff;
+
+ pcl812_set_ai_range_table(dev, s, it);
+
+ s->insn_read = pcl812_ai_insn_read;
+
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = MAX_CHANLIST_LEN;
+ s->do_cmdtest = pcl812_ai_cmdtest;
+ s->do_cmd = pcl812_ai_cmd;
+ s->poll = pcl812_ai_poll;
+ s->cancel = pcl812_ai_cancel;
+ }
+
+ devpriv->use_mpc508 = board->has_mpc508_mux;
+
+ subdev++;
+
+ /* analog output */
+ if (board->n_aochan > 0) {
+ s = &dev->subdevices[subdev];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0xfff;
+ s->range_table = &range_unipolar5;
+ s->insn_read = pcl812_ao_insn_read;
+ s->insn_write = pcl812_ao_insn_write;
+ switch (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++;
+ }
+
+ if (board->has_dio) {
+ /* Digital Input subdevice */
+ s = &dev->subdevices[subdev];
+ 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 = pcl812_di_insn_bits;
+ subdev++;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[subdev];
+ 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 = pcl812_do_insn_bits;
+ subdev++;
+ }
+
+ switch (board->board_type) {
+ case boardACL8216:
+ 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;
+ break;
+ case boardA821:
+ devpriv->max_812_ai_mode0_rangewait = 1;
+ devpriv->mode_reg_int = (dev->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;
+ }
+
+ pcl812_reset(dev);
+
+ return 0;
+}
+
+static void pcl812_detach(struct comedi_device *dev)
+{
+ struct pcl812_private *devpriv = dev->private;
+
+ if (devpriv) {
+ if (devpriv->dmabuf[0])
+ free_pages(devpriv->dmabuf[0], devpriv->dmapages);
+ if (devpriv->dmabuf[1])
+ free_pages(devpriv->dmabuf[1], devpriv->dmapages);
+ if (devpriv->dma)
+ free_dma(devpriv->dma);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl812_driver = {
+ .driver_name = "pcl812",
+ .module = THIS_MODULE,
+ .attach = pcl812_attach,
+ .detach = pcl812_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl812_board),
+};
+module_comedi_driver(pcl812_driver);
+
+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 00000000000..d9ca7fe16c9
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl816.c
@@ -0,0 +1,811 @@
+/*
+ comedi/drivers/pcl816.c
+
+ Author: Juan Grigera <juan@grigera.com.ar>
+ based on pcl818 by Michal Dobes <dobes@tesnet.cz> 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 <juan@grigera.com.ar>
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/gfp.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <asm/dma.h>
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+/*
+ * Register I/O map
+ */
+#define PCL816_DO_DI_LSB_REG 0x00
+#define PCL816_DO_DI_MSB_REG 0x01
+#define PCL816_TIMER_BASE 0x04
+#define PCL816_AI_LSB_REG 0x08
+#define PCL816_AI_MSB_REG 0x09
+#define PCL816_RANGE_REG 0x09
+#define PCL816_CLRINT_REG 0x0a
+#define PCL816_MUX_REG 0x0b
+#define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
+#define PCL816_CTRL_REG 0x0c
+#define PCL816_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL816_CTRL_SOFT_TRIG (1 << 0)
+#define PCL816_CTRL_PACER_TRIG (1 << 1)
+#define PCL816_CTRL_EXT_TRIG (1 << 2)
+#define PCL816_CTRL_POE (1 << 3)
+#define PCL816_CTRL_DMAEN (1 << 4)
+#define PCL816_CTRL_INTEN (1 << 5)
+#define PCL816_CTRL_DMASRC_SLOT0 (0 << 6)
+#define PCL816_CTRL_DMASRC_SLOT1 (1 << 6)
+#define PCL816_CTRL_DMASRC_SLOT2 (2 << 6)
+#define PCL816_STATUS_REG 0x0d
+#define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0)
+#define PCL816_STATUS_INTSRC_MASK (3 << 4)
+#define PCL816_STATUS_INTSRC_SLOT0 (0 << 4)
+#define PCL816_STATUS_INTSRC_SLOT1 (1 << 4)
+#define PCL816_STATUS_INTSRC_SLOT2 (2 << 4)
+#define PCL816_STATUS_INTSRC_DMA (3 << 4)
+#define PCL816_STATUS_INTACT (1 << 6)
+#define PCL816_STATUS_DRDY (1 << 7)
+
+#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;
+ int ai_maxdata;
+ int ao_maxdata;
+ int ai_chanlist;
+};
+
+static const struct pcl816_board boardtypes[] = {
+ {
+ .name = "pcl816",
+ .ai_maxdata = 0xffff,
+ .ao_maxdata = 0xffff,
+ .ai_chanlist = 1024,
+ }, {
+ .name = "pcl814b",
+ .ai_maxdata = 0x3fff,
+ .ao_maxdata = 0x3fff,
+ .ai_chanlist = 1024,
+ },
+};
+
+struct pcl816_private {
+ unsigned int dma; /* used DMA, 0=don't use DMA */
+ unsigned int dmapages;
+ unsigned int hwdmasize;
+ unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */
+ unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */
+ 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 */
+ int ai_act_scan; /* how many scans we finished */
+ unsigned int ai_poll_ptr; /* how many sampes transfer poll */
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int ai_cmd_running:1;
+ unsigned int ai_cmd_canceled:1;
+};
+
+static int check_channel_list(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chanlist, unsigned int chanlen);
+
+static void pcl816_start_pacer(struct comedi_device *dev, bool load_counters)
+{
+ struct pcl816_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;
+
+ i8254_set_mode(timer_base, 0, 0, I8254_MODE1 | I8254_BINARY);
+ i8254_write(timer_base, 0, 0, 0x00ff);
+ udelay(1);
+
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ udelay(1);
+
+ if (load_counters) {
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ }
+}
+
+static void pcl816_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int dma_flags;
+ unsigned int bytes;
+
+ bytes = devpriv->hwdmasize;
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* how many */
+ bytes = cmd->stop_arg * cfc_bytes_per_scan(s);
+
+ /* how many DMA pages we must fill */
+ devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize;
+
+ /* on last dma transfer must be moved */
+ devpriv->last_dma_run = bytes % devpriv->hwdmasize;
+ devpriv->dma_runs_to_end--;
+ if (devpriv->dma_runs_to_end >= 0)
+ bytes = devpriv->hwdmasize;
+ } 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);
+}
+
+static void pcl816_ai_setup_next_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long dma_flags;
+
+ disable_dma(devpriv->dma);
+ if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
+ /* switch dma bufs */
+ devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
+ 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->dma_runs_to_end)
+ set_dma_count(devpriv->dma, devpriv->hwdmasize);
+ else
+ set_dma_count(devpriv->dma, devpriv->last_dma_run);
+ release_dma_lock(dma_flags);
+ enable_dma(devpriv->dma);
+ }
+
+ devpriv->dma_runs_to_end--;
+}
+
+static void pcl816_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chan,
+ unsigned int range)
+{
+ outb(chan, dev->iobase + PCL816_MUX_REG);
+ outb(range, dev->iobase + PCL816_RANGE_REG);
+}
+
+static void pcl816_ai_set_chan_scan(struct comedi_device *dev,
+ unsigned int first_chan,
+ unsigned int last_chan)
+{
+ outb(PCL816_MUX_SCAN(first_chan, last_chan),
+ dev->iobase + PCL816_MUX_REG);
+}
+
+static void pcl816_ai_setup_chanlist(struct comedi_device *dev,
+ unsigned int *chanlist,
+ unsigned int seglen)
+{
+ unsigned int first_chan = CR_CHAN(chanlist[0]);
+ unsigned int last_chan;
+ unsigned int range;
+ unsigned int i;
+
+ /* store range list to card */
+ for (i = 0; i < seglen; i++) {
+ last_chan = CR_CHAN(chanlist[i]);
+ range = CR_RANGE(chanlist[i]);
+
+ pcl816_ai_set_chan_range(dev, last_chan, range);
+ }
+
+ udelay(1);
+
+ pcl816_ai_set_chan_scan(dev, first_chan, last_chan);
+}
+
+static void pcl816_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL816_CLRINT_REG);
+}
+
+static void pcl816_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(0, dev->iobase + PCL816_AI_LSB_REG);
+}
+
+static unsigned int pcl816_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL816_AI_LSB_REG);
+
+ return val & s->maxdata;
+}
+
+static int pcl816_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL816_STATUS_REG);
+ if ((status & PCL816_STATUS_DRDY) == 0)
+ return 0;
+ return -EBUSY;
+}
+
+static bool pcl816_ai_next_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ s->async->cur_chan++;
+ if (s->async->cur_chan >= cmd->chanlist_len) {
+ s->async->cur_chan = 0;
+ devpriv->ai_act_scan++;
+ s->async->events |= COMEDI_CB_EOS;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT &&
+ devpriv->ai_act_scan >= cmd->stop_arg) {
+ /* all data sampled */
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void transfer_from_dma_buf(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned short *ptr,
+ unsigned int bufptr, unsigned int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ comedi_buf_put(s, ptr[bufptr++]);
+
+ if (!pcl816_ai_next_chan(dev, s))
+ return;
+ }
+}
+
+static irqreturn_t pcl816_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct pcl816_private *devpriv = dev->private;
+ unsigned short *ptr;
+ unsigned int bufptr;
+ unsigned int len;
+
+ if (!dev->attached || !devpriv->ai_cmd_running) {
+ pcl816_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_cmd_canceled) {
+ devpriv->ai_cmd_canceled = 0;
+ pcl816_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ ptr = (unsigned short *)devpriv->dmabuf[devpriv->next_dma_buf];
+
+ pcl816_ai_setup_next_dma(dev, s);
+
+ len = (devpriv->hwdmasize >> 1) - devpriv->ai_poll_ptr;
+ bufptr = devpriv->ai_poll_ptr;
+ devpriv->ai_poll_ptr = 0;
+
+ transfer_from_dma_buf(dev, s, ptr, bufptr, len);
+
+ pcl816_ai_clear_eoc(dev);
+
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+static int pcl816_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct pcl816_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_EXT | TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 10000);
+ else /* TRIG_EXT */
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+
+ /* step 4: fix up any arguments */
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(I8254_OSC_BASE_10MHZ,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ 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)
+{
+ struct pcl816_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl;
+ unsigned int seglen;
+
+ if (devpriv->ai_cmd_running)
+ return -EBUSY;
+
+ pcl816_start_pacer(dev, false);
+
+ seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
+ if (seglen < 1)
+ return -EINVAL;
+ pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen);
+ udelay(1);
+
+ devpriv->ai_act_scan = 0;
+ s->async->cur_chan = 0;
+ devpriv->ai_cmd_running = 1;
+ devpriv->ai_poll_ptr = 0;
+ devpriv->ai_cmd_canceled = 0;
+
+ pcl816_ai_setup_dma(dev, s);
+
+ pcl816_start_pacer(dev, true);
+
+ ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | PCL816_CTRL_DMASRC_SLOT0;
+ if (cmd->convert_src == TRIG_TIMER)
+ ctrl |= PCL816_CTRL_PACER_TRIG;
+ else /* TRIG_EXT */
+ ctrl |= PCL816_CTRL_EXT_TRIG;
+
+ outb(ctrl, dev->iobase + PCL816_CTRL_REG);
+ outb((devpriv->dma << 4) | dev->irq, dev->iobase + PCL816_STATUS_REG);
+
+ return 0;
+}
+
+static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+ unsigned long flags;
+ unsigned int top1, top2, i;
+
+ 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 - 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,
+ (unsigned 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);
+
+ cfc_handle_events(dev, s);
+
+ return s->async->buf_write_count - s->async->buf_read_count;
+}
+
+static int pcl816_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl816_private *devpriv = dev->private;
+
+ if (!devpriv->ai_cmd_running)
+ return 0;
+
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_clear_eoc(dev);
+
+ /* Stop pacer */
+ i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
+ 2, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(dev->iobase + PCL816_TIMER_BASE, 0,
+ 1, I8254_MODE0 | I8254_BINARY);
+
+ devpriv->ai_cmd_running = 0;
+ devpriv->ai_cmd_canceled = 1;
+
+ return 0;
+}
+
+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++) {
+ /* 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 :-( */
+ dev_dbg(dev->class_dev,
+ "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;
+ }
+ /* well, this is next correct channel in list */
+ chansegment[i] = chanlist[i];
+ }
+
+ /* check whole chanlist */
+ for (i = 0, segpos = 0; i < chanlen; i++) {
+ if (chanlist[i] != chansegment[i % seglen]) {
+ dev_dbg(dev->class_dev,
+ "bad channel 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; /* we can serve this with MUX logic */
+}
+
+static int pcl816_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG);
+
+ pcl816_ai_set_chan_range(dev, chan, range);
+ pcl816_ai_set_chan_scan(dev, chan, chan);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl816_ai_clear_eoc(dev);
+ pcl816_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl816_ai_get_sample(dev, s);
+ }
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl816_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) |
+ (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl816_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl816_reset(struct comedi_device *dev)
+{
+ unsigned long timer_base = dev->iobase + PCL816_TIMER_BASE;
+
+ outb(PCL816_CTRL_DISABLE_TRIG, dev->iobase + PCL816_CTRL_REG);
+ pcl816_ai_set_chan_range(dev, 0, 0);
+ pcl816_ai_clear_eoc(dev);
+
+ /* Stop pacer */
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY);
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + PCL816_DO_DI_LSB_REG);
+ outb(0, dev->iobase + PCL816_DO_DI_MSB_REG);
+}
+
+static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl816_board *board = comedi_board(dev);
+ struct pcl816_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ /* we can use IRQ 2-7 for async command support */
+ if (it->options[1] >= 2 && it->options[1] <= 7) {
+ ret = request_irq(it->options[1], pcl816_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* we need an IRQ to do DMA on channel 3 or 1 */
+ if (dev->irq && (it->options[2] == 3 || it->options[2] == 1)) {
+ ret = request_dma(it->options[2], dev->board_name);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "unable to request DMA channel %d\n",
+ it->options[2]);
+ return -EBUSY;
+ }
+ devpriv->dma = it->options[2];
+
+ devpriv->dmapages = 2; /* we need 16KB */
+ devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE;
+
+ for (i = 0; i < 2; i++) {
+ unsigned long dmabuf;
+
+ dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ devpriv->dmabuf[i] = dmabuf;
+ devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf);
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_CMD_READ | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = &range_pcl816;
+ s->insn_read = pcl816_ai_insn_read;
+ if (devpriv->dma) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = board->ai_chanlist;
+ s->do_cmdtest = pcl816_ai_cmdtest;
+ s->do_cmd = pcl816_ai_cmd;
+ s->poll = pcl816_ai_poll;
+ s->cancel = pcl816_ai_cancel;
+ }
+
+ /* Analog OUtput subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_UNUSED;
+#if 0
+ subdevs[1] = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = 1;
+ s->maxdata = board->ao_maxdata;
+ s->range_table = &range_pcl816;
+#endif
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ 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 = pcl816_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ 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 = pcl816_do_insn_bits;
+
+ pcl816_reset(dev);
+
+ return 0;
+}
+
+static void pcl816_detach(struct comedi_device *dev)
+{
+ struct pcl816_private *devpriv = dev->private;
+
+ if (dev->private) {
+ pcl816_ai_cancel(dev, dev->read_subdev);
+ pcl816_reset(dev);
+ if (devpriv->dma)
+ free_dma(devpriv->dma);
+ if (devpriv->dmabuf[0])
+ free_pages(devpriv->dmabuf[0], devpriv->dmapages);
+ if (devpriv->dmabuf[1])
+ free_pages(devpriv->dmabuf[1], devpriv->dmapages);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl816_driver = {
+ .driver_name = "pcl816",
+ .module = THIS_MODULE,
+ .attach = pcl816_attach,
+ .detach = pcl816_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl816_board),
+};
+module_comedi_driver(pcl816_driver);
+
+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 00000000000..7d00ae639d3
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcl818.c
@@ -0,0 +1,1258 @@
+/*
+ comedi/drivers/pcl818.c
+
+ Author: Michal Dobes <dobes@tesnet.cz>
+
+ 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 <dobes@tesnet.cz>
+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 <linux/module.h>
+#include <linux/gfp.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <asm/dma.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+/* boards constants */
+
+#define boardPCL818L 0
+#define boardPCL818H 1
+#define boardPCL818HD 2
+#define boardPCL818HG 3
+#define boardPCL818 4
+#define boardPCL718 5
+
+/*
+ * Register I/O map
+ */
+#define PCL818_AI_LSB_REG 0x00
+#define PCL818_AI_MSB_REG 0x01
+#define PCL818_RANGE_REG 0x01
+#define PCL818_MUX_REG 0x02
+#define PCL818_MUX_SCAN(_first, _last) (((_last) << 4) | (_first))
+#define PCL818_DO_DI_LSB_REG 0x03
+#define PCL818_AO_LSB_REG(x) (0x04 + ((x) * 2))
+#define PCL818_AO_MSB_REG(x) (0x05 + ((x) * 2))
+#define PCL818_STATUS_REG 0x08
+#define PCL818_STATUS_NEXT_CHAN_MASK (0xf << 0)
+#define PCL818_STATUS_INT (1 << 4)
+#define PCL818_STATUS_MUX (1 << 5)
+#define PCL818_STATUS_UNI (1 << 6)
+#define PCL818_STATUS_EOC (1 << 7)
+#define PCL818_CTRL_REG 0x09
+#define PCL818_CTRL_DISABLE_TRIG (0 << 0)
+#define PCL818_CTRL_SOFT_TRIG (1 << 0)
+#define PCL818_CTRL_EXT_TRIG (2 << 0)
+#define PCL818_CTRL_PACER_TRIG (3 << 0)
+#define PCL818_CTRL_DMAE (1 << 2)
+#define PCL818_CTRL_IRQ(x) ((x) << 4)
+#define PCL818_CTRL_INTE (1 << 7)
+#define PCL818_CNTENABLE_REG 0x0a
+#define PCL818_CNTENABLE_PACER_ENA (0 << 0)
+#define PCL818_CNTENABLE_PACER_TRIG0 (1 << 0)
+#define PCL818_CNTENABLE_CNT0_EXT_CLK (0 << 1)
+#define PCL818_CNTENABLE_CNT0_INT_CLK (1 << 1)
+#define PCL818_DO_DI_MSB_REG 0x0b
+#define PCL818_TIMER_BASE 0x0c
+
+/* 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 24
+
+#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)
+ }
+};
+
+struct pcl818_board {
+ const char *name;
+ unsigned int ns_min;
+ int n_aochan;
+ const struct comedi_lrange *ai_range_type;
+ unsigned int has_dma:1;
+ unsigned int has_fifo:1;
+ unsigned int is_818:1;
+};
+
+static const struct pcl818_board boardtypes[] = {
+ {
+ .name = "pcl818l",
+ .ns_min = 25000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818l_l_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818h",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818hd",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .has_fifo = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818hg",
+ .ns_min = 10000,
+ .n_aochan = 1,
+ .ai_range_type = &range_pcl818hg_ai,
+ .has_dma = 1,
+ .has_fifo = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl818",
+ .ns_min = 10000,
+ .n_aochan = 2,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ }, {
+ .name = "pcl718",
+ .ns_min = 16000,
+ .n_aochan = 2,
+ .ai_range_type = &range_unipolar5,
+ .has_dma = 1,
+ }, {
+ .name = "pcm3718",
+ .ns_min = 10000,
+ .ai_range_type = &range_pcl818h_ai,
+ .has_dma = 1,
+ .is_818 = 1,
+ },
+};
+
+struct pcl818_private {
+ unsigned int dma; /* used DMA, 0=don't use DMA */
+ unsigned int dmapages;
+ unsigned int hwdmasize;
+ unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */
+ unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */
+ 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 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 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_data_len; /* len of data buffer */
+ unsigned int ao_readback[2];
+ unsigned int divisor1;
+ unsigned int divisor2;
+ unsigned int usefifo:1;
+ unsigned int ai_cmd_running:1;
+ unsigned int ai_cmd_canceled:1;
+};
+
+static void pcl818_start_pacer(struct comedi_device *dev, bool load_counters)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned long timer_base = dev->iobase + PCL818_TIMER_BASE;
+
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
+ udelay(1);
+
+ if (load_counters) {
+ i8254_write(timer_base, 0, 2, devpriv->divisor2);
+ i8254_write(timer_base, 0, 1, devpriv->divisor1);
+ }
+}
+
+static void pcl818_ai_setup_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int flags;
+ unsigned int bytes;
+
+ disable_dma(devpriv->dma); /* disable dma */
+ bytes = devpriv->hwdmasize;
+ if (cmd->stop_src == TRIG_COUNT) {
+ bytes = cmd->stop_arg * cfc_bytes_per_scan(s);
+ devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize;
+ devpriv->last_dma_run = bytes % devpriv->hwdmasize;
+ devpriv->dma_runs_to_end--;
+ if (devpriv->dma_runs_to_end >= 0)
+ bytes = devpriv->hwdmasize;
+ }
+
+ 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);
+}
+
+static void pcl818_ai_setup_next_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ disable_dma(devpriv->dma);
+ devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
+ if (devpriv->dma_runs_to_end > -1 || cmd->stop_src == TRIG_NONE) {
+ /* 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 || cmd->stop_src == TRIG_NONE)
+ set_dma_count(devpriv->dma, devpriv->hwdmasize);
+ else
+ set_dma_count(devpriv->dma, devpriv->last_dma_run);
+ release_dma_lock(flags);
+ enable_dma(devpriv->dma);
+ }
+
+ devpriv->dma_runs_to_end--;
+}
+
+static void pcl818_ai_set_chan_range(struct comedi_device *dev,
+ unsigned int chan,
+ unsigned int range)
+{
+ outb(chan, dev->iobase + PCL818_MUX_REG);
+ outb(range, dev->iobase + PCL818_RANGE_REG);
+}
+
+static void pcl818_ai_set_chan_scan(struct comedi_device *dev,
+ unsigned int first_chan,
+ unsigned int last_chan)
+{
+ outb(PCL818_MUX_SCAN(first_chan, last_chan),
+ dev->iobase + PCL818_MUX_REG);
+}
+
+static void pcl818_ai_setup_chanlist(struct comedi_device *dev,
+ unsigned int *chanlist,
+ unsigned int seglen)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned int first_chan = CR_CHAN(chanlist[0]);
+ unsigned int last_chan;
+ unsigned int range;
+ int i;
+
+ devpriv->act_chanlist_len = seglen;
+ devpriv->act_chanlist_pos = 0;
+
+ /* store range list to card */
+ for (i = 0; i < seglen; i++) {
+ last_chan = CR_CHAN(chanlist[i]);
+ range = CR_RANGE(chanlist[i]);
+
+ devpriv->act_chanlist[i] = last_chan;
+
+ pcl818_ai_set_chan_range(dev, last_chan, range);
+ }
+
+ udelay(1);
+
+ pcl818_ai_set_chan_scan(dev, first_chan, last_chan);
+}
+
+static void pcl818_ai_clear_eoc(struct comedi_device *dev)
+{
+ /* writing any value clears the interrupt request */
+ outb(0, dev->iobase + PCL818_STATUS_REG);
+}
+
+static void pcl818_ai_soft_trig(struct comedi_device *dev)
+{
+ /* writing any value triggers a software conversion */
+ outb(0, dev->iobase + PCL818_AI_LSB_REG);
+}
+
+static unsigned int pcl818_ai_get_fifo_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chan)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL818_FI_DATALO);
+ val |= (inb(dev->iobase + PCL818_FI_DATAHI) << 8);
+
+ if (chan)
+ *chan = val & 0xf;
+
+ return (val >> 4) & s->maxdata;
+}
+
+static unsigned int pcl818_ai_get_sample(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int *chan)
+{
+ unsigned int val;
+
+ val = inb(dev->iobase + PCL818_AI_MSB_REG) << 8;
+ val |= inb(dev->iobase + PCL818_AI_LSB_REG);
+
+ if (chan)
+ *chan = val & 0xf;
+
+ return (val >> 4) & s->maxdata;
+}
+
+static int pcl818_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCL818_STATUS_REG);
+ if (status & PCL818_STATUS_INT)
+ return 0;
+ return -EBUSY;
+}
+
+static bool pcl818_ai_dropout(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned int expected_chan;
+
+ expected_chan = devpriv->act_chanlist[devpriv->act_chanlist_pos];
+ if (chan != expected_chan) {
+ dev_dbg(dev->class_dev,
+ "A/D mode1/3 %s - channel dropout %d!=%d !\n",
+ (devpriv->dma) ? "DMA" :
+ (devpriv->usefifo) ? "FIFO" : "IRQ",
+ chan, expected_chan);
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return true;
+ }
+ return false;
+}
+
+static bool pcl818_ai_next_chan(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ 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 >= cmd->chanlist_len) {
+ s->async->cur_chan = 0;
+ devpriv->ai_act_scan--;
+ s->async->events |= COMEDI_CB_EOS;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan == 0) {
+ /* all data sampled */
+ s->async->events |= COMEDI_CB_EOA;
+ return false;
+ }
+
+ return true;
+}
+
+static void pcl818_handle_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int chan;
+ unsigned int val;
+
+ if (pcl818_ai_eoc(dev, s, NULL, 0)) {
+ comedi_error(dev, "A/D mode1/3 IRQ without DRDY!");
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return;
+ }
+
+ val = pcl818_ai_get_sample(dev, s, &chan);
+
+ if (pcl818_ai_dropout(dev, s, chan))
+ return;
+
+ comedi_buf_put(s, val);
+
+ pcl818_ai_next_chan(dev, s);
+}
+
+static void pcl818_handle_dma(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned short *ptr;
+ unsigned int chan;
+ unsigned int val;
+ int i, len, bufptr;
+
+ pcl818_ai_setup_next_dma(dev, s);
+
+ ptr = (unsigned short *)devpriv->dmabuf[1 - devpriv->next_dma_buf];
+
+ len = devpriv->hwdmasize >> 1;
+ bufptr = 0;
+
+ for (i = 0; i < len; i++) {
+ val = ptr[bufptr++];
+ chan = val & 0xf;
+ val = (val >> 4) & s->maxdata;
+
+ if (pcl818_ai_dropout(dev, s, chan))
+ break;
+
+ comedi_buf_put(s, val);
+
+ if (!pcl818_ai_next_chan(dev, s))
+ break;
+ }
+}
+
+static void pcl818_handle_fifo(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ unsigned int status;
+ unsigned int chan;
+ unsigned int val;
+ int i, len;
+
+ status = inb(dev->iobase + PCL818_FI_STATUS);
+
+ if (status & 4) {
+ comedi_error(dev, "A/D mode1/3 FIFO overflow!");
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return;
+ }
+
+ if (status & 1) {
+ comedi_error(dev, "A/D mode1/3 FIFO interrupt without data!");
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ return;
+ }
+
+ if (status & 2)
+ len = 512;
+ else
+ len = 0;
+
+ for (i = 0; i < len; i++) {
+ val = pcl818_ai_get_fifo_sample(dev, s, &chan);
+
+ if (pcl818_ai_dropout(dev, s, chan))
+ break;
+
+ comedi_buf_put(s, val);
+
+ if (!pcl818_ai_next_chan(dev, s))
+ break;
+ }
+}
+
+static irqreturn_t pcl818_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+
+ if (!dev->attached || !devpriv->ai_cmd_running) {
+ pcl818_ai_clear_eoc(dev);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->ai_cmd_canceled) {
+ /*
+ * 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.
+ */
+ devpriv->ai_act_scan = 0;
+ s->cancel(dev, s);
+ return IRQ_HANDLED;
+ }
+
+ if (devpriv->dma)
+ pcl818_handle_dma(dev, s);
+ else if (devpriv->usefifo)
+ pcl818_handle_fifo(dev, s);
+ else
+ pcl818_handle_eoc(dev, s);
+
+ pcl818_ai_clear_eoc(dev);
+
+ cfc_handle_events(dev, s);
+ return IRQ_HANDLED;
+}
+
+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++) {
+ /* 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 :-( */
+ dev_dbg(dev->class_dev,
+ "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;
+ }
+ /* well, this is next correct channel in list */
+ chansegment[i] = chanlist[i];
+ }
+
+ /* check whole chanlist */
+ for (i = 0, segpos = 0; i < n_chan; i++) {
+ if (chanlist[i] != chansegment[i % seglen]) {
+ dev_dbg(dev->class_dev,
+ "bad channel 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 int check_single_ended(unsigned int port)
+{
+ if (inb(port + PCL818_STATUS_REG) & PCL818_STATUS_MUX)
+ return 1;
+ return 0;
+}
+
+static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ const struct pcl818_board *board = comedi_board(dev);
+ struct pcl818_private *devpriv = dev->private;
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ board->ns_min);
+ else /* TRIG_EXT */
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ i8253_cascade_ns_to_timer(devpriv->i8253_osc_base,
+ &devpriv->divisor1,
+ &devpriv->divisor2,
+ &arg, cmd->flags);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ 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 pcl818_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int ctrl = 0;
+ unsigned int seglen;
+
+ if (devpriv->ai_cmd_running)
+ return -EBUSY;
+
+ pcl818_start_pacer(dev, false);
+
+ seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
+ if (seglen < 1)
+ return -EINVAL;
+ pcl818_ai_setup_chanlist(dev, cmd->chanlist, seglen);
+
+ devpriv->ai_data_len = s->async->prealloc_bufsz;
+ devpriv->ai_act_scan = cmd->stop_arg;
+ devpriv->ai_act_chan = 0;
+ devpriv->ai_cmd_running = 1;
+ devpriv->ai_cmd_canceled = 0;
+ devpriv->act_chanlist_pos = 0;
+ devpriv->dma_runs_to_end = 0;
+
+ if (cmd->convert_src == TRIG_TIMER)
+ ctrl |= PCL818_CTRL_PACER_TRIG;
+ else
+ ctrl |= PCL818_CTRL_EXT_TRIG;
+
+ outb(PCL818_CNTENABLE_PACER_ENA, dev->iobase + PCL818_CNTENABLE_REG);
+
+ if (devpriv->dma) {
+ pcl818_ai_setup_dma(dev, s);
+
+ ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq) |
+ PCL818_CTRL_DMAE;
+ } else if (devpriv->usefifo) {
+ /* enable FIFO */
+ outb(1, dev->iobase + PCL818_FI_ENABLE);
+ } else {
+ ctrl |= PCL818_CTRL_INTE | PCL818_CTRL_IRQ(dev->irq);
+ }
+ outb(ctrl, dev->iobase + PCL818_CTRL_REG);
+
+ if (cmd->convert_src == TRIG_TIMER)
+ pcl818_start_pacer(dev, true);
+
+ return 0;
+}
+
+static int pcl818_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcl818_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (!devpriv->ai_cmd_running)
+ return 0;
+
+ if (devpriv->dma) {
+ if (cmd->stop_src == TRIG_NONE ||
+ (cmd->stop_src == TRIG_COUNT && devpriv->ai_act_scan > 0)) {
+ if (!devpriv->ai_cmd_canceled) {
+ /*
+ * Wait for running dma transfer to end,
+ * do cleanup in interrupt.
+ */
+ devpriv->ai_cmd_canceled = 1;
+ return 0;
+ }
+ }
+ disable_dma(devpriv->dma);
+ }
+
+ outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG);
+ pcl818_start_pacer(dev, false);
+ pcl818_ai_clear_eoc(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);
+ }
+ devpriv->ai_cmd_running = 0;
+ devpriv->ai_cmd_canceled = 0;
+
+ return 0;
+}
+
+static int pcl818_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ outb(PCL818_CTRL_SOFT_TRIG, dev->iobase + PCL818_CTRL_REG);
+
+ pcl818_ai_set_chan_range(dev, chan, range);
+ pcl818_ai_set_chan_scan(dev, chan, chan);
+
+ for (i = 0; i < insn->n; i++) {
+ pcl818_ai_clear_eoc(dev);
+ pcl818_ai_soft_trig(dev);
+
+ ret = comedi_timeout(dev, s, insn, pcl818_ai_eoc, 0);
+ if (ret)
+ break;
+
+ data[i] = pcl818_ai_get_sample(dev, s, NULL);
+ }
+ pcl818_ai_clear_eoc(dev);
+
+ return ret ? ret : insn->n;
+}
+
+static int pcl818_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ devpriv->ao_readback[chan] = data[i];
+ outb((data[i] & 0x000f) << 4,
+ dev->iobase + PCL818_AO_LSB_REG(chan));
+ outb((data[i] & 0x0ff0) >> 4,
+ dev->iobase + PCL818_AO_MSB_REG(chan));
+ }
+
+ return insn->n;
+}
+
+static int pcl818_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcl818_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pcl818_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + PCL818_DO_DI_LSB_REG) |
+ (inb(dev->iobase + PCL818_DO_DI_MSB_REG) << 8);
+
+ return insn->n;
+}
+
+static int pcl818_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ outb(s->state & 0xff, dev->iobase + PCL818_DO_DI_LSB_REG);
+ outb((s->state >> 8), dev->iobase + PCL818_DO_DI_MSB_REG);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static void pcl818_reset(struct comedi_device *dev)
+{
+ const struct pcl818_board *board = comedi_board(dev);
+ unsigned long timer_base = dev->iobase + PCL818_TIMER_BASE;
+ unsigned int chan;
+
+ /* flush and disable the FIFO */
+ if (board->has_fifo) {
+ outb(0, dev->iobase + PCL818_FI_INTCLR);
+ outb(0, dev->iobase + PCL818_FI_FLUSH);
+ outb(0, dev->iobase + PCL818_FI_ENABLE);
+ }
+
+ /* disable analog input trigger */
+ outb(PCL818_CTRL_DISABLE_TRIG, dev->iobase + PCL818_CTRL_REG);
+ pcl818_ai_clear_eoc(dev);
+
+ pcl818_ai_set_chan_range(dev, 0, 0);
+
+ /* stop pacer */
+ outb(PCL818_CNTENABLE_PACER_ENA, dev->iobase + PCL818_CNTENABLE_REG);
+ i8254_set_mode(timer_base, 0, 2, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 1, I8254_MODE0 | I8254_BINARY);
+ i8254_set_mode(timer_base, 0, 0, I8254_MODE0 | I8254_BINARY);
+
+ /* set analog output channels to 0V */
+ for (chan = 0; chan < board->n_aochan; chan++) {
+ outb(0, dev->iobase + PCL818_AO_LSB_REG(chan));
+ outb(0, dev->iobase + PCL818_AO_MSB_REG(chan));
+ }
+
+ /* set all digital outputs low */
+ outb(0, dev->iobase + PCL818_DO_DI_MSB_REG);
+ outb(0, dev->iobase + PCL818_DO_DI_LSB_REG);
+}
+
+static void pcl818_set_ai_range_table(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_devconfig *it)
+{
+ const struct pcl818_board *board = comedi_board(dev);
+
+ /* default to the range table from the boardinfo */
+ s->range_table = board->ai_range_type;
+
+ /* now check the user config option based on the boardtype */
+ if (board->is_818) {
+ if (it->options[4] == 1 || it->options[4] == 10) {
+ /* secondary range list jumper selectable */
+ s->range_table = &range_pcl818l_h_ai;
+ }
+ } 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;
+ }
+ }
+}
+
+static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcl818_board *board = comedi_board(dev);
+ struct pcl818_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+ int i;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0],
+ board->has_fifo ? 0x20 : 0x10);
+ if (ret)
+ return ret;
+
+ /* we can use IRQ 2-7 for async command support */
+ if (it->options[1] >= 2 && it->options[1] <= 7) {
+ ret = request_irq(it->options[1], pcl818_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ /* should we use the FIFO? */
+ if (dev->irq && board->has_fifo && it->options[2] == -1)
+ devpriv->usefifo = 1;
+
+ /* we need an IRQ to do DMA on channel 3 or 1 */
+ if (dev->irq && board->has_dma &&
+ (it->options[2] == 3 || it->options[2] == 1)) {
+ ret = request_dma(it->options[2], dev->board_name);
+ if (ret) {
+ dev_err(dev->class_dev,
+ "unable to request DMA channel %d\n",
+ it->options[2]);
+ return -EBUSY;
+ }
+ devpriv->dma = it->options[2];
+
+ devpriv->dmapages = 2; /* we need 16KB */
+ devpriv->hwdmasize = (1 << devpriv->dmapages) * PAGE_SIZE;
+
+ for (i = 0; i < 2; i++) {
+ unsigned long dmabuf;
+
+ dmabuf = __get_dma_pages(GFP_KERNEL, devpriv->dmapages);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ devpriv->dmabuf[i] = dmabuf;
+ devpriv->hwdmaptr[i] = virt_to_bus((void *)dmabuf);
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE;
+ if (check_single_ended(dev->iobase)) {
+ s->n_chan = 16;
+ s->subdev_flags |= SDF_COMMON | SDF_GROUND;
+ } else {
+ s->n_chan = 8;
+ s->subdev_flags |= SDF_DIFF;
+ }
+ s->maxdata = 0x0fff;
+
+ pcl818_set_ai_range_table(dev, s, it);
+
+ s->insn_read = pcl818_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->do_cmdtest = ai_cmdtest;
+ s->do_cmd = pcl818_ai_cmd;
+ s->cancel = pcl818_ai_cancel;
+ }
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ if (board->n_aochan) {
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
+ s->n_chan = board->n_aochan;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_unipolar5;
+ s->insn_read = pcl818_ao_insn_read;
+ s->insn_write = pcl818_ao_insn_write;
+ if (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;
+ }
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ /* Digital Input subdevice */
+ s = &dev->subdevices[2];
+ 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 = pcl818_di_insn_bits;
+
+ /* Digital Output subdevice */
+ s = &dev->subdevices[3];
+ 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 = pcl818_do_insn_bits;
+
+ /* select 1/10MHz oscilator */
+ if ((it->options[3] == 0) || (it->options[3] == 10))
+ devpriv->i8253_osc_base = I8254_OSC_BASE_10MHZ;
+ else
+ devpriv->i8253_osc_base = I8254_OSC_BASE_1MHZ;
+
+ /* max sampling speed */
+ devpriv->ns_min = board->ns_min;
+
+ if (!board->is_818) {
+ if ((it->options[6] == 1) || (it->options[6] == 100))
+ devpriv->ns_min = 10000; /* extended PCL718 to 100kHz DAC */
+ }
+
+ pcl818_reset(dev);
+
+ return 0;
+}
+
+static void pcl818_detach(struct comedi_device *dev)
+{
+ struct pcl818_private *devpriv = dev->private;
+
+ if (devpriv) {
+ pcl818_ai_cancel(dev, dev->read_subdev);
+ pcl818_reset(dev);
+ if (devpriv->dma)
+ free_dma(devpriv->dma);
+ if (devpriv->dmabuf[0])
+ free_pages(devpriv->dmabuf[0], devpriv->dmapages);
+ if (devpriv->dmabuf[1])
+ free_pages(devpriv->dmabuf[1], devpriv->dmapages);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcl818_driver = {
+ .driver_name = "pcl818",
+ .module = THIS_MODULE,
+ .attach = pcl818_attach,
+ .detach = pcl818_detach,
+ .board_name = &boardtypes[0].name,
+ .num_names = ARRAY_SIZE(boardtypes),
+ .offset = sizeof(struct pcl818_board),
+};
+module_comedi_driver(pcl818_driver);
+
+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 00000000000..53e73737a90
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcm3724.c
@@ -0,0 +1,247 @@
+/*
+ comedi/drivers/pcm724.c
+
+ Drew Csillag <drew_csillag@yahoo.com>
+
+ 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 <drew_csillag@yahoo.com>
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#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
+
+/* used to track configured dios */
+struct priv_pcm3724 {
+ int dio_1;
+ int dio_2;
+};
+
+static int subdev_8255_cb(int dir, int port, int data, unsigned long arg)
+{
+ unsigned long iobase = arg;
+ unsigned char inbres;
+ if (dir) {
+ outb(data, iobase + port);
+ return 0;
+ } else {
+ inbres = inb(iobase + port);
+ 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)
+{
+ struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
+ struct comedi_subdevice *s_dio2 = &dev->subdevices[1];
+ 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, s_dio1);
+ buffer_config = compute_buffer(buffer_config, 1, s_dio2);
+
+ if (s == s_dio1)
+ 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 */
+
+ outb(config, port_8255_cfg);
+}
+
+static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s,
+ int chanspec)
+{
+ struct priv_pcm3724 *priv = dev->private;
+ struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
+ unsigned int mask;
+ int gatecfg;
+
+ gatecfg = 0;
+
+ mask = 1 << CR_CHAN(chanspec);
+ if (s == s_dio1)
+ priv->dio_1 |= mask;
+ else
+ 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;
+
+ 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 chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 8)
+ mask = 0x0000ff;
+ else if (chan < 16)
+ mask = 0x00ff00;
+ else if (chan < 20)
+ mask = 0x0f0000;
+ else
+ mask = 0xf00000;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ do_3724_config(dev, s, insn->chanspec);
+ enable_chan(dev, s, insn->chanspec);
+
+ return insn->n;
+}
+
+static int pcm3724_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct priv_pcm3724 *priv;
+ struct comedi_subdevice *s;
+ int ret, i;
+
+ priv = comedi_alloc_devpriv(dev, sizeof(*priv));
+ if (!priv)
+ return -ENOMEM;
+
+ ret = comedi_request_region(dev, it->options[0], PCM3724_SIZE);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 2);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ ret = subdev_8255_init(dev, s, subdev_8255_cb,
+ dev->iobase + SIZE_8255 * i);
+ if (ret)
+ return ret;
+ s->insn_config = subdev_3724_insn_config;
+ }
+ return 0;
+}
+
+static struct comedi_driver pcm3724_driver = {
+ .driver_name = "pcm3724",
+ .module = THIS_MODULE,
+ .attach = pcm3724_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcm3724_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmad.c b/drivers/staging/comedi/drivers/pcmad.c
new file mode 100644
index 00000000000..87c61d9b11d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmad.c
@@ -0,0 +1,159 @@
+/*
+ * 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 <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcmad
+ * Description: Winsystems PCM-A/D12, PCM-A/D16
+ * Devices: (Winsystems) PCM-A/D12 [pcmad12]
+ * (Winsystems) PCM-A/D16 [pcmad16]
+ * Author: ds
+ * 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] - IRQ (unused)
+ * [2] - Analog input reference (must match jumpers)
+ * 0 = single-ended (16 channels)
+ * 1 = differential (8 channels)
+ * [3] - Analog input encoding (must match jumpers)
+ * 0 = straight binary (0-5V input range)
+ * 1 = two's complement (+-10V input range)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+#define PCMAD_STATUS 0
+#define PCMAD_LSB 1
+#define PCMAD_MSB 2
+#define PCMAD_CONVERT 1
+
+struct pcmad_board_struct {
+ const char *name;
+ unsigned int ai_maxdata;
+};
+
+static const struct pcmad_board_struct pcmad_boards[] = {
+ {
+ .name = "pcmad12",
+ .ai_maxdata = 0x0fff,
+ }, {
+ .name = "pcmad16",
+ .ai_maxdata = 0xffff,
+ },
+};
+
+static int pcmad_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inb(dev->iobase + PCMAD_STATUS);
+ if ((status & 0x3) == 0x3)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmad_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(chan, dev->iobase + PCMAD_CONVERT);
+
+ ret = comedi_timeout(dev, s, insn, pcmad_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(dev->iobase + PCMAD_LSB) |
+ (inb(dev->iobase + PCMAD_MSB) << 8);
+
+ /* data is shifted on the pcmad12, fix it */
+ if (s->maxdata == 0x0fff)
+ val >>= 4;
+
+ if (comedi_range_is_bipolar(s, range)) {
+ /* munge the two's complement value */
+ val ^= ((s->maxdata + 1) >> 1);
+ }
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcmad_board_struct *board = comedi_board(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x04);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ if (it->options[1]) {
+ /* 8 differential channels */
+ s->subdev_flags = SDF_READABLE | AREF_DIFF;
+ s->n_chan = 8;
+ } else {
+ /* 16 single-ended channels */
+ s->subdev_flags = SDF_READABLE | AREF_GROUND;
+ s->n_chan = 16;
+ }
+ s->len_chanlist = 1;
+ s->maxdata = board->ai_maxdata;
+ s->range_table = it->options[2] ? &range_bipolar10 : &range_unipolar5;
+ s->insn_read = pcmad_ai_insn_read;
+
+ return 0;
+}
+
+static struct comedi_driver pcmad_driver = {
+ .driver_name = "pcmad",
+ .module = THIS_MODULE,
+ .attach = pcmad_attach,
+ .detach = comedi_legacy_detach,
+ .board_name = &pcmad_boards[0].name,
+ .num_names = ARRAY_SIZE(pcmad_boards),
+ .offset = sizeof(pcmad_boards[0]),
+};
+module_comedi_driver(pcmad_driver);
+
+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 00000000000..1c7a135c91d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmda12.c
@@ -0,0 +1,176 @@
+/*
+ * 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 <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcmda12
+ * Description: A driver for the Winsystems PCM-D/A-12
+ * Devices: (Winsystems) PCM-D/A-12 [pcmda12]
+ * Author: Calin Culianu <calin@ajvar.org>
+ * 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 <linux/module.h>
+#include "../comedidev.h"
+
+/* AI range is not configurable, it's set by jumpers on the board */
+static const struct comedi_lrange pcmda12_ranges = {
+ 3, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5)
+ }
+};
+
+struct pcmda12_private {
+ unsigned int ao_readback[8];
+ int simultaneous_xfer_mode;
+};
+
+static int pcmda12_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmda12_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ unsigned long ioreg = dev->iobase + (chan * 2);
+ int i;
+
+ for (i = 0; i < insn->n; ++i) {
+ val = data[i];
+ outb(val & 0xff, ioreg);
+ outb((val >> 8) & 0xff, ioreg + 1);
+
+ /*
+ * Initiate transfer if not in simultaneaous xfer
+ * mode by reading one of the AO registers.
+ */
+ if (!devpriv->simultaneous_xfer_mode)
+ inb(ioreg);
+ }
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int pcmda12_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmda12_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ /*
+ * Initiate simultaneaous xfer mode by reading one of the
+ * AO registers. All analog outputs will then be updated.
+ */
+ if (devpriv->simultaneous_xfer_mode)
+ inb(dev->iobase);
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static void pcmda12_ao_reset(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ int i;
+
+ for (i = 0; i < s->n_chan; ++i) {
+ outb(0, dev->iobase + (i * 2));
+ outb(0, dev->iobase + (i * 2) + 1);
+ }
+ /* Initiate transfer by reading one of the AO registers. */
+ inb(dev->iobase);
+}
+
+static int pcmda12_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct pcmda12_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x10);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->simultaneous_xfer_mode = it->options[1];
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 8;
+ s->maxdata = 0x0fff;
+ s->range_table = &pcmda12_ranges;
+ s->insn_write = pcmda12_ao_insn_write;
+ s->insn_read = pcmda12_ao_insn_read;
+
+ pcmda12_ao_reset(dev, s);
+
+ return 0;
+}
+
+static struct comedi_driver pcmda12_driver = {
+ .driver_name = "pcmda12",
+ .module = THIS_MODULE,
+ .attach = pcmda12_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcmda12_driver);
+
+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 00000000000..fed7e77e030
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmmio.c
@@ -0,0 +1,849 @@
+/*
+ * pcmmio.c
+ * Driver for Winsystems PC-104 based multifunction IO board.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2007 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcmmio
+ * Description: A driver for the PCM-MIO multifunction board
+ * Devices: (Winsystems) PCM-MIO [pcmmio]
+ * Author: Calin Culianu <calin@ajvar.org>
+ * Updated: Wed, May 16 2007 16:21:10 -0500
+ * Status: works
+ *
+ * A driver for the PCM-MIO multifunction board from Winsystems. This
+ * 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 analog
+ * input and output. The hardware itself can do streaming acquisition, etc.
+ *
+ * Asynchronous I/O for the DIO subdevices *is* implemented, however! They
+ * are basically edge-triggered interrupts for any configuration of the
+ * channels in subdevice 2.
+ *
+ * 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 <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/*
+ * Register I/O map
+ */
+#define PCMMIO_AI_LSB_REG 0x00
+#define PCMMIO_AI_MSB_REG 0x01
+#define PCMMIO_AI_CMD_REG 0x02
+#define PCMMIO_AI_CMD_SE (1 << 7)
+#define PCMMIO_AI_CMD_ODD_CHAN (1 << 6)
+#define PCMMIO_AI_CMD_CHAN_SEL(x) (((x) & 0x3) << 4)
+#define PCMMIO_AI_CMD_RANGE(x) (((x) & 0x3) << 2)
+#define PCMMIO_RESOURCE_REG 0x02
+#define PCMMIO_RESOURCE_IRQ(x) (((x) & 0xf) << 0)
+#define PCMMIO_AI_STATUS_REG 0x03
+#define PCMMIO_AI_STATUS_DATA_READY (1 << 7)
+#define PCMMIO_AI_STATUS_DATA_DMA_PEND (1 << 6)
+#define PCMMIO_AI_STATUS_CMD_DMA_PEND (1 << 5)
+#define PCMMIO_AI_STATUS_IRQ_PEND (1 << 4)
+#define PCMMIO_AI_STATUS_DATA_DRQ_ENA (1 << 2)
+#define PCMMIO_AI_STATUS_REG_SEL (1 << 3)
+#define PCMMIO_AI_STATUS_CMD_DRQ_ENA (1 << 1)
+#define PCMMIO_AI_STATUS_IRQ_ENA (1 << 0)
+#define PCMMIO_AI_RES_ENA_REG 0x03
+#define PCMMIO_AI_RES_ENA_CMD_REG_ACCESS (0 << 3)
+#define PCMMIO_AI_RES_ENA_AI_RES_ACCESS (1 << 3)
+#define PCMMIO_AI_RES_ENA_DIO_RES_ACCESS (1 << 4)
+#define PCMMIO_AI_2ND_ADC_OFFSET 0x04
+
+#define PCMMIO_AO_LSB_REG 0x08
+#define PCMMIO_AO_LSB_SPAN(x) (((x) & 0xf) << 0)
+#define PCMMIO_AO_MSB_REG 0x09
+#define PCMMIO_AO_CMD_REG 0x0a
+#define PCMMIO_AO_CMD_WR_SPAN (0x2 << 4)
+#define PCMMIO_AO_CMD_WR_CODE (0x3 << 4)
+#define PCMMIO_AO_CMD_UPDATE (0x4 << 4)
+#define PCMMIO_AO_CMD_UPDATE_ALL (0x5 << 4)
+#define PCMMIO_AO_CMD_WR_SPAN_UPDATE (0x6 << 4)
+#define PCMMIO_AO_CMD_WR_CODE_UPDATE (0x7 << 4)
+#define PCMMIO_AO_CMD_WR_SPAN_UPDATE_ALL (0x8 << 4)
+#define PCMMIO_AO_CMD_WR_CODE_UPDATE_ALL (0x9 << 4)
+#define PCMMIO_AO_CMD_RD_B1_SPAN (0xa << 4)
+#define PCMMIO_AO_CMD_RD_B1_CODE (0xb << 4)
+#define PCMMIO_AO_CMD_RD_B2_SPAN (0xc << 4)
+#define PCMMIO_AO_CMD_RD_B2_CODE (0xd << 4)
+#define PCMMIO_AO_CMD_NOP (0xf << 4)
+#define PCMMIO_AO_CMD_CHAN_SEL(x) (((x) & 0x03) << 1)
+#define PCMMIO_AO_CMD_CHAN_SEL_ALL (0x0f << 0)
+#define PCMMIO_AO_STATUS_REG 0x0b
+#define PCMMIO_AO_STATUS_DATA_READY (1 << 7)
+#define PCMMIO_AO_STATUS_DATA_DMA_PEND (1 << 6)
+#define PCMMIO_AO_STATUS_CMD_DMA_PEND (1 << 5)
+#define PCMMIO_AO_STATUS_IRQ_PEND (1 << 4)
+#define PCMMIO_AO_STATUS_DATA_DRQ_ENA (1 << 2)
+#define PCMMIO_AO_STATUS_REG_SEL (1 << 3)
+#define PCMMIO_AO_STATUS_CMD_DRQ_ENA (1 << 1)
+#define PCMMIO_AO_STATUS_IRQ_ENA (1 << 0)
+#define PCMMIO_AO_RESOURCE_ENA_REG 0x0b
+#define PCMMIO_AO_2ND_DAC_OFFSET 0x04
+
+/*
+ * WinSystems WS16C48
+ *
+ * Offset Page 0 Page 1 Page 2 Page 3
+ * ------ ----------- ----------- ----------- -----------
+ * 0x10 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
+ * 0x11 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
+ * 0x12 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
+ * 0x13 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
+ * 0x14 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
+ * 0x15 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
+ * 0x16 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
+ * 0x17 Page/Lock Page/Lock Page/Lock Page/Lock
+ * 0x18 N/A POL_0 ENAB_0 INT_ID0
+ * 0x19 N/A POL_1 ENAB_1 INT_ID1
+ * 0x1a N/A POL_2 ENAB_2 INT_ID2
+ */
+#define PCMMIO_PORT_REG(x) (0x10 + (x))
+#define PCMMIO_INT_PENDING_REG 0x16
+#define PCMMIO_PAGE_LOCK_REG 0x17
+#define PCMMIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
+#define PCMMIO_PAGE(x) (((x) & 0x3) << 6)
+#define PCMMIO_PAGE_MASK PCMUIO_PAGE(3)
+#define PCMMIO_PAGE_POL 1
+#define PCMMIO_PAGE_ENAB 2
+#define PCMMIO_PAGE_INT_ID 3
+#define PCMMIO_PAGE_REG(x) (0x18 + (x))
+
+static const struct comedi_lrange pcmmio_ai_ranges = {
+ 4, {
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+static const struct comedi_lrange pcmmio_ao_ranges = {
+ 6, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ BIP_RANGE(2.5),
+ RANGE(-2.5, 7.5)
+ }
+};
+
+struct pcmmio_private {
+ spinlock_t pagelock; /* protects the page registers */
+ spinlock_t spinlock; /* protects the member variables */
+ unsigned int enabled_mask;
+ unsigned int stop_count;
+ unsigned int active:1;
+
+ unsigned int ao_readback[8];
+};
+
+static void pcmmio_dio_write(struct comedi_device *dev, unsigned int val,
+ int page, int port)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long iobase = dev->iobase;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ outb(val & 0xff, iobase + PCMMIO_PORT_REG(port + 0));
+ outb((val >> 8) & 0xff, iobase + PCMMIO_PORT_REG(port + 1));
+ outb((val >> 16) & 0xff, iobase + PCMMIO_PORT_REG(port + 2));
+ } else {
+ outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
+ outb(val & 0xff, iobase + PCMMIO_PAGE_REG(0));
+ outb((val >> 8) & 0xff, iobase + PCMMIO_PAGE_REG(1));
+ outb((val >> 16) & 0xff, iobase + PCMMIO_PAGE_REG(2));
+ }
+ spin_unlock_irqrestore(&devpriv->pagelock, flags);
+}
+
+static unsigned int pcmmio_dio_read(struct comedi_device *dev,
+ int page, int port)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long iobase = dev->iobase;
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&devpriv->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ val = inb(iobase + PCMMIO_PORT_REG(port + 0));
+ val |= (inb(iobase + PCMMIO_PORT_REG(port + 1)) << 8);
+ val |= (inb(iobase + PCMMIO_PORT_REG(port + 2)) << 16);
+ } else {
+ outb(PCMMIO_PAGE(page), iobase + PCMMIO_PAGE_LOCK_REG);
+ val = inb(iobase + PCMMIO_PAGE_REG(0));
+ val |= (inb(iobase + PCMMIO_PAGE_REG(1)) << 8);
+ val |= (inb(iobase + PCMMIO_PAGE_REG(2)) << 16);
+ }
+ spin_unlock_irqrestore(&devpriv->pagelock, flags);
+
+ return val;
+}
+
+/*
+ * Each channel can be individually programmed for input or output.
+ * Writing a '0' to a channel causes the corresponding output pin
+ * to go to a high-z state (pulled high by an external 10K resistor).
+ * This allows it to be used as an input. When used in the input mode,
+ * a read reflects the inverted state of the I/O pin, such that a
+ * high on the pin will read as a '0' in the register. Writing a '1'
+ * to a bit position causes the pin to sink current (up to 12mA),
+ * effectively pulling it low.
+ */
+static int pcmmio_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
+ int port = s->index == 2 ? 0 : 3;
+ unsigned int chanmask = (1 << s->n_chan) - 1;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /*
+ * Outputs are inverted, invert the state and
+ * update the channels.
+ *
+ * The s->io_bits mask makes sure the input channels
+ * are '0' so that the outputs pins stay in a high
+ * z-state.
+ */
+ val = ~s->state & chanmask;
+ val &= s->io_bits;
+ pcmmio_dio_write(dev, val, 0, port);
+ }
+
+ /* get inverted state of the channels from the port */
+ val = pcmmio_dio_read(dev, 0, port);
+
+ /* return the true state of the channels */
+ data[1] = ~val & chanmask;
+
+ return insn->n;
+}
+
+static int pcmmio_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ /* subdevice 2 uses ports 0-2, subdevice 3 uses ports 3-5 */
+ int port = s->index == 2 ? 0 : 3;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (data[0] == INSN_CONFIG_DIO_INPUT)
+ pcmmio_dio_write(dev, s->io_bits, 0, port);
+
+ return insn->n;
+}
+
+static void pcmmio_reset(struct comedi_device *dev)
+{
+ /* Clear all the DIO port bits */
+ pcmmio_dio_write(dev, 0, 0, 0);
+ pcmmio_dio_write(dev, 0, 0, 3);
+
+ /* Clear all the paged registers */
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_POL, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0);
+}
+
+/* devpriv->spinlock is already locked */
+static void pcmmio_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+
+ devpriv->enabled_mask = 0;
+ devpriv->active = 0;
+ s->async->inttrig = NULL;
+
+ /* disable all dio interrupts */
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_ENAB, 0);
+}
+
+static void pcmmio_handle_dio_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int triggered)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int oldevents = s->async->events;
+ unsigned int val = 0;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+
+ if (!devpriv->active)
+ goto done;
+
+ if (!(triggered & devpriv->enabled_mask))
+ goto done;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ if (triggered & (1 << chan))
+ val |= (1 << i);
+ }
+
+ /* Write the scan to the buffer. */
+ if (comedi_buf_put(s, val) &&
+ comedi_buf_put(s, val >> 16)) {
+ 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 (cmd->stop_src == TRIG_COUNT && devpriv->stop_count > 0) {
+ devpriv->stop_count--;
+ if (devpriv->stop_count == 0) {
+ s->async->events |= COMEDI_CB_EOA;
+ /* TODO: STOP_ACQUISITION_CALL_HERE!! */
+ pcmmio_stop_intr(dev, s);
+ }
+ }
+
+done:
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ if (oldevents != s->async->events)
+ comedi_event(dev, s);
+}
+
+static irqreturn_t interrupt_pcmmio(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ unsigned int triggered;
+ unsigned char int_pend;
+
+ /* are there any interrupts pending */
+ int_pend = inb(dev->iobase + PCMMIO_INT_PENDING_REG) & 0x07;
+ if (!int_pend)
+ return IRQ_NONE;
+
+ /* get, and clear, the pending interrupts */
+ triggered = pcmmio_dio_read(dev, PCMMIO_PAGE_INT_ID, 0);
+ pcmmio_dio_write(dev, 0, PCMMIO_PAGE_INT_ID, 0);
+
+ pcmmio_handle_dio_intr(dev, s, triggered);
+
+ return IRQ_HANDLED;
+}
+
+/* devpriv->spinlock is already locked */
+static int pcmmio_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int bits = 0;
+ unsigned int pol_bits = 0;
+ int i;
+
+ if (cmd->stop_src == TRIG_COUNT && devpriv->stop_count == 0) {
+ /* An empty acquisition! */
+ s->async->events |= COMEDI_CB_EOA;
+ devpriv->active = 0;
+ return 1;
+ }
+
+ devpriv->enabled_mask = 0;
+ devpriv->active = 1;
+ if (cmd->chanlist) {
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+
+ bits |= (1 << chan);
+ pol_bits |= (((aref || range) ? 1 : 0) << chan);
+ }
+ }
+ bits &= ((1 << s->n_chan) - 1);
+ devpriv->enabled_mask = bits;
+
+ /* set polarity and enable interrupts */
+ pcmmio_dio_write(dev, pol_bits, PCMMIO_PAGE_POL, 0);
+ pcmmio_dio_write(dev, bits, PCMMIO_PAGE_ENAB, 0);
+
+ return 0;
+}
+
+static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long flags;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ if (devpriv->active)
+ pcmmio_stop_intr(dev, s);
+ spin_unlock_irqrestore(&devpriv->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmmio_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+ int event = 0;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ s->async->inttrig = NULL;
+ if (devpriv->active)
+ event = pcmmio_start_intr(dev, s);
+ spin_unlock_irqrestore(&devpriv->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 pcmmio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+ int event = 0;
+
+ spin_lock_irqsave(&devpriv->spinlock, flags);
+ devpriv->active = 1;
+
+ /* Set up end of acquisition. */
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->stop_count = cmd->stop_arg;
+ else /* TRIG_NONE */
+ devpriv->stop_count = 0;
+
+ /* Set up start of acquisition. */
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pcmmio_inttrig_start_intr;
+ else /* TRIG_NOW */
+ event = pcmmio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&devpriv->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)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* any count allowed */
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ /* if (err) return 4; */
+
+ return 0;
+}
+
+static int pcmmio_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + PCMMIO_AI_STATUS_REG);
+ if (status & PCMMIO_AI_STATUS_DATA_READY)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmmio_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long iobase = dev->iobase;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int aref = CR_AREF(insn->chanspec);
+ unsigned char cmd = 0;
+ unsigned int val;
+ int ret;
+ int i;
+
+ /*
+ * The PCM-MIO uses two Linear Tech LTC1859CG 8-channel A/D converters.
+ * The devices use a full duplex serial interface which transmits and
+ * receives data simultaneously. An 8-bit command is shifted into the
+ * ADC interface to configure it for the next conversion. At the same
+ * time, the data from the previous conversion is shifted out of the
+ * device. Consequently, the conversion result is delayed by one
+ * conversion from the command word.
+ *
+ * Setup the cmd for the conversions then do a dummy conversion to
+ * flush the junk data. Then do each conversion requested by the
+ * comedi_insn. Note that the last conversion will leave junk data
+ * in ADC which will get flushed on the next comedi_insn.
+ */
+
+ if (chan > 7) {
+ chan -= 8;
+ iobase += PCMMIO_AI_2ND_ADC_OFFSET;
+ }
+
+ if (aref == AREF_GROUND)
+ cmd |= PCMMIO_AI_CMD_SE;
+ if (chan % 2)
+ cmd |= PCMMIO_AI_CMD_ODD_CHAN;
+ cmd |= PCMMIO_AI_CMD_CHAN_SEL(chan / 2);
+ cmd |= PCMMIO_AI_CMD_RANGE(range);
+
+ outb(cmd, iobase + PCMMIO_AI_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(iobase + PCMMIO_AI_LSB_REG);
+ val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8;
+
+ for (i = 0; i < insn->n; i++) {
+ outb(cmd, iobase + PCMMIO_AI_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ val = inb(iobase + PCMMIO_AI_LSB_REG);
+ val |= inb(iobase + PCMMIO_AI_MSB_REG) << 8;
+
+ /* bipolar data is two's complement */
+ if (comedi_range_is_bipolar(s, range))
+ val = comedi_offset_munge(s, val);
+
+ data[i] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmmio_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int pcmmio_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + PCMMIO_AO_STATUS_REG);
+ if (status & PCMMIO_AO_STATUS_DATA_READY)
+ return 0;
+ return -EBUSY;
+}
+
+static int pcmmio_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct pcmmio_private *devpriv = dev->private;
+ unsigned long iobase = dev->iobase;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ unsigned char cmd = 0;
+ int ret;
+ int i;
+
+ /*
+ * The PCM-MIO has two Linear Tech LTC2704 DAC devices. Each device
+ * is a 4-channel converter with software-selectable output range.
+ */
+
+ if (chan > 3) {
+ cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan - 4);
+ iobase += PCMMIO_AO_2ND_DAC_OFFSET;
+ } else {
+ cmd |= PCMMIO_AO_CMD_CHAN_SEL(chan);
+ }
+
+ /* set the range for the channel */
+ outb(PCMMIO_AO_LSB_SPAN(range), iobase + PCMMIO_AO_LSB_REG);
+ outb(0, iobase + PCMMIO_AO_MSB_REG);
+ outb(cmd | PCMMIO_AO_CMD_WR_SPAN_UPDATE, iobase + PCMMIO_AO_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* write the data to the channel */
+ outb(val & 0xff, iobase + PCMMIO_AO_LSB_REG);
+ outb((val >> 8) & 0xff, iobase + PCMMIO_AO_MSB_REG);
+ outb(cmd | PCMMIO_AO_CMD_WR_CODE_UPDATE,
+ iobase + PCMMIO_AO_CMD_REG);
+
+ ret = comedi_timeout(dev, s, insn, pcmmio_ao_eoc, 0);
+ if (ret)
+ return ret;
+
+ devpriv->ao_readback[chan] = val;
+ }
+
+ return insn->n;
+}
+
+static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct pcmmio_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 32);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ spin_lock_init(&devpriv->pagelock);
+ spin_lock_init(&devpriv->spinlock);
+
+ pcmmio_reset(dev);
+
+ if (it->options[1]) {
+ ret = request_irq(it->options[1], interrupt_pcmmio, 0,
+ dev->board_name, dev);
+ if (ret == 0) {
+ dev->irq = it->options[1];
+
+ /* configure the interrupt routing on the board */
+ outb(PCMMIO_AI_RES_ENA_DIO_RES_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG);
+ outb(PCMMIO_RESOURCE_IRQ(dev->irq),
+ dev->iobase + PCMMIO_RESOURCE_REG);
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ /* Analog Input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0xffff;
+ s->range_table = &pcmmio_ai_ranges;
+ s->insn_read = pcmmio_ai_insn_read;
+
+ /* initialize the resource enable register by clearing it */
+ outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG);
+ outb(PCMMIO_AI_RES_ENA_CMD_REG_ACCESS,
+ dev->iobase + PCMMIO_AI_RES_ENA_REG + PCMMIO_AI_2ND_ADC_OFFSET);
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 8;
+ s->maxdata = 0xffff;
+ s->range_table = &pcmmio_ao_ranges;
+ s->insn_read = pcmmio_ao_insn_read;
+ s->insn_write = pcmmio_ao_insn_write;
+
+ /* initialize the resource enable register by clearing it */
+ outb(0, dev->iobase + PCMMIO_AO_RESOURCE_ENA_REG);
+ outb(0, dev->iobase + PCMMIO_AO_2ND_DAC_OFFSET +
+ PCMMIO_AO_RESOURCE_ENA_REG);
+
+ /* Digital I/O subdevice with interrupt support */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->len_chanlist = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmmio_dio_insn_bits;
+ s->insn_config = pcmmio_dio_insn_config;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->cancel = pcmmio_cancel;
+ s->do_cmd = pcmmio_cmd;
+ s->do_cmdtest = pcmmio_cmdtest;
+ }
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmmio_dio_insn_bits;
+ s->insn_config = pcmmio_dio_insn_config;
+
+ return 0;
+}
+
+static struct comedi_driver pcmmio_driver = {
+ .driver_name = "pcmmio",
+ .module = THIS_MODULE,
+ .attach = pcmmio_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(pcmmio_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Winsystems PCM-MIO PC/104 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/pcmuio.c b/drivers/staging/comedi/drivers/pcmuio.c
new file mode 100644
index 00000000000..62914bb342d
--- /dev/null
+++ b/drivers/staging/comedi/drivers/pcmuio.c
@@ -0,0 +1,690 @@
+/*
+ * pcmuio.c
+ * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards.
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: pcmuio
+ * Description: Winsystems PC-104 based 48/96-channel DIO boards.
+ * Devices: (Winsystems) PCM-UIO48A [pcmuio48]
+ * (Winsystems) PCM-UIO96A [pcmuio96]
+ * Author: Calin Culianu <calin@ajvar.org>
+ * 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
+ * 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 following 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 <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/*
+ * Register I/O map
+ *
+ * Offset Page 0 Page 1 Page 2 Page 3
+ * ------ ----------- ----------- ----------- -----------
+ * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
+ * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
+ * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
+ * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
+ * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
+ * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
+ * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
+ * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock
+ * 0x08 N/A POL_0 ENAB_0 INT_ID0
+ * 0x09 N/A POL_1 ENAB_1 INT_ID1
+ * 0x0a N/A POL_2 ENAB_2 INT_ID2
+ */
+#define PCMUIO_PORT_REG(x) (0x00 + (x))
+#define PCMUIO_INT_PENDING_REG 0x06
+#define PCMUIO_PAGE_LOCK_REG 0x07
+#define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
+#define PCMUIO_PAGE(x) (((x) & 0x3) << 6)
+#define PCMUIO_PAGE_MASK PCMUIO_PAGE(3)
+#define PCMUIO_PAGE_POL 1
+#define PCMUIO_PAGE_ENAB 2
+#define PCMUIO_PAGE_INT_ID 3
+#define PCMUIO_PAGE_REG(x) (0x08 + (x))
+
+#define PCMUIO_ASIC_IOSIZE 0x10
+#define PCMUIO_MAX_ASICS 2
+
+struct pcmuio_board {
+ const char *name;
+ const int num_asics;
+};
+
+static const struct pcmuio_board pcmuio_boards[] = {
+ {
+ .name = "pcmuio48",
+ .num_asics = 1,
+ }, {
+ .name = "pcmuio96",
+ .num_asics = 2,
+ },
+};
+
+struct pcmuio_asic {
+ spinlock_t pagelock; /* protects the page registers */
+ spinlock_t spinlock; /* protects member variables */
+ unsigned int enabled_mask;
+ unsigned int stop_count;
+ unsigned int active:1;
+ unsigned int continuous:1;
+};
+
+struct pcmuio_private {
+ struct pcmuio_asic asics[PCMUIO_MAX_ASICS];
+ unsigned int irq2;
+};
+
+static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev,
+ int asic)
+{
+ return dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
+}
+
+static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s)
+{
+ /*
+ * subdevice 0 and 1 are handled by the first asic
+ * subdevice 2 and 3 are handled by the second asic
+ */
+ return s->index / 2;
+}
+
+static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s)
+{
+ /*
+ * subdevice 0 and 2 use port registers 0-2
+ * subdevice 1 and 3 use port registers 3-5
+ */
+ return (s->index % 2) ? 3 : 0;
+}
+
+static void pcmuio_write(struct comedi_device *dev, unsigned int val,
+ int asic, int page, int port)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0));
+ outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1));
+ outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2));
+ } else {
+ outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
+ outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0));
+ outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1));
+ outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2));
+ }
+ spin_unlock_irqrestore(&chip->pagelock, flags);
+}
+
+static unsigned int pcmuio_read(struct comedi_device *dev,
+ int asic, int page, int port)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&chip->pagelock, flags);
+ if (page == 0) {
+ /* Port registers are valid for any page */
+ val = inb(iobase + PCMUIO_PORT_REG(port + 0));
+ val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8);
+ val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16);
+ } else {
+ outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
+ val = inb(iobase + PCMUIO_PAGE_REG(0));
+ val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8);
+ val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16);
+ }
+ spin_unlock_irqrestore(&chip->pagelock, flags);
+
+ return val;
+}
+
+/*
+ * Each channel can be individually programmed for input or output.
+ * Writing a '0' to a channel causes the corresponding output pin
+ * to go to a high-z state (pulled high by an external 10K resistor).
+ * This allows it to be used as an input. When used in the input mode,
+ * a read reflects the inverted state of the I/O pin, such that a
+ * high on the pin will read as a '0' in the register. Writing a '1'
+ * to a bit position causes the pin to sink current (up to 12mA),
+ * effectively pulling it low.
+ */
+static int pcmuio_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int asic = pcmuio_subdevice_to_asic(s);
+ int port = pcmuio_subdevice_to_port(s);
+ unsigned int chanmask = (1 << s->n_chan) - 1;
+ unsigned int mask;
+ unsigned int val;
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ /*
+ * Outputs are inverted, invert the state and
+ * update the channels.
+ *
+ * The s->io_bits mask makes sure the input channels
+ * are '0' so that the outputs pins stay in a high
+ * z-state.
+ */
+ val = ~s->state & chanmask;
+ val &= s->io_bits;
+ pcmuio_write(dev, val, asic, 0, port);
+ }
+
+ /* get inverted state of the channels from the port */
+ val = pcmuio_read(dev, asic, 0, port);
+
+ /* return the true state of the channels */
+ data[1] = ~val & chanmask;
+
+ return insn->n;
+}
+
+static int pcmuio_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int asic = pcmuio_subdevice_to_asic(s);
+ int port = pcmuio_subdevice_to_port(s);
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (data[0] == INSN_CONFIG_DIO_INPUT)
+ pcmuio_write(dev, s->io_bits, asic, 0, port);
+
+ return insn->n;
+}
+
+static void pcmuio_reset(struct comedi_device *dev)
+{
+ const struct pcmuio_board *board = comedi_board(dev);
+ int asic;
+
+ for (asic = 0; asic < board->num_asics; ++asic) {
+ /* first, clear all the DIO port bits */
+ pcmuio_write(dev, 0, asic, 0, 0);
+ pcmuio_write(dev, 0, asic, 0, 3);
+
+ /* Next, clear all the paged registers for each page */
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
+ }
+}
+
+/* chip->spinlock is already locked */
+static void pcmuio_stop_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+
+ chip->enabled_mask = 0;
+ chip->active = 0;
+ s->async->inttrig = NULL;
+
+ /* disable all intrs for this subdev.. */
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
+}
+
+static void pcmuio_handle_intr_subdev(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned triggered)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned oldevents = s->async->events;
+ unsigned int val = 0;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+
+ if (!chip->active)
+ goto done;
+
+ if (!(triggered & chip->enabled_mask))
+ goto done;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ if (triggered & (1 << chan))
+ val |= (1 << i);
+ }
+
+ /* Write the scan to the buffer. */
+ if (comedi_buf_put(s, val) &&
+ comedi_buf_put(s, val >> 16)) {
+ 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 (!chip->continuous) {
+ /* stop_src == TRIG_COUNT */
+ if (chip->stop_count > 0) {
+ chip->stop_count--;
+ if (chip->stop_count == 0) {
+ s->async->events |= COMEDI_CB_EOA;
+ /* TODO: STOP_ACQUISITION_CALL_HERE!! */
+ pcmuio_stop_intr(dev, s);
+ }
+ }
+ }
+
+done:
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ if (oldevents != s->async->events)
+ comedi_event(dev, s);
+}
+
+static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic)
+{
+ /* there are could be two asics so we can't use dev->read_subdev */
+ struct comedi_subdevice *s = &dev->subdevices[asic * 2];
+ unsigned long iobase = pcmuio_asic_iobase(dev, asic);
+ unsigned int val;
+
+ /* are there any interrupts pending */
+ val = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07;
+ if (!val)
+ return 0;
+
+ /* get, and clear, the pending interrupts */
+ val = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0);
+ pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
+
+ /* handle the pending interrupts */
+ pcmuio_handle_intr_subdev(dev, s, val);
+
+ return 1;
+}
+
+static irqreturn_t pcmuio_interrupt(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct pcmuio_private *devpriv = dev->private;
+ int handled = 0;
+
+ if (irq == dev->irq)
+ handled += pcmuio_handle_asic_interrupt(dev, 0);
+ if (irq == devpriv->irq2)
+ handled += pcmuio_handle_asic_interrupt(dev, 1);
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/* chip->spinlock is already locked */
+static int pcmuio_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int bits = 0;
+ unsigned int pol_bits = 0;
+ int i;
+
+ if (!chip->continuous && chip->stop_count == 0) {
+ /* An empty acquisition! */
+ s->async->events |= COMEDI_CB_EOA;
+ chip->active = 0;
+ return 1;
+ }
+
+ chip->enabled_mask = 0;
+ chip->active = 1;
+ if (cmd->chanlist) {
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chanspec = cmd->chanlist[i];
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+
+ bits |= (1 << chan);
+ pol_bits |= ((aref || range) ? 1 : 0) << chan;
+ }
+ }
+ bits &= ((1 << s->n_chan) - 1);
+ chip->enabled_mask = bits;
+
+ /* set pol and enab intrs for this subdev.. */
+ pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0);
+ pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0);
+
+ return 0;
+}
+
+static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ if (chip->active)
+ pcmuio_stop_intr(dev, s);
+ spin_unlock_irqrestore(&chip->spinlock, flags);
+
+ return 0;
+}
+
+static int pcmuio_inttrig_start_intr(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct pcmuio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+ int event = 0;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ s->async->inttrig = NULL;
+ if (chip->active)
+ event = pcmuio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&chip->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 pcmuio_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int asic = pcmuio_subdevice_to_asic(s);
+ struct pcmuio_asic *chip = &devpriv->asics[asic];
+ unsigned long flags;
+ int event = 0;
+
+ spin_lock_irqsave(&chip->spinlock, flags);
+ chip->active = 1;
+
+ /* Set up end of acquisition. */
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ chip->continuous = 0;
+ chip->stop_count = cmd->stop_arg;
+ break;
+ default:
+ /* TRIG_NONE */
+ chip->continuous = 1;
+ chip->stop_count = 0;
+ break;
+ }
+
+ /* Set up start of acquisition. */
+ if (cmd->start_src == TRIG_INT)
+ s->async->inttrig = pcmuio_inttrig_start_intr;
+ else /* TRIG_NOW */
+ event = pcmuio_start_intr(dev, s);
+
+ spin_unlock_irqrestore(&chip->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)
+{
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* any count allowed */
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ break;
+ default:
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ /* if (err) return 4; */
+
+ return 0;
+}
+
+static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct pcmuio_board *board = comedi_board(dev);
+ struct comedi_subdevice *s;
+ struct pcmuio_private *devpriv;
+ int ret;
+ int i;
+
+ ret = comedi_request_region(dev, it->options[0],
+ board->num_asics * PCMUIO_ASIC_IOSIZE);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ for (i = 0; i < PCMUIO_MAX_ASICS; ++i) {
+ struct pcmuio_asic *chip = &devpriv->asics[i];
+
+ spin_lock_init(&chip->pagelock);
+ spin_lock_init(&chip->spinlock);
+ }
+
+ pcmuio_reset(dev);
+
+ if (it->options[1]) {
+ /* request the irq for the 1st asic */
+ ret = request_irq(it->options[1], pcmuio_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = it->options[1];
+ }
+
+ if (board->num_asics == 2) {
+ if (it->options[2] == dev->irq) {
+ /* the same irq (or none) is used by both asics */
+ devpriv->irq2 = it->options[2];
+ } else if (it->options[2]) {
+ /* request the irq for the 2nd asic */
+ ret = request_irq(it->options[2], pcmuio_interrupt, 0,
+ dev->board_name, dev);
+ if (ret == 0)
+ devpriv->irq2 = it->options[2];
+ }
+ }
+
+ ret = comedi_alloc_subdevices(dev, board->num_asics * 2);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dev->n_subdevices; ++i) {
+ s = &dev->subdevices[i];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = pcmuio_dio_insn_bits;
+ s->insn_config = pcmuio_dio_insn_config;
+
+ /* subdevices 0 and 2 can suppport interrupts */
+ if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) {
+ /* setup the interrupt subdevice */
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->len_chanlist = s->n_chan;
+ s->cancel = pcmuio_cancel;
+ s->do_cmd = pcmuio_cmd;
+ s->do_cmdtest = pcmuio_cmdtest;
+ }
+ }
+
+ return 0;
+}
+
+static void pcmuio_detach(struct comedi_device *dev)
+{
+ struct pcmuio_private *devpriv = dev->private;
+
+ if (devpriv) {
+ pcmuio_reset(dev);
+
+ /* free the 2nd irq if used, the core will free the 1st one */
+ if (devpriv->irq2 && devpriv->irq2 != dev->irq)
+ free_irq(devpriv->irq2, dev);
+ }
+ comedi_legacy_detach(dev);
+}
+
+static struct comedi_driver pcmuio_driver = {
+ .driver_name = "pcmuio",
+ .module = THIS_MODULE,
+ .attach = pcmuio_attach,
+ .detach = pcmuio_detach,
+ .board_name = &pcmuio_boards[0].name,
+ .offset = sizeof(struct pcmuio_board),
+ .num_names = ARRAY_SIZE(pcmuio_boards),
+};
+module_comedi_driver(pcmuio_driver);
+
+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 00000000000..fbcf2506980
--- /dev/null
+++ b/drivers/staging/comedi/drivers/plx9052.h
@@ -0,0 +1,79 @@
+/*
+ comedi/drivers/plx9052.h
+ Definitions for the PLX-9052 PCI interface chip
+
+ Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+
+#ifndef _PLX9052_H_
+#define _PLX9052_H_
+
+/*
+ * INTCSR - Interrupt Control/Status register
+ */
+#define PLX9052_INTCSR 0x4c
+#define PLX9052_INTCSR_LI1ENAB (1 << 0) /* LI1 enabled */
+#define PLX9052_INTCSR_LI1POL (1 << 1) /* LI1 active high */
+#define PLX9052_INTCSR_LI1STAT (1 << 2) /* LI1 active */
+#define PLX9052_INTCSR_LI2ENAB (1 << 3) /* LI2 enabled */
+#define PLX9052_INTCSR_LI2POL (1 << 4) /* LI2 active high */
+#define PLX9052_INTCSR_LI2STAT (1 << 5) /* LI2 active */
+#define PLX9052_INTCSR_PCIENAB (1 << 6) /* PCIINT enabled */
+#define PLX9052_INTCSR_SOFTINT (1 << 7) /* generate soft int */
+#define PLX9052_INTCSR_LI1SEL (1 << 8) /* LI1 edge */
+#define PLX9052_INTCSR_LI2SEL (1 << 9) /* LI2 edge */
+#define PLX9052_INTCSR_LI1CLRINT (1 << 10) /* LI1 clear int */
+#define PLX9052_INTCSR_LI2CLRINT (1 << 11) /* LI2 clear int */
+#define PLX9052_INTCSR_ISAMODE (1 << 12) /* ISA interface mode */
+
+/*
+ * CNTRL - User I/O, Direct Slave Response, Serial EEPROM, and
+ * Initialization Control register
+ */
+#define PLX9052_CNTRL 0x50
+#define PLX9052_CNTRL_WAITO (1 << 0) /* UIO0 or WAITO# select */
+#define PLX9052_CNTRL_UIO0_DIR (1 << 1) /* UIO0 direction */
+#define PLX9052_CNTRL_UIO0_DATA (1 << 2) /* UIO0 data */
+#define PLX9052_CNTRL_LLOCKO (1 << 3) /* UIO1 or LLOCKo# select */
+#define PLX9052_CNTRL_UIO1_DIR (1 << 4) /* UIO1 direction */
+#define PLX9052_CNTRL_UIO1_DATA (1 << 5) /* UIO1 data */
+#define PLX9052_CNTRL_CS2 (1 << 6) /* UIO2 or CS2# select */
+#define PLX9052_CNTRL_UIO2_DIR (1 << 7) /* UIO2 direction */
+#define PLX9052_CNTRL_UIO2_DATA (1 << 8) /* UIO2 data */
+#define PLX9052_CNTRL_CS3 (1 << 9) /* UIO3 or CS3# select */
+#define PLX9052_CNTRL_UIO3_DIR (1 << 10) /* UIO3 direction */
+#define PLX9052_CNTRL_UIO3_DATA (1 << 11) /* UIO3 data */
+#define PLX9052_CNTRL_PCIBAR01 (0 << 12) /* bar 0 (mem) and 1 (I/O) */
+#define PLX9052_CNTRL_PCIBAR0 (1 << 12) /* bar 0 (mem) only */
+#define PLX9052_CNTRL_PCIBAR1 (2 << 12) /* bar 1 (I/O) only */
+#define PLX9052_CNTRL_PCI2_1_FEATURES (1 << 14) /* PCI r2.1 features enabled */
+#define PLX9052_CNTRL_PCI_R_W_FLUSH (1 << 15) /* read w/write flush mode */
+#define PLX9052_CNTRL_PCI_R_NO_FLUSH (1 << 16) /* read no flush mode */
+#define PLX9052_CNTRL_PCI_R_NO_WRITE (1 << 17) /* read no write mode */
+#define PLX9052_CNTRL_PCI_W_RELEASE (1 << 18) /* write release bus mode */
+#define PLX9052_CNTRL_RETRY_CLKS(x) (((x) & 0xf) << 19) /* slave retry clks */
+#define PLX9052_CNTRL_LOCK_ENAB (1 << 23) /* slave LOCK# enable */
+#define PLX9052_CNTRL_EEPROM_MASK (0x1f << 24) /* EEPROM bits */
+#define PLX9052_CNTRL_EEPROM_CLK (1 << 24) /* EEPROM clock */
+#define PLX9052_CNTRL_EEPROM_CS (1 << 25) /* EEPROM chip select */
+#define PLX9052_CNTRL_EEPROM_DOUT (1 << 26) /* EEPROM write bit */
+#define PLX9052_CNTRL_EEPROM_DIN (1 << 27) /* EEPROM read bit */
+#define PLX9052_CNTRL_EEPROM_PRESENT (1 << 28) /* EEPROM present */
+#define PLX9052_CNTRL_RELOAD_CFG (1 << 29) /* reload configuration */
+#define PLX9052_CNTRL_PCI_RESET (1 << 30) /* PCI adapter reset */
+#define PLX9052_CNTRL_MASK_REV (1 << 31) /* mask revision */
+
+#endif /* _PLX9052_H_ */
diff --git a/drivers/staging/comedi/drivers/plx9080.h b/drivers/staging/comedi/drivers/plx9080.h
new file mode 100644
index 00000000000..25706531b88
--- /dev/null
+++ b/drivers/staging/comedi/drivers/plx9080.h
@@ -0,0 +1,422 @@
+/* plx9080.h
+ *
+ * Copyright (C) 2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
+ *
+ * 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 <khc@rgstudio.com.pl>
+ *
+ * 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 {
+ __le32 pci_start_addr;
+ __le32 local_start_addr;
+ /* transfer_size is in bytes, only first 23 bits of register are used */
+ __le32 transfer_size;
+ /* address of next descriptor (quad word aligned), plus some
+ * additional bits (see PLX_DMA0_DESCRIPTOR_REG) */
+ __le32 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)
+ 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)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+#endif /* __COMEDI_PLX9080_H */
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 00000000000..b3bbec0a0d2
--- /dev/null
+++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
@@ -0,0 +1,816 @@
+/*======================================================================
+
+ comedi/drivers/quatech_daqp_cs.c
+
+ Quatech DAQP PCMCIA data capture cards COMEDI client driver
+ Copyright (C) 2000, 2003 Brent Baccala <baccala@freesoft.org>
+ 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 <ds@schleef.org>
+ 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 <baccala@freesoft.org>
+Status: works
+Devices: [Quatech] DAQP-208 (daqp), DAQP-308
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include <linux/semaphore.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#include <linux/completion.h>
+
+#include "comedi_fc.h"
+
+struct daqp_private {
+ int stop;
+
+ enum { semaphore, buffer } interrupt_mode;
+
+ struct completion eos;
+
+ int count;
+};
+
+/* 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
+
+static const struct comedi_lrange range_daqp_ai = {
+ 4, {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25)
+ }
+};
+
+/* Cancel a running acquisition */
+
+static int daqp_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->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); */
+
+ devpriv->interrupt_mode = semaphore;
+
+ return 0;
+}
+
+/* Interrupt handler
+ *
+ * Operates in one of two modes. If devpriv->interrupt_mode is
+ * 'semaphore', just signal the devpriv->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 comedi_device *dev = dev_id;
+ struct daqp_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ int loop_limit = 10000;
+ int status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ switch (devpriv->interrupt_mode) {
+ case semaphore:
+ complete(&devpriv->eos);
+ break;
+
+ case buffer:
+ while (!((status = inb(dev->iobase + DAQP_STATUS))
+ & DAQP_STATUS_FIFO_EMPTY)) {
+ unsigned short data;
+
+ if (status & DAQP_STATUS_DATA_LOST) {
+ s->async->events |=
+ COMEDI_CB_EOA | COMEDI_CB_OVERFLOW;
+ dev_warn(dev->class_dev, "data lost\n");
+ break;
+ }
+
+ data = inb(dev->iobase + DAQP_FIFO);
+ data |= inb(dev->iobase + DAQP_FIFO) << 8;
+ data ^= 0x8000;
+
+ comedi_buf_put(s, data);
+
+ /* If there's a limit, decrement it
+ * and stop conversion if zero
+ */
+
+ if (devpriv->count > 0) {
+ devpriv->count--;
+ if (devpriv->count == 0) {
+ s->async->events |= COMEDI_CB_EOA;
+ break;
+ }
+ }
+
+ if ((loop_limit--) <= 0)
+ break;
+ }
+
+ if (loop_limit <= 0) {
+ dev_warn(dev->class_dev,
+ "loop_limit reached in daqp_interrupt()\n");
+ s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
+ }
+
+ s->async->events |= COMEDI_CB_BLOCK;
+
+ cfc_handle_events(dev, s);
+ }
+ return IRQ_HANDLED;
+}
+
+static void daqp_ai_set_one_scanlist_entry(struct comedi_device *dev,
+ unsigned int chanspec,
+ int start)
+{
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned int val;
+
+ val = DAQP_SCANLIST_CHANNEL(chan) | DAQP_SCANLIST_GAIN(range);
+
+ if (aref == AREF_DIFF)
+ val |= DAQP_SCANLIST_DIFFERENTIAL;
+
+ if (start)
+ val |= DAQP_SCANLIST_START;
+
+ outb(val & 0xff, dev->iobase + DAQP_SCANLIST);
+ outb((val >> 8) & 0xff, dev->iobase + DAQP_SCANLIST);
+}
+
+/* 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 daqp_private *devpriv = dev->private;
+ int i;
+ int v;
+ int counter = 10000;
+
+ if (devpriv->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 */
+ daqp_ai_set_one_scanlist_entry(dev, insn->chanspec, 1);
+
+ /* 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) {
+ dev_err(dev->class_dev,
+ "couldn't clear interrupts in status register\n");
+ return -1;
+ }
+
+ init_completion(&devpriv->eos);
+ devpriv->interrupt_mode = semaphore;
+
+ 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(&devpriv->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;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED 10000 /* 100 kHz - in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+
+ /* 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 |= -EINVAL;
+ }
+
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, MAX_SPEED);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ daqp_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ daqp_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct daqp_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int counter;
+ int scanlist_start_on_every_entry;
+ int threshold;
+
+ int i;
+ int v;
+
+ if (devpriv->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 start = (i == 0 || scanlist_start_on_every_entry);
+
+ daqp_ai_set_one_scanlist_entry(dev, cmd->chanlist[i], start);
+ }
+
+ /* 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 devpriv->count by 2 = sizeof(sample))
+ */
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ devpriv->count = cmd->stop_arg * cmd->scan_end_arg;
+ threshold = 2 * devpriv->count;
+ while (threshold > DAQP_FIFO_SIZE * 3 / 4)
+ threshold /= 2;
+ } else {
+ devpriv->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) {
+ dev_err(dev->class_dev,
+ "couldn't clear interrupts in status register\n");
+ return -1;
+ }
+
+ devpriv->interrupt_mode = buffer;
+
+ /* Start conversion */
+ outb(DAQP_COMMAND_ARM | DAQP_COMMAND_FIFO_DATA,
+ dev->iobase + DAQP_COMMAND);
+
+ return 0;
+}
+
+static int daqp_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ /* Make sure D/A update mode is direct update */
+ outb(0, dev->iobase + DAQP_AUX);
+
+ for (i = 0; i > insn->n; i++) {
+ val = data[0];
+ val &= 0x0fff;
+ val ^= 0x0800; /* Flip the sign */
+ val |= (chan << 12);
+
+ outw(val, dev->iobase + DAQP_DA);
+ }
+
+ return insn->n;
+}
+
+static int daqp_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ data[0] = inb(dev->iobase + DAQP_DIGITAL_IO);
+
+ return insn->n;
+}
+
+static int daqp_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct daqp_private *devpriv = dev->private;
+
+ if (devpriv->stop)
+ return -EIO;
+
+ if (comedi_dio_update_state(s, data))
+ outb(s->state, dev->iobase + DAQP_DIGITAL_IO);
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int daqp_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
+ struct daqp_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
+ ret = comedi_pcmcia_enable(dev, NULL);
+ if (ret)
+ return ret;
+ dev->iobase = link->resource[0]->start;
+
+ link->priv = dev;
+ ret = pcmcia_request_irq(link, daqp_interrupt);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ dev->read_subdev = s;
+ 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];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ s->range_table = &range_bipolar5;
+ s->insn_write = daqp_ao_insn_write;
+
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->insn_bits = daqp_di_insn_bits;
+
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 1;
+ s->maxdata = 1;
+ s->insn_bits = daqp_do_insn_bits;
+
+ return 0;
+}
+
+static struct comedi_driver driver_daqp = {
+ .driver_name = "quatech_daqp_cs",
+ .module = THIS_MODULE,
+ .auto_attach = daqp_auto_attach,
+ .detach = comedi_pcmcia_disable,
+};
+
+static int daqp_cs_suspend(struct pcmcia_device *link)
+{
+ struct comedi_device *dev = link->priv;
+ struct daqp_private *devpriv = dev ? dev->private : NULL;
+
+ /* Mark the device as stopped, to block IO until later */
+ if (devpriv)
+ devpriv->stop = 1;
+
+ return 0;
+}
+
+static int daqp_cs_resume(struct pcmcia_device *link)
+{
+ struct comedi_device *dev = link->priv;
+ struct daqp_private *devpriv = dev ? dev->private : NULL;
+
+ if (devpriv)
+ devpriv->stop = 0;
+
+ return 0;
+}
+
+static int daqp_cs_attach(struct pcmcia_device *link)
+{
+ return comedi_pcmcia_auto_config(link, &driver_daqp);
+}
+
+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);
+
+static struct pcmcia_driver daqp_cs_driver = {
+ .name = "quatech_daqp_cs",
+ .owner = THIS_MODULE,
+ .id_table = daqp_cs_id_table,
+ .probe = daqp_cs_attach,
+ .remove = comedi_pcmcia_auto_unconfig,
+ .suspend = daqp_cs_suspend,
+ .resume = daqp_cs_resume,
+};
+module_comedi_pcmcia_driver(driver_daqp, daqp_cs_driver);
+
+MODULE_DESCRIPTION("Comedi driver for Quatech DAQP PCMCIA data capture cards");
+MODULE_AUTHOR("Brent Baccala <baccala@freesoft.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c
new file mode 100644
index 00000000000..d55c5893203
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rtd520.c
@@ -0,0 +1,1426 @@
+/*
+ * 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 <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: rtd520
+ * Description: Real Time Devices PCI4520/DM7520
+ * Devices: (Real Time Devices) DM7520HR-1 [DM7520]
+ * (Real Time Devices) DM7520HR-8 [DM7520]
+ * (Real Time Devices) PCI4520 [PCI4520]
+ * (Real Time Devices) PCI4520-8 [PCI4520]
+ * Author: Dan Christian
+ * Status: Works. Only tested on DM7520-8. Not SMP safe.
+ *
+ * Configuration options: not applicable, uses PCI auto config
+ */
+
+/*
+ * 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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "plx9080.h"
+
+/*
+ * Local Address Space 0 Offsets
+ */
+#define LAS0_USER_IO 0x0008 /* User I/O */
+#define LAS0_ADC 0x0010 /* FIFO Status/Software A/D Start */
+#define FS_DAC1_NOT_EMPTY (1 << 0) /* DAC1 FIFO not empty */
+#define FS_DAC1_HEMPTY (1 << 1) /* DAC1 FIFO half empty */
+#define FS_DAC1_NOT_FULL (1 << 2) /* DAC1 FIFO not full */
+#define FS_DAC2_NOT_EMPTY (1 << 4) /* DAC2 FIFO not empty */
+#define FS_DAC2_HEMPTY (1 << 5) /* DAC2 FIFO half empty */
+#define FS_DAC2_NOT_FULL (1 << 6) /* DAC2 FIFO not full */
+#define FS_ADC_NOT_EMPTY (1 << 8) /* ADC FIFO not empty */
+#define FS_ADC_HEMPTY (1 << 9) /* ADC FIFO half empty */
+#define FS_ADC_NOT_FULL (1 << 10) /* ADC FIFO not full */
+#define FS_DIN_NOT_EMPTY (1 << 12) /* DIN FIFO not empty */
+#define FS_DIN_HEMPTY (1 << 13) /* DIN FIFO half empty */
+#define FS_DIN_NOT_FULL (1 << 14) /* DIN FIFO not full */
+#define LAS0_DAC1 0x0014 /* Software D/A1 Update (w) */
+#define LAS0_DAC2 0x0018 /* Software D/A2 Update (w) */
+#define LAS0_DAC 0x0024 /* Software Simultaneous Update (w) */
+#define LAS0_PACER 0x0028 /* Software Pacer Start/Stop */
+#define LAS0_TIMER 0x002c /* Timer Status/HDIN Software Trig. */
+#define LAS0_IT 0x0030 /* Interrupt Status/Enable */
+#define IRQM_ADC_FIFO_WRITE (1 << 0) /* ADC FIFO Write */
+#define IRQM_CGT_RESET (1 << 1) /* Reset CGT */
+#define IRQM_CGT_PAUSE (1 << 3) /* Pause CGT */
+#define IRQM_ADC_ABOUT_CNT (1 << 4) /* About Counter out */
+#define IRQM_ADC_DELAY_CNT (1 << 5) /* Delay Counter out */
+#define IRQM_ADC_SAMPLE_CNT (1 << 6) /* ADC Sample Counter */
+#define IRQM_DAC1_UCNT (1 << 7) /* DAC1 Update Counter */
+#define IRQM_DAC2_UCNT (1 << 8) /* DAC2 Update Counter */
+#define IRQM_UTC1 (1 << 9) /* User TC1 out */
+#define IRQM_UTC1_INV (1 << 10) /* User TC1 out, inverted */
+#define IRQM_UTC2 (1 << 11) /* User TC2 out */
+#define IRQM_DIGITAL_IT (1 << 12) /* Digital Interrupt */
+#define IRQM_EXTERNAL_IT (1 << 13) /* External Interrupt */
+#define IRQM_ETRIG_RISING (1 << 14) /* Ext Trigger rising-edge */
+#define IRQM_ETRIG_FALLING (1 << 15) /* Ext Trigger falling-edge */
+#define LAS0_CLEAR 0x0034 /* Clear/Set Interrupt Clear Mask */
+#define LAS0_OVERRUN 0x0038 /* Pending interrupts/Clear Overrun */
+#define LAS0_PCLK 0x0040 /* Pacer Clock (24bit) */
+#define LAS0_BCLK 0x0044 /* Burst Clock (10bit) */
+#define LAS0_ADC_SCNT 0x0048 /* A/D Sample counter (10bit) */
+#define LAS0_DAC1_UCNT 0x004c /* D/A1 Update counter (10 bit) */
+#define LAS0_DAC2_UCNT 0x0050 /* D/A2 Update counter (10 bit) */
+#define LAS0_DCNT 0x0054 /* Delay counter (16 bit) */
+#define LAS0_ACNT 0x0058 /* About counter (16 bit) */
+#define LAS0_DAC_CLK 0x005c /* DAC clock (16bit) */
+#define LAS0_UTC0 0x0060 /* 8254 TC Counter 0 */
+#define LAS0_UTC1 0x0064 /* 8254 TC Counter 1 */
+#define LAS0_UTC2 0x0068 /* 8254 TC Counter 2 */
+#define LAS0_UTC_CTRL 0x006c /* 8254 TC Control */
+#define LAS0_DIO0 0x0070 /* Digital I/O Port 0 */
+#define LAS0_DIO1 0x0074 /* Digital I/O Port 1 */
+#define LAS0_DIO0_CTRL 0x0078 /* Digital I/O Control */
+#define LAS0_DIO_STATUS 0x007c /* Digital I/O Status */
+#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 /* HiSpd DI 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 /* Ext. Trigger polarity select */
+#define LAS0_EINT_POLARITY 0x01a8 /* Ext. 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 */
+
+/*
+ * Local Address Space 1 Offsets
+ */
+#define LAS1_ADC_FIFO 0x0000 /* A/D FIFO (16bit) */
+#define LAS1_HDIO_FIFO 0x0004 /* HiSpd DI FIFO (16bit) */
+#define LAS1_DAC1_FIFO 0x0008 /* D/A1 FIFO (16bit) */
+#define LAS1_DAC2_FIFO 0x000c /* D/A2 FIFO (16bit) */
+
+/*======================================================================
+ Driver specific stuff (tunable)
+======================================================================*/
+
+/* 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 */
+
+/*======================================================================
+ Board specific stuff
+======================================================================*/
+
+#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 */
+
+/* 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, {
+ UNI_RANGE(5),
+ UNI_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(10),
+ }
+};
+
+enum rtd_boardid {
+ BOARD_DM7520,
+ BOARD_PCI4520,
+};
+
+struct rtd_boardinfo {
+ const char *name;
+ int range_bip10; /* start of +-10V range */
+ int range_uni10; /* start of +10V range */
+ const struct comedi_lrange *ai_range;
+};
+
+static const struct rtd_boardinfo rtd520Boards[] = {
+ [BOARD_DM7520] = {
+ .name = "DM7520",
+ .range_bip10 = 6,
+ .range_uni10 = 12,
+ .ai_range = &rtd_ai_7520_range,
+ },
+ [BOARD_PCI4520] = {
+ .name = "PCI4520",
+ .range_bip10 = 8,
+ .range_uni10 = 16,
+ .ai_range = &rtd_ai_4520_range,
+ },
+};
+
+struct rtd_private {
+ /* memory mapped board structures */
+ void __iomem *las0;
+ void __iomem *las1;
+ void __iomem *lcfg;
+
+ long ai_count; /* total transfer size (samples) */
+ int xfer_count; /* # to transfer data. 0->1/2FIFO */
+ int flags; /* flag event modes */
+ DECLARE_BITMAP(chan_is_bipolar, RTD_MAX_CHANLIST);
+ unsigned int ao_readback[2];
+ unsigned fifosz;
+};
+
+/* 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 */
+
+/*
+ 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,
+ int round_mode, int base)
+{
+ 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);
+}
+
+/*
+ Convert a single comedi channel-gain entry to a RTD520 table entry
+*/
+static unsigned short rtd_convert_chan_gain(struct comedi_device *dev,
+ unsigned int chanspec, int index)
+{
+ const struct rtd_boardinfo *board = comedi_board(dev);
+ struct rtd_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(chanspec);
+ unsigned int range = CR_RANGE(chanspec);
+ unsigned int aref = CR_AREF(chanspec);
+ unsigned short r = 0;
+
+ r |= chan & 0xf;
+
+ /* Note: we also setup the channel list bipolar flag array */
+ if (range < board->range_bip10) {
+ /* +-5 range */
+ r |= 0x000;
+ r |= (range & 0x7) << 4;
+ __set_bit(index, devpriv->chan_is_bipolar);
+ } else if (range < board->range_uni10) {
+ /* +-10 range */
+ r |= 0x100;
+ r |= ((range - board->range_bip10) & 0x7) << 4;
+ __set_bit(index, devpriv->chan_is_bipolar);
+ } else {
+ /* +10 range */
+ r |= 0x200;
+ r |= ((range - board->range_uni10) & 0x7) << 4;
+ __clear_bit(index, devpriv->chan_is_bipolar);
+ }
+
+ 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;
+ }
+ 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)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ if (n_chan > 1) { /* setup channel gain table */
+ int ii;
+
+ writel(0, devpriv->las0 + LAS0_CGT_CLEAR);
+ writel(1, devpriv->las0 + LAS0_CGT_ENABLE);
+ for (ii = 0; ii < n_chan; ii++) {
+ writel(rtd_convert_chan_gain(dev, list[ii], ii),
+ devpriv->las0 + LAS0_CGT_WRITE);
+ }
+ } else { /* just use the channel gain latch */
+ writel(0, devpriv->las0 + LAS0_CGT_ENABLE);
+ writel(rtd_convert_chan_gain(dev, list[0], 0),
+ devpriv->las0 + LAS0_CGL_WRITE);
+ }
+}
+
+/* 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)
+{
+ struct rtd_private *devpriv = dev->private;
+ unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND);
+ unsigned i;
+ static const unsigned limit = 0x2000;
+ unsigned fifo_size = 0;
+
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+ rtd_load_channelgain_list(dev, 1, &chanspec);
+ /* ADC conversion trigger source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_ADC_CONVERSION);
+ /* convert samples */
+ for (i = 0; i < limit; ++i) {
+ unsigned fifo_status;
+ /* trigger conversion */
+ writew(0, devpriv->las0 + LAS0_ADC);
+ udelay(1);
+ fifo_status = readl(devpriv->las0 + LAS0_ADC);
+ if ((fifo_status & FS_ADC_HEMPTY) == 0) {
+ fifo_size = 2 * i;
+ break;
+ }
+ }
+ if (i == limit) {
+ dev_info(dev->class_dev, "failed to probe fifo size.\n");
+ return -EIO;
+ }
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+ if (fifo_size != 0x400 && fifo_size != 0x2000) {
+ dev_info(dev->class_dev,
+ "unexpected fifo size of %i, expected 1024 or 8192.\n",
+ fifo_size);
+ return -EIO;
+ }
+ return fifo_size;
+}
+
+static int rtd_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct rtd_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readl(devpriv->las0 + LAS0_ADC);
+ if (status & FS_ADC_NOT_EMPTY)
+ return 0;
+ return -EBUSY;
+}
+
+static int rtd_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+ int ret;
+ int n;
+
+ /* clear any old fifo data */
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+
+ /* write channel to multiplexer and clear channel gain table */
+ rtd_load_channelgain_list(dev, 1, &insn->chanspec);
+
+ /* ADC conversion trigger source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_ADC_CONVERSION);
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ unsigned short d;
+ /* trigger conversion */
+ writew(0, devpriv->las0 + LAS0_ADC);
+
+ ret = comedi_timeout(dev, s, insn, rtd_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* read data */
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ d = d >> 3; /* low 3 bits are marker lines */
+ if (test_bit(0, devpriv->chan_is_bipolar))
+ /* convert to comedi unsigned data */
+ d = comedi_offset_munge(s, d);
+ data[n] = d & s->maxdata;
+ }
+
+ /* 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)
+{
+ struct rtd_private *devpriv = dev->private;
+ int ii;
+
+ for (ii = 0; ii < count; ii++) {
+ unsigned short d;
+
+ if (0 == devpriv->ai_count) { /* done */
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ continue;
+ }
+
+ d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+ d = d >> 3; /* low 3 bits are marker lines */
+ if (test_bit(s->async->cur_chan, devpriv->chan_is_bipolar))
+ /* convert to comedi unsigned data */
+ d = comedi_offset_munge(s, d);
+ d &= s->maxdata;
+
+ if (!comedi_buf_put(s, d))
+ return -1;
+
+ if (devpriv->ai_count > 0) /* < 0, means read forever */
+ devpriv->ai_count--;
+ }
+ return 0;
+}
+
+/*
+ unknown amout of data is waiting in fifo.
+*/
+static int ai_read_dregs(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ while (readl(devpriv->las0 + LAS0_ADC) & FS_ADC_NOT_EMPTY) {
+ unsigned short d = readw(devpriv->las1 + LAS1_ADC_FIFO);
+
+ if (0 == devpriv->ai_count) { /* done */
+ continue; /* read rest */
+ }
+
+ d = d >> 3; /* low 3 bits are marker lines */
+ if (test_bit(s->async->cur_chan, devpriv->chan_is_bipolar))
+ /* convert to comedi unsigned data */
+ d = comedi_offset_munge(s, d);
+ d &= s->maxdata;
+
+ if (!comedi_buf_put(s, d))
+ return -1;
+
+ if (devpriv->ai_count > 0) /* < 0, means read forever */
+ devpriv->ai_count--;
+ }
+ return 0;
+}
+
+/*
+ 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, void *d)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct rtd_private *devpriv = dev->private;
+ u32 overrun;
+ u16 status;
+ u16 fifo_status;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+
+ fifo_status = readl(devpriv->las0 + LAS0_ADC);
+ /* check for FIFO full, this automatically halts the ADC! */
+ if (!(fifo_status & FS_ADC_NOT_FULL)) /* 0 -> full */
+ goto xfer_abort;
+
+ status = readw(devpriv->las0 + LAS0_IT);
+ /* 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 (!(fifo_status & FS_ADC_HEMPTY)) {
+ /* FIFO half full */
+ if (ai_read_n(dev, s, devpriv->fifosz / 2) < 0)
+ goto xfer_abort;
+
+ if (0 == devpriv->ai_count)
+ goto xfer_done;
+
+ comedi_event(dev, s);
+ } else if (devpriv->xfer_count > 0) {
+ if (fifo_status & FS_ADC_NOT_EMPTY) {
+ /* FIFO not empty */
+ if (ai_read_n(dev, s, devpriv->xfer_count) < 0)
+ goto xfer_abort;
+
+ if (0 == devpriv->ai_count)
+ goto xfer_done;
+
+ comedi_event(dev, s);
+ }
+ }
+ }
+
+ overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff;
+ if (overrun)
+ goto xfer_abort;
+
+ /* clear the interrupt */
+ writew(status, devpriv->las0 + LAS0_CLEAR);
+ readw(devpriv->las0 + LAS0_CLEAR);
+ return IRQ_HANDLED;
+
+xfer_abort:
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+ s->async->events |= COMEDI_CB_ERROR;
+ devpriv->ai_count = 0; /* stop and don't transfer any more */
+ /* fall into xfer_done */
+
+xfer_done:
+ /* pacer stop source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_PACER_STOP);
+ writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */
+ writel(0, devpriv->las0 + LAS0_ADC_CONVERSION);
+ writew(0, devpriv->las0 + LAS0_IT);
+
+ if (devpriv->ai_count > 0) { /* there shouldn't be anything left */
+ fifo_status = readl(devpriv->las0 + LAS0_ADC);
+ 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 = readw(devpriv->las0 + LAS0_IT);
+ writew(status, devpriv->las0 + LAS0_CLEAR);
+ readw(devpriv->las0 + LAS0_CLEAR);
+
+ fifo_status = readl(devpriv->las0 + LAS0_ADC);
+ overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff;
+
+ return IRQ_HANDLED;
+}
+
+/*
+ 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 (asynchronously).
+
+ 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;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Note: these are time periods, not actual rates */
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ RTD_MAX_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ TRIG_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ RTD_MIN_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ TRIG_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ } else {
+ if (cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ RTD_MAX_SPEED)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ TRIG_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ RTD_MIN_SPEED)) {
+ rtd_ns_to_timer(&cmd->scan_begin_arg,
+ TRIG_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ }
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ /* should specify multiple external triggers */
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ if (1 == cmd->chanlist_len) { /* no scanning */
+ if (cfc_check_trigger_arg_min(&cmd->convert_arg,
+ RTD_MAX_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ TRIG_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (cfc_check_trigger_arg_max(&cmd->convert_arg,
+ RTD_MIN_SPEED_1)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ TRIG_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ } else {
+ if (cfc_check_trigger_arg_min(&cmd->convert_arg,
+ RTD_MAX_SPEED)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ TRIG_ROUND_UP);
+ err |= -EINVAL;
+ }
+ if (cfc_check_trigger_arg_max(&cmd->convert_arg,
+ RTD_MIN_SPEED)) {
+ rtd_ns_to_timer(&cmd->convert_arg,
+ TRIG_ROUND_DOWN);
+ err |= -EINVAL;
+ }
+ }
+ } else {
+ /* external trigger */
+ /* see above */
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 9);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* TODO check for rounding error due to counter wrap */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ rtd_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ rtd_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ 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 rtd_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int timer;
+
+ /* stop anything currently running */
+ /* pacer stop source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_PACER_STOP);
+ writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */
+ writel(0, devpriv->las0 + LAS0_ADC_CONVERSION);
+ writew(0, devpriv->las0 + LAS0_IT);
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+ writel(0, devpriv->las0 + LAS0_OVERRUN);
+
+ /* 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) {
+ /* pacer start source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_PACER_START);
+ /* burst trigger source: PACER */
+ writel(1, devpriv->las0 + LAS0_BURST_START);
+ /* ADC conversion trigger source: BURST */
+ writel(2, devpriv->las0 + LAS0_ADC_CONVERSION);
+ } else { /* single channel */
+ /* pacer start source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_PACER_START);
+ /* ADC conversion trigger source: PACER */
+ writel(1, devpriv->las0 + LAS0_ADC_CONVERSION);
+ }
+ writel((devpriv->fifosz / 2 - 1) & 0xffff, devpriv->las0 + LAS0_ACNT);
+
+ 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->xfer_count = cmd->chanlist_len;
+ devpriv->flags |= SEND_EOS;
+ } else {
+ /* arrange to transfer data periodically */
+ devpriv->xfer_count =
+ (TRANS_TARGET_PERIOD * cmd->chanlist_len) /
+ cmd->scan_begin_arg;
+ if (devpriv->xfer_count < cmd->chanlist_len) {
+ /* transfer after each scan (and avoid 0) */
+ devpriv->xfer_count = cmd->chanlist_len;
+ } else { /* make a multiple of scan length */
+ devpriv->xfer_count =
+ (devpriv->xfer_count +
+ cmd->chanlist_len - 1)
+ / cmd->chanlist_len;
+ devpriv->xfer_count *= cmd->chanlist_len;
+ }
+ devpriv->flags |= SEND_EOS;
+ }
+ if (devpriv->xfer_count >= (devpriv->fifosz / 2)) {
+ /* out of counter range, use 1/2 fifo instead */
+ devpriv->xfer_count = 0;
+ devpriv->flags &= ~SEND_EOS;
+ } else {
+ /* interrupt for each transfer */
+ writel((devpriv->xfer_count - 1) & 0xffff,
+ devpriv->las0 + LAS0_ACNT);
+ }
+ } else { /* unknown timing, just use 1/2 FIFO */
+ devpriv->xfer_count = 0;
+ devpriv->flags &= ~SEND_EOS;
+ }
+ /* pacer clock source: INTERNAL 8MHz */
+ writel(1, devpriv->las0 + LAS0_PACER_SELECT);
+ /* just interrupt, don't stop */
+ writel(1, devpriv->las0 + LAS0_ACNT_STOP_ENABLE);
+
+ /* 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->ai_count = cmd->stop_arg * cmd->chanlist_len;
+ if ((devpriv->xfer_count > 0)
+ && (devpriv->xfer_count > devpriv->ai_count)) {
+ devpriv->xfer_count = devpriv->ai_count;
+ }
+ break;
+
+ case TRIG_NONE: /* stop when cancel is called */
+ devpriv->ai_count = -1; /* read forever */
+ break;
+ }
+
+ /* 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 */
+ writel(timer & 0xffffff, devpriv->las0 + LAS0_PCLK);
+
+ break;
+
+ case TRIG_EXT:
+ /* pacer start source: EXTERNAL */
+ writel(1, devpriv->las0 + LAS0_PACER_START);
+ break;
+ }
+
+ /* 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 */
+ writel(timer & 0x3ff, devpriv->las0 + LAS0_BCLK);
+ }
+
+ break;
+
+ case TRIG_EXT: /* external */
+ /* burst trigger source: EXTERNAL */
+ writel(2, devpriv->las0 + LAS0_BURST_START);
+ break;
+ }
+ /* end configuration */
+
+ /* This doesn't seem to work. There is no way to clear an interrupt
+ that the priority controller has queued! */
+ writew(~0, devpriv->las0 + LAS0_CLEAR);
+ readw(devpriv->las0 + LAS0_CLEAR);
+
+ /* TODO: allow multiple interrupt sources */
+ if (devpriv->xfer_count > 0) { /* transfer every N samples */
+ writew(IRQM_ADC_ABOUT_CNT, devpriv->las0 + LAS0_IT);
+ } else { /* 1/2 FIFO transfers */
+ writew(IRQM_ADC_ABOUT_CNT, devpriv->las0 + LAS0_IT);
+ }
+
+ /* BUG: start_src is ASSUMED to be TRIG_NOW */
+ /* BUG? it seems like things are running before the "start" */
+ readl(devpriv->las0 + LAS0_PACER); /* start pacer */
+ return 0;
+}
+
+/*
+ Stop a running data acquisition.
+*/
+static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct rtd_private *devpriv = dev->private;
+ u32 overrun;
+ u16 status;
+
+ /* pacer stop source: SOFTWARE */
+ writel(0, devpriv->las0 + LAS0_PACER_STOP);
+ writel(0, devpriv->las0 + LAS0_PACER); /* stop pacer */
+ writel(0, devpriv->las0 + LAS0_ADC_CONVERSION);
+ writew(0, devpriv->las0 + LAS0_IT);
+ devpriv->ai_count = 0; /* stop and don't transfer any more */
+ status = readw(devpriv->las0 + LAS0_IT);
+ overrun = readl(devpriv->las0 + LAS0_OVERRUN) & 0xffff;
+ return 0;
+}
+
+static int rtd_ao_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct rtd_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int bit = (chan == 0) ? FS_DAC1_NOT_EMPTY : FS_DAC2_NOT_EMPTY;
+ unsigned int status;
+
+ status = readl(devpriv->las0 + LAS0_ADC);
+ if (status & bit)
+ return 0;
+ return -EBUSY;
+}
+
+static int rtd_ao_winsn(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+ int i;
+ int chan = CR_CHAN(insn->chanspec);
+ int range = CR_RANGE(insn->chanspec);
+ int ret;
+
+ /* Configure the output range (table index matches the range values) */
+ writew(range & 7, devpriv->las0 +
+ ((chan == 0) ? LAS0_DAC1_CTRL : LAS0_DAC2_CTRL));
+
+ /* 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;
+
+ /* 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;
+ }
+
+ /* a typical programming sequence */
+ writew(val, devpriv->las1 +
+ ((chan == 0) ? LAS1_DAC1_FIFO : LAS1_DAC2_FIFO));
+ writew(0, devpriv->las0 +
+ ((chan == 0) ? LAS0_DAC1 : LAS0_DAC2));
+
+ devpriv->ao_readback[chan] = data[i];
+
+ ret = comedi_timeout(dev, s, insn, rtd_ao_eoc, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* 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)
+{
+ struct rtd_private *devpriv = dev->private;
+ 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 rtd_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ if (comedi_dio_update_state(s, data))
+ writew(s->state & 0xff, devpriv->las0 + LAS0_DIO0);
+
+ data[1] = readw(devpriv->las0 + LAS0_DIO0) & 0xff;
+
+ return insn->n;
+}
+
+static int rtd_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rtd_private *devpriv = dev->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /* TODO support digital match interrupts and strobes */
+
+ /* set direction */
+ writew(0x01, devpriv->las0 + LAS0_DIO_STATUS);
+ writew(s->io_bits & 0xff, devpriv->las0 + LAS0_DIO0_CTRL);
+
+ /* clear interrupts */
+ writew(0x00, devpriv->las0 + LAS0_DIO_STATUS);
+
+ /* port1 can only be all input or all output */
+
+ /* there are also 2 user input lines and 2 user output lines */
+
+ return insn->n;
+}
+
+static void rtd_reset(struct comedi_device *dev)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ writel(0, devpriv->las0 + LAS0_BOARD_RESET);
+ udelay(100); /* needed? */
+ writel(0, devpriv->lcfg + PLX_INTRCS_REG);
+ writew(0, devpriv->las0 + LAS0_IT);
+ writew(~0, devpriv->las0 + LAS0_CLEAR);
+ readw(devpriv->las0 + LAS0_CLEAR);
+}
+
+/*
+ * initialize board, per RTD spec
+ * also, initialize shadow registers
+ */
+static void rtd_init_board(struct comedi_device *dev)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ rtd_reset(dev);
+
+ writel(0, devpriv->las0 + LAS0_OVERRUN);
+ writel(0, devpriv->las0 + LAS0_CGT_CLEAR);
+ writel(0, devpriv->las0 + LAS0_ADC_FIFO_CLEAR);
+ writel(0, devpriv->las0 + LAS0_DAC1_RESET);
+ writel(0, devpriv->las0 + LAS0_DAC2_RESET);
+ /* clear digital IO fifo */
+ writew(0, devpriv->las0 + LAS0_DIO_STATUS);
+ writeb((0 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL);
+ writeb((1 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL);
+ writeb((2 << 6) | 0x30, devpriv->las0 + LAS0_UTC_CTRL);
+ writeb((3 << 6) | 0x00, devpriv->las0 + LAS0_UTC_CTRL);
+ /* TODO: set user out source ??? */
+}
+
+/* The RTD driver does this */
+static void rtd_pci_latency_quirk(struct comedi_device *dev,
+ struct pci_dev *pcidev)
+{
+ unsigned char pci_latency;
+
+ pci_read_config_byte(pcidev, PCI_LATENCY_TIMER, &pci_latency);
+ if (pci_latency < 32) {
+ dev_info(dev->class_dev,
+ "PCI latency changed from %d to %d\n",
+ pci_latency, 32);
+ pci_write_config_byte(pcidev, PCI_LATENCY_TIMER, 32);
+ }
+}
+
+static int rtd_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct rtd_boardinfo *board = NULL;
+ struct rtd_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ if (context < ARRAY_SIZE(rtd520Boards))
+ board = &rtd520Boards[context];
+ if (!board)
+ return -ENODEV;
+ dev->board_ptr = board;
+ dev->board_name = board->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->las0 = pci_ioremap_bar(pcidev, 2);
+ devpriv->las1 = pci_ioremap_bar(pcidev, 3);
+ devpriv->lcfg = pci_ioremap_bar(pcidev, 0);
+ if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg)
+ return -ENOMEM;
+
+ rtd_pci_latency_quirk(dev, pcidev);
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, rtd_interrupt, IRQF_SHARED,
+ dev->board_name, dev);
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF;
+ s->n_chan = 16;
+ s->maxdata = 0x0fff;
+ s->range_table = board->ai_range;
+ s->len_chanlist = RTD_MAX_CHANLIST;
+ s->insn_read = rtd_ai_rinsn;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_READ;
+ s->do_cmd = rtd_ai_cmd;
+ s->do_cmdtest = rtd_ai_cmdtest;
+ s->cancel = rtd_ai_cancel;
+ }
+
+ s = &dev->subdevices[1];
+ /* analog output subdevice */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = 2;
+ s->maxdata = 0x0fff;
+ 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;
+
+ rtd_init_board(dev);
+
+ ret = rtd520_probe_fifo_depth(dev);
+ if (ret < 0)
+ return ret;
+ devpriv->fifosz = ret;
+
+ if (dev->irq)
+ writel(ICS_PIE | ICS_PLIE, devpriv->lcfg + PLX_INTRCS_REG);
+
+ return 0;
+}
+
+static void rtd_detach(struct comedi_device *dev)
+{
+ struct rtd_private *devpriv = dev->private;
+
+ if (devpriv) {
+ /* Shut down any board ops by resetting it */
+ if (devpriv->las0 && devpriv->lcfg)
+ rtd_reset(dev);
+ if (dev->irq) {
+ writel(readl(devpriv->lcfg + PLX_INTRCS_REG) &
+ ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E),
+ devpriv->lcfg + PLX_INTRCS_REG);
+ free_irq(dev->irq, dev);
+ }
+ if (devpriv->las0)
+ iounmap(devpriv->las0);
+ if (devpriv->las1)
+ iounmap(devpriv->las1);
+ if (devpriv->lcfg)
+ iounmap(devpriv->lcfg);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver rtd520_driver = {
+ .driver_name = "rtd520",
+ .module = THIS_MODULE,
+ .auto_attach = rtd_auto_attach,
+ .detach = rtd_detach,
+};
+
+static int rtd520_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &rtd520_driver, id->driver_data);
+}
+
+static const struct pci_device_id rtd520_pci_table[] = {
+ { PCI_VDEVICE(RTD, 0x7520), BOARD_DM7520 },
+ { PCI_VDEVICE(RTD, 0x4520), BOARD_PCI4520 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, rtd520_pci_table);
+
+static struct pci_driver rtd520_pci_driver = {
+ .name = "rtd520",
+ .id_table = rtd520_pci_table,
+ .probe = rtd520_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(rtd520_driver, rtd520_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rti800.c b/drivers/staging/comedi/drivers/rti800.c
new file mode 100644
index 00000000000..bd447b2add7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rti800.c
@@ -0,0 +1,378 @@
+/*
+ * 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 <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: rti800
+ * Description: Analog Devices RTI-800/815
+ * Devices: (Analog Devices) RTI-800 [rti800]
+ * (Analog Devices) RTI-815 [rti815]
+ * Author: David A. Schleef <ds@schleef.org>
+ * Status: unknown
+ * Updated: Fri, 05 Sep 2008 14:50:44 +0100
+ *
+ * Configuration options:
+ * [0] - I/O port base address
+ * [1] - IRQ (not supported / unused)
+ * [2] - A/D mux/reference (number of channels)
+ * 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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include "../comedidev.h"
+
+/*
+ * Register map
+ */
+#define RTI800_CSR 0x00
+#define RTI800_CSR_BUSY (1 << 7)
+#define RTI800_CSR_DONE (1 << 6)
+#define RTI800_CSR_OVERRUN (1 << 5)
+#define RTI800_CSR_TCR (1 << 4)
+#define RTI800_CSR_DMA_ENAB (1 << 3)
+#define RTI800_CSR_INTR_TC (1 << 2)
+#define RTI800_CSR_INTR_EC (1 << 1)
+#define RTI800_CSR_INTR_OVRN (1 << 0)
+#define RTI800_MUXGAIN 0x01
+#define RTI800_CONVERT 0x02
+#define RTI800_ADCLO 0x03
+#define RTI800_ADCHI 0x04
+#define RTI800_DAC0LO 0x05
+#define RTI800_DAC0HI 0x06
+#define RTI800_DAC1LO 0x07
+#define RTI800_DAC1HI 0x08
+#define RTI800_CLRFLAGS 0x09
+#define RTI800_DI 0x0a
+#define RTI800_DO 0x0b
+#define RTI800_9513A_DATA 0x0c
+#define RTI800_9513A_CNTRL 0x0d
+#define RTI800_9513A_STATUS 0x0d
+
+#define RTI800_IOSIZE 0x10
+
+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)
+ }
+};
+
+static const struct comedi_lrange *const rti800_ai_ranges[] = {
+ &range_rti800_ai_10_bipolar,
+ &range_rti800_ai_5_bipolar,
+ &range_rti800_ai_unipolar,
+};
+
+static const struct comedi_lrange *const rti800_ao_ranges[] = {
+ &range_bipolar10,
+ &range_unipolar10,
+};
+
+struct rti800_board {
+ const char *name;
+ int has_ao;
+};
+
+static const struct rti800_board rti800_boardtypes[] = {
+ {
+ .name = "rti800",
+ }, {
+ .name = "rti815",
+ .has_ao = 1,
+ },
+};
+
+struct rti800_private {
+ bool adc_2comp;
+ bool dac_2comp[2];
+ const struct comedi_lrange *ao_range_type_list[2];
+ unsigned int ao_readback[2];
+ unsigned char muxgain_bits;
+};
+
+static int rti800_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned char status;
+
+ status = inb(dev->iobase + RTI800_CSR);
+ if (status & RTI800_CSR_OVERRUN) {
+ outb(0, dev->iobase + RTI800_CLRFLAGS);
+ return -EOVERFLOW;
+ }
+ if (status & RTI800_CSR_DONE)
+ return 0;
+ return -EBUSY;
+}
+
+static int rti800_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int gain = CR_RANGE(insn->chanspec);
+ unsigned char muxgain_bits;
+ int ret;
+ int i;
+
+ 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_CSR_OVERRUN bit
+ * gets set, and you will have an error.
+ */
+ if (insn->n > 0) {
+ int delay = (gain == 0) ? 10 :
+ (gain == 1) ? 20 :
+ (gain == 2) ? 40 : 80;
+
+ udelay(delay);
+ }
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ outb(0, dev->iobase + RTI800_CONVERT);
+
+ ret = comedi_timeout(dev, s, insn, rti800_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ data[i] = inb(dev->iobase + RTI800_ADCLO);
+ data[i] |= (inb(dev->iobase + RTI800_ADCHI) & 0xf) << 8;
+
+ if (devpriv->adc_2comp)
+ data[i] ^= 0x800;
+ }
+
+ return insn->n;
+}
+
+static int rti800_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int rti800_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti800_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int reg_lo = chan ? RTI800_DAC1LO : RTI800_DAC0LO;
+ int reg_hi = chan ? RTI800_DAC1HI : RTI800_DAC0HI;
+ int val = devpriv->ao_readback[chan];
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+ if (devpriv->dac_2comp[chan])
+ val ^= 0x800;
+
+ outb(val & 0xff, dev->iobase + reg_lo);
+ outb((val >> 8) & 0xff, dev->iobase + reg_hi);
+ }
+
+ devpriv->ao_readback[chan] = val;
+
+ return insn->n;
+}
+
+static int rti800_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ data[1] = inb(dev->iobase + RTI800_DI);
+ return insn->n;
+}
+
+static int rti800_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data)) {
+ /* Outputs are inverted... */
+ outb(s->state ^ 0xff, dev->iobase + RTI800_DO);
+ }
+
+ data[1] = s->state;
+
+ return insn->n;
+}
+
+static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct rti800_board *board = comedi_board(dev);
+ struct rti800_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], RTI800_IOSIZE);
+ if (ret)
+ return ret;
+
+ outb(0, dev->iobase + RTI800_CSR);
+ inb(dev->iobase + RTI800_ADCHI);
+ outb(0, dev->iobase + RTI800_CLRFLAGS);
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->adc_2comp = (it->options[4] == 0);
+ devpriv->dac_2comp[0] = (it->options[6] == 0);
+ devpriv->dac_2comp[1] = (it->options[8] == 0);
+ /* invalid, forces the MUXGAIN register to be set when first used */
+ devpriv->muxgain_bits = 0xff;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* ai subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = (it->options[2] ? 16 : 8);
+ s->insn_read = rti800_ai_insn_read;
+ s->maxdata = 0x0fff;
+ s->range_table = (it->options[3] < ARRAY_SIZE(rti800_ai_ranges))
+ ? rti800_ai_ranges[it->options[3]]
+ : &range_unknown;
+
+ s = &dev->subdevices[1];
+ if (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 = 0x0fff;
+ s->range_table_list = devpriv->ao_range_type_list;
+ devpriv->ao_range_type_list[0] =
+ (it->options[5] < ARRAY_SIZE(rti800_ao_ranges))
+ ? rti800_ao_ranges[it->options[5]]
+ : &range_unknown;
+ devpriv->ao_range_type_list[1] =
+ (it->options[7] < ARRAY_SIZE(rti800_ao_ranges))
+ ? rti800_ao_ranges[it->options[7]]
+ : &range_unknown;
+ } else {
+ s->type = COMEDI_SUBD_UNUSED;
+ }
+
+ s = &dev->subdevices[2];
+ /* 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 = &dev->subdevices[3];
+ /* 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;
+
+ /*
+ * There is also an Am9513 timer on these boards. This subdevice
+ * is not currently supported.
+ */
+
+ return 0;
+}
+
+static struct comedi_driver rti800_driver = {
+ .driver_name = "rti800",
+ .module = THIS_MODULE,
+ .attach = rti800_attach,
+ .detach = comedi_legacy_detach,
+ .num_names = ARRAY_SIZE(rti800_boardtypes),
+ .board_name = &rti800_boardtypes[0].name,
+ .offset = sizeof(struct rti800_board),
+};
+module_comedi_driver(rti800_driver);
+
+MODULE_DESCRIPTION("Comedi: RTI-800 Multifunction Analog/Digital board");
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/rti802.c b/drivers/staging/comedi/drivers/rti802.c
new file mode 100644
index 00000000000..605a31d702e
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rti802.c
@@ -0,0 +1,143 @@
+/*
+ * rti802.c
+ * Comedi driver for Analog Devices RTI-802 board
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * Driver: rti802
+ * Description: Analog Devices RTI-802
+ * Author: Anders Blomdell <anders.blomdell@control.lth.se>
+ * Devices: (Analog Devices) RTI-802 [rti802]
+ * Status: works
+ *
+ * Configuration Options:
+ * [0] - i/o base
+ * [1] - unused
+ * [2,4,6,8,10,12,14,16] - dac#[0-7] 0=two's comp, 1=straight
+ * [3,5,7,9,11,13,15,17] - dac#[0-7] 0=bipolar, 1=unipolar
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define RTI802_SELECT 0x00
+#define RTI802_DATALOW 0x01
+#define RTI802_DATAHIGH 0x02
+
+struct rti802_private {
+ enum {
+ dac_2comp, dac_straight
+ } dac_coding[8];
+ const struct comedi_lrange *range_type_list[8];
+ unsigned int ao_readback[8];
+};
+
+static int rti802_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti802_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return insn->n;
+}
+
+static int rti802_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct rti802_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val;
+ int i;
+
+ outb(chan, dev->iobase + RTI802_SELECT);
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ devpriv->ao_readback[chan] = val;
+
+ /* munge offset binary to two's complement if needed */
+ if (devpriv->dac_coding[chan] == dac_2comp)
+ val = comedi_offset_munge(s, val);
+
+ outb(val & 0xff, dev->iobase + RTI802_DATALOW);
+ outb((val >> 8) & 0xff, dev->iobase + RTI802_DATAHIGH);
+ }
+
+ return insn->n;
+}
+
+static int rti802_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct rti802_private *devpriv;
+ struct comedi_subdevice *s;
+ int i;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], 0x04);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ return ret;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[0];
+ 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 struct comedi_driver rti802_driver = {
+ .driver_name = "rti802",
+ .module = THIS_MODULE,
+ .attach = rti802_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(rti802_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi driver for Analog Devices RTI-802 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/s526.c b/drivers/staging/comedi/drivers/s526.c
new file mode 100644
index 00000000000..85d2b7a3c12
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s526.c
@@ -0,0 +1,628 @@
+/*
+ comedi/drivers/s526.c
+ Sensoray s526 Comedi driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: s526
+Description: Sensoray 526 driver
+Devices: [Sensoray] 526 (s526)
+Author: Richie
+ Everett Wang <everett.wang@everteq.com>
+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 <linux/module.h>
+#include "../comedidev.h"
+#include <asm/byteorder.h>
+
+#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
+
+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;
+};
+
+struct s526_private {
+ unsigned int ao_readback[2];
+ unsigned int gpct_config[4];
+ unsigned short ai_config;
+};
+
+static int s526_gpct_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+ unsigned int lo;
+ unsigned int hi;
+ int i;
+
+ for (i = 0; i < insn->n; i++) {
+ /* Read the low word first */
+ lo = inw(chan_iobase + REG_C0L) & 0xffff;
+ hi = inw(chan_iobase + REG_C0H) & 0xff;
+
+ data[i] = (hi << 16) | lo;
+ }
+
+ return insn->n;
+}
+
+static int s526_gpct_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+ unsigned int val;
+ union cmReg cmReg;
+
+ /* Check what type of Counter the user requested, data[0] contains */
+ /* the Application type */
+ switch (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
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+#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, chan_iobase + REG_C0M);
+
+ outw(0x0001, chan_iobase + REG_C0H);
+ outw(0x3C68, chan_iobase + REG_C0L);
+
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0 */
+ outw(0x4000, chan_iobase + REG_C0C);
+
+ /* Reset RCAP (fires one-shot) */
+ outw(0x0008, chan_iobase + REG_C0C);
+
+#endif
+
+#if 1
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Reset the counter if it is software preload */
+ if (cmReg.reg.autoLoadResetRcap == 0) {
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0
+ * outw(0x4000, chan_iobase + REG_C0C);
+ */
+ }
+#else
+ /* 0 quadrature, 1 software control */
+ cmReg.reg.countDirCtrl = 0;
+
+ /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
+ if (data[1] == GPCT_X2)
+ cmReg.reg.clockSource = 1;
+ else if (data[1] == GPCT_X4)
+ cmReg.reg.clockSource = 2;
+ else
+ cmReg.reg.clockSource = 0;
+
+ /* When to take into account the indexpulse: */
+ /*if (data[2] == GPCT_IndexPhaseLowLow) {
+ } else if (data[2] == GPCT_IndexPhaseLowHigh) {
+ } else if (data[2] == GPCT_IndexPhaseHighLow) {
+ } else if (data[2] == GPCT_IndexPhaseHighHigh) {
+ }*/
+ /* Take into account the index pulse? */
+ if (data[3] == GPCT_RESET_COUNTER_ON_INDEX)
+ /* Auto load with INDEX^ */
+ cmReg.reg.autoLoadResetRcap = 4;
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[3]) {
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ /* Reset the counter if it is software preload */
+ if (cmReg.reg.autoLoadResetRcap == 0) {
+ /* Reset the counter */
+ outw(0x8000, chan_iobase + REG_C0C);
+ /* Load the counter from PR0 */
+ outw(0x4000, chan_iobase + REG_C0C);
+ }
+#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
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 0; /* PR0 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 0 high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 0 low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 1; /* PR1 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 1 high word */
+ val = (data[3] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 1 low word */
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[4]) {
+ val = data[4] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ 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
+ */
+ devpriv->gpct_config[chan] = data[0];
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 0; /* PR0 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 0 high word */
+ val = (data[2] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 0 low word */
+ val = data[2] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Set Counter Mode Register */
+ cmReg.value = data[1] & 0xffff;
+ cmReg.reg.preloadRegSel = 1; /* PR1 */
+ outw(cmReg.value, chan_iobase + REG_C0M);
+
+ /* Load the pre-load register 1 high word */
+ val = (data[3] >> 16) & 0xffff;
+ outw(val, chan_iobase + REG_C0H);
+
+ /* Load the pre-load register 1 low word */
+ val = data[3] & 0xffff;
+ outw(val, chan_iobase + REG_C0L);
+
+ /* Write the Counter Control Register */
+ if (data[4]) {
+ val = data[4] & 0xffff;
+ outw(val, chan_iobase + REG_C0C);
+ }
+ break;
+
+ default:
+ 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)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned long chan_iobase = dev->iobase + chan * 8;
+
+ inw(chan_iobase + REG_C0M); /* Is this read required? */
+
+ /* Check what Application of Counter this channel is configured for */
+ switch (devpriv->gpct_config[chan]) {
+ case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
+ /* 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
+ */
+ if ((data[1] <= data[0]) || !data[0])
+ return -EINVAL;
+
+ /* Fall thru to write the PULSE_WIDTH */
+
+ case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
+ case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
+ outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
+ outw(data[0] & 0xffff, chan_iobase + REG_C0L);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ 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)
+{
+ struct s526_private *devpriv = dev->private;
+ 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, dev->iobase + REG_IER);
+ devpriv->ai_config = (data[0] & 0x3ff) << 5;
+ if (data[1] > 0)
+ devpriv->ai_config |= 0x8000; /* set the delay */
+
+ devpriv->ai_config |= 0x0001; /* ADC start bit */
+
+ return result;
+}
+
+static int s526_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ status = inw(dev->iobase + REG_ISR);
+ if (status & ISR_ADC_DONE)
+ return 0;
+ return -EBUSY;
+}
+
+static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int n;
+ unsigned short value;
+ unsigned int d;
+ int ret;
+
+ /* Set configured delay, enable channel for this channel only,
+ * select "ADC read" channel, set "ADC start" bit. */
+ value = (devpriv->ai_config & 0x8000) |
+ ((1 << 5) << chan) | (chan << 1) | 0x0001;
+
+ /* convert n samples */
+ for (n = 0; n < insn->n; n++) {
+ /* trigger conversion */
+ outw(value, dev->iobase + REG_ADC);
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
+
+ /* read data */
+ d = inw(dev->iobase + REG_ADD);
+
+ /* 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)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned short val;
+ int i;
+
+ val = chan << 1;
+ outw(val, dev->iobase + REG_DAC);
+
+ for (i = 0; i < insn->n; i++) {
+ outw(data[i], dev->iobase + REG_ADD);
+ devpriv->ao_readback[chan] = data[i];
+ /* starts the D/A conversion */
+ outw(val + 1, dev->iobase + REG_DAC);
+ }
+
+ return i;
+}
+
+static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s526_private *devpriv = dev->private;
+ unsigned 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 s526_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ if (comedi_dio_update_state(s, data))
+ outw(s->state, dev->iobase + REG_DIO);
+
+ data[1] = inw(dev->iobase + REG_DIO) & 0xff;
+
+ return insn->n;
+}
+
+static int s526_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ int ret;
+
+ if (chan < 4)
+ mask = 0x0f;
+ else
+ mask = 0xf0;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+ if (ret)
+ return ret;
+
+ /* bit 10/11 set the group 1/2's mode */
+ if (s->io_bits & 0x0f)
+ s->state |= (1 << 10);
+ else
+ s->state &= ~(1 << 10);
+ if (s->io_bits & 0xf0)
+ s->state |= (1 << 11);
+ else
+ s->state &= ~(1 << 11);
+
+ outw(s->state, dev->iobase + REG_DIO);
+
+ return insn->n;
+}
+
+static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct s526_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_request_region(dev, it->options[0], S526_IOSIZE);
+ if (ret)
+ return ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_alloc_subdevices(dev, 4);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+ s->n_chan = 4;
+ 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;
+
+ s = &dev->subdevices[1];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ 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;
+ 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 */
+ 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;
+
+ return 0;
+}
+
+static struct comedi_driver s526_driver = {
+ .driver_name = "s526",
+ .module = THIS_MODULE,
+ .attach = s526_attach,
+ .detach = comedi_legacy_detach,
+};
+module_comedi_driver(s526_driver);
+
+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 00000000000..0838f8aa695
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s626.c
@@ -0,0 +1,3110 @@
+/*
+ * comedi/drivers/s626.c
+ * Sensoray s626 Comedi driver
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * 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.
+ */
+
+/*
+ * Driver: s626
+ * Description: Sensoray 626 driver
+ * Devices: [Sensoray] 626 (s626)
+ * Authors: Gianluca Palli <gpalli@deis.unibo.it>,
+ * Updated: Fri, 15 Feb 2008 10:28:42 +0000
+ * Status: experimental
+
+ * Configuration options: not applicable, uses PCI auto config
+
+ * 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 <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+#include "s626.h"
+
+struct s626_buffer_dma {
+ dma_addr_t physical_base;
+ void *logical_base;
+};
+
+struct s626_private {
+ void __iomem *mmio;
+ uint8_t ai_cmd_running; /* ai_cmd is running */
+ uint8_t ai_continuous; /* continuous 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 counter_int_enabs; /* counter interrupt enable mask
+ * for MISC2 register */
+ uint8_t adc_items; /* number of items in ADC poll list */
+ struct s626_buffer_dma rps_buf; /* DMA buffer used to hold ADC (RPS1)
+ * program */
+ struct s626_buffer_dma ana_buf; /* DMA buffer used to receive ADC data
+ * and hold DAC data */
+ uint32_t *dac_wbuf; /* pointer to logical adrs of DMA buffer
+ * used to hold DAC data */
+ uint16_t dacpol; /* image of DAC polarity register */
+ uint8_t trim_setpoint[12]; /* images of TrimDAC setpoints */
+ uint32_t i2c_adrs; /* I2C device address for onboard EEPROM
+ * (board rev dependent) */
+ unsigned int ao_readback[S626_DAC_CHANNELS];
+};
+
+/* COUNTER OBJECT ------------------------------------------------ */
+struct s626_enc_info {
+ /* Pointers to functions that differ for A and B counters: */
+ /* Return clock enable. */
+ uint16_t (*get_enable)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+ /* Return interrupt source. */
+ uint16_t (*get_int_src)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+ /* Return preload trigger source. */
+ uint16_t (*get_load_trig)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+ /* Return standardized operating mode. */
+ uint16_t (*get_mode)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+ /* Generate soft index strobe. */
+ void (*pulse_index)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+ /* Program clock enable. */
+ void (*set_enable)(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t enab);
+ /* Program interrupt source. */
+ void (*set_int_src)(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t int_source);
+ /* Program preload trigger source. */
+ void (*set_load_trig)(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t trig);
+ /* Program standardized operating mode. */
+ void (*set_mode)(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t setup,
+ uint16_t disable_int_src);
+ /* Reset event capture flags. */
+ void (*reset_cap_flags)(struct comedi_device *dev,
+ const struct s626_enc_info *k);
+
+ uint16_t my_cra; /* address of CRA register */
+ uint16_t my_crb; /* address of CRB register */
+ uint16_t my_latch_lsw; /* address of Latch least-significant-word
+ * register */
+ uint16_t my_event_bits[4]; /* bit translations for IntSrc -->RDMISC2 */
+};
+
+/* Counter overflow/index event flag masks for RDMISC2. */
+#define S626_INDXMASK(C) (1 << (((C) > 2) ? ((C) * 2 - 1) : ((C) * 2 + 4)))
+#define S626_OVERMASK(C) (1 << (((C) > 2) ? ((C) * 2 + 5) : ((C) * 2 + 10)))
+#define S626_EVBITS(C) { 0, S626_OVERMASK(C), S626_INDXMASK(C), \
+ S626_OVERMASK(C) | S626_INDXMASK(C) }
+
+/*
+ * Translation table to map IntSrc into equivalent RDMISC2 event flag bits.
+ * static const uint16_t s626_event_bits[][4] =
+ * { S626_EVBITS(0), S626_EVBITS(1), S626_EVBITS(2), S626_EVBITS(3),
+ * S626_EVBITS(4), S626_EVBITS(5) };
+ */
+
+/*
+ * Enable/disable a function or test status bit(s) that are accessed
+ * through Main Control Registers 1 or 2.
+ */
+static void s626_mc_enable(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int val = (cmd << 16) | cmd;
+
+ mmiowb();
+ writel(val, devpriv->mmio + reg);
+}
+
+static void s626_mc_disable(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ struct s626_private *devpriv = dev->private;
+
+ writel(cmd << 16 , devpriv->mmio + reg);
+ mmiowb();
+}
+
+static bool s626_mc_test(struct comedi_device *dev,
+ unsigned int cmd, unsigned int reg)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int val;
+
+ val = readl(devpriv->mmio + reg);
+
+ return (val & cmd) ? true : false;
+}
+
+#define S626_BUGFIX_STREG(REGADRS) ((REGADRS) - 4)
+
+/* Write a time slot control record to TSL2. */
+#define S626_VECTPORT(VECTNUM) (S626_P_TSL2 + ((VECTNUM) << 2))
+
+static const struct comedi_lrange s626_range_table = {
+ 2, {
+ BIP_RANGE(5),
+ BIP_RANGE(10)
+ }
+};
+
+/*
+ * Execute a DEBI transfer. This must be called from within a critical section.
+ */
+static void s626_debi_transfer(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ static const int timeout = 10000;
+ int i;
+
+ /* Initiate upload of shadow RAM to DEBI control register */
+ s626_mc_enable(dev, S626_MC2_UPLD_DEBI, S626_P_MC2);
+
+ /*
+ * Wait for completion of upload from shadow RAM to
+ * DEBI control register.
+ */
+ for (i = 0; i < timeout; i++) {
+ if (s626_mc_test(dev, S626_MC2_UPLD_DEBI, S626_P_MC2))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ comedi_error(dev,
+ "Timeout while uploading to DEBI control register.");
+
+ /* Wait until DEBI transfer is done */
+ for (i = 0; i < timeout; i++) {
+ if (!(readl(devpriv->mmio + S626_P_PSR) & S626_PSR_DEBI_S))
+ break;
+ udelay(1);
+ }
+ if (i == timeout)
+ comedi_error(dev, "DEBI transfer timeout.");
+}
+
+/*
+ * Read a value from a gate array register.
+ */
+static uint16_t s626_debi_read(struct comedi_device *dev, uint16_t addr)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /* Set up DEBI control register value in shadow RAM */
+ writel(S626_DEBI_CMD_RDWORD | addr, devpriv->mmio + S626_P_DEBICMD);
+
+ /* Execute the DEBI transfer. */
+ s626_debi_transfer(dev);
+
+ return readl(devpriv->mmio + S626_P_DEBIAD);
+}
+
+/*
+ * Write a value to a gate array register.
+ */
+static void s626_debi_write(struct comedi_device *dev, uint16_t addr,
+ uint16_t wdata)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /* Set up DEBI control register value in shadow RAM */
+ writel(S626_DEBI_CMD_WRWORD | addr, devpriv->mmio + S626_P_DEBICMD);
+ writel(wdata, devpriv->mmio + S626_P_DEBIAD);
+
+ /* Execute the DEBI transfer. */
+ s626_debi_transfer(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 s626_debi_replace(struct comedi_device *dev, unsigned int addr,
+ unsigned int mask, unsigned int wdata)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int val;
+
+ addr &= 0xffff;
+ writel(S626_DEBI_CMD_RDWORD | addr, devpriv->mmio + S626_P_DEBICMD);
+ s626_debi_transfer(dev);
+
+ writel(S626_DEBI_CMD_WRWORD | addr, devpriv->mmio + S626_P_DEBICMD);
+ val = readl(devpriv->mmio + S626_P_DEBIAD);
+ val &= mask;
+ val |= wdata;
+ writel(val & 0xffff, devpriv->mmio + S626_P_DEBIAD);
+ s626_debi_transfer(dev);
+}
+
+/* ************** EEPROM ACCESS FUNCTIONS ************** */
+
+static int s626_i2c_handshake_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ bool status;
+
+ status = s626_mc_test(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ if (status)
+ return 0;
+ return -EBUSY;
+}
+
+static int s626_i2c_handshake(struct comedi_device *dev, uint32_t val)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int ctrl;
+ int ret;
+
+ /* Write I2C command to I2C Transfer Control shadow register */
+ writel(val, devpriv->mmio + S626_P_I2CCTRL);
+
+ /*
+ * Upload I2C shadow registers into working registers and
+ * wait for upload confirmation.
+ */
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Wait until I2C bus transfer is finished or an error occurs */
+ do {
+ ctrl = readl(devpriv->mmio + S626_P_I2CCTRL);
+ } while ((ctrl & (S626_I2C_BUSY | S626_I2C_ERR)) == S626_I2C_BUSY);
+
+ /* Return non-zero if I2C error occurred */
+ return ctrl & S626_I2C_ERR;
+}
+
+/* Read uint8_t from EEPROM. */
+static uint8_t s626_i2c_read(struct comedi_device *dev, uint8_t addr)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /*
+ * Send EEPROM target address:
+ * Byte2 = I2C command: write to I2C EEPROM device.
+ * Byte1 = EEPROM internal target address.
+ * Byte0 = Not sent.
+ */
+ if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART,
+ devpriv->i2c_adrs) |
+ S626_I2C_B1(S626_I2C_ATTRSTOP, addr) |
+ S626_I2C_B0(S626_I2C_ATTRNOP, 0)))
+ /* Abort function and declare error if handshake failed. */
+ return 0;
+
+ /*
+ * Execute EEPROM read:
+ * Byte2 = I2C command: read from I2C EEPROM device.
+ * Byte1 receives uint8_t from EEPROM.
+ * Byte0 = Not sent.
+ */
+ if (s626_i2c_handshake(dev, S626_I2C_B2(S626_I2C_ATTRSTART,
+ (devpriv->i2c_adrs | 1)) |
+ S626_I2C_B1(S626_I2C_ATTRSTOP, 0) |
+ S626_I2C_B0(S626_I2C_ATTRNOP, 0)))
+ /* Abort function and declare error if handshake failed. */
+ return 0;
+
+ return (readl(devpriv->mmio + S626_P_I2CCTRL) >> 16) & 0xff;
+}
+
+/* *********** DAC FUNCTIONS *********** */
+
+/* TrimDac LogicalChan-to-PhysicalChan mapping table. */
+static const uint8_t s626_trimchan[] = { 10, 9, 8, 3, 2, 7, 6, 1, 0, 5, 4 };
+
+/* TrimDac LogicalChan-to-EepromAdrs mapping table. */
+static const uint8_t s626_trimadrs[] = {
+ 0x40, 0x41, 0x42, 0x50, 0x51, 0x52, 0x53, 0x60, 0x61, 0x62, 0x63
+};
+
+enum {
+ s626_send_dac_wait_not_mc1_a2out,
+ s626_send_dac_wait_ssr_af2_out,
+ s626_send_dac_wait_fb_buffer2_msb_00,
+ s626_send_dac_wait_fb_buffer2_msb_ff
+};
+
+static int s626_send_dac_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int status;
+
+ switch (context) {
+ case s626_send_dac_wait_not_mc1_a2out:
+ status = readl(devpriv->mmio + S626_P_MC1);
+ if (!(status & S626_MC1_A2OUT))
+ return 0;
+ break;
+ case s626_send_dac_wait_ssr_af2_out:
+ status = readl(devpriv->mmio + S626_P_SSR);
+ if (status & S626_SSR_AF2_OUT)
+ return 0;
+ break;
+ case s626_send_dac_wait_fb_buffer2_msb_00:
+ status = readl(devpriv->mmio + S626_P_FB_BUFFER2);
+ if (!(status & 0xff000000))
+ return 0;
+ break;
+ case s626_send_dac_wait_fb_buffer2_msb_ff:
+ status = readl(devpriv->mmio + S626_P_FB_BUFFER2);
+ if (status & 0xff000000)
+ return 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return -EBUSY;
+}
+
+/*
+ * 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 int s626_send_dac(struct comedi_device *dev, uint32_t val)
+{
+ struct s626_private *devpriv = dev->private;
+ int ret;
+
+ /* 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 s626_debi_write 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.
+ */
+ s626_debi_write(dev, S626_LP_DACPOL, devpriv->dacpol);
+
+ /* TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- */
+
+ /* Copy DAC setpoint value to DAC's output DMA buffer. */
+ /* writel(val, devpriv->mmio + (uint32_t)devpriv->dac_wbuf); */
+ *devpriv->dac_wbuf = val;
+
+ /*
+ * Enable 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.
+ */
+ s626_mc_enable(dev, S626_MC1_A2OUT, S626_P_MC1);
+
+ /* 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.
+ */
+ writel(S626_ISR_AFOU, devpriv->mmio + S626_P_ISR);
+
+ /*
+ * 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.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_not_mc1_a2out);
+ if (ret) {
+ comedi_error(dev, "DMA transfer timeout.");
+ return ret;
+ }
+
+ /* 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.
+ */
+ writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2,
+ devpriv->mmio + S626_VECTPORT(0));
+
+ /*
+ * 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.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_ssr_af2_out);
+ if (ret) {
+ comedi_error(dev, "TSL timeout waiting for slot 1 to execute.");
+ return ret;
+ }
+
+ /*
+ * 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.
+ */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_RSD2 | S626_SIB_A2 | S626_EOS,
+ devpriv->mmio + S626_VECTPORT(0));
+
+ /* 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 (readl(devpriv->mmio + S626_P_FB_BUFFER2) & 0xff000000) {
+ /*
+ * 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.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_fb_buffer2_msb_00);
+ if (ret) {
+ comedi_error(dev,
+ "TSL timeout waiting for slot 0 to execute.");
+ return ret;
+ }
+ }
+ /*
+ * 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.
+ */
+ writel(S626_RSD3 | S626_SIB_A2 | S626_EOS,
+ devpriv->mmio + S626_VECTPORT(0));
+
+ /*
+ * 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.
+ */
+ ret = comedi_timeout(dev, NULL, NULL, s626_send_dac_eoc,
+ s626_send_dac_wait_fb_buffer2_msb_ff);
+ if (ret) {
+ comedi_error(dev, "TSL timeout waiting for slot 0 to execute.");
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Private helper function: Write setpoint to an application DAC channel.
+ */
+static int s626_set_dac(struct comedi_device *dev, uint16_t chan,
+ int16_t dacdata)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t signmask;
+ uint32_t ws_image;
+ uint32_t val;
+
+ /*
+ * 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.
+ */
+
+ /* Choose DAC chip select to be asserted */
+ ws_image = (chan & 2) ? S626_WS1 : S626_WS2;
+ /* Slot 2: Transmit high data byte to target DAC */
+ writel(S626_XSD2 | S626_XFIFO_1 | ws_image,
+ devpriv->mmio + S626_VECTPORT(2));
+ /* Slot 3: Transmit low data byte to target DAC */
+ writel(S626_XSD2 | S626_XFIFO_0 | ws_image,
+ devpriv->mmio + S626_VECTPORT(3));
+ /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */
+ writel(S626_XSD2 | S626_XFIFO_3 | S626_WS3,
+ devpriv->mmio + S626_VECTPORT(4));
+ /* Slot 5: running after writing target DAC's low data byte */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_WS3 | S626_EOS,
+ devpriv->mmio + S626_VECTPORT(5));
+
+ /*
+ * 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.
+ */
+ val = 0x0F000000; /* Continue clock after target DAC data
+ * (write to non-existent trimdac). */
+ val |= 0x00004000; /* Address the two main dual-DAC devices
+ * (TSL's chip select enables target device). */
+ val |= ((uint32_t)(chan & 1) << 15); /* Address the DAC channel
+ * within the device. */
+ val |= (uint32_t)dacdata; /* Include DAC setpoint data. */
+ return s626_send_dac(dev, val);
+}
+
+static int s626_write_trim_dac(struct comedi_device *dev, uint8_t logical_chan,
+ uint8_t dac_data)
+{
+ struct s626_private *devpriv = dev->private;
+ uint32_t chan;
+
+ /*
+ * Save the new setpoint in case the application needs to read it back
+ * later.
+ */
+ devpriv->trim_setpoint[logical_chan] = (uint8_t)dac_data;
+
+ /* Map logical channel number to physical channel number. */
+ chan = s626_trimchan[logical_chan];
+
+ /*
+ * 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.
+ */
+
+ /* Slot 2: Send high uint8_t to target TrimDac */
+ writel(S626_XSD2 | S626_XFIFO_1 | S626_WS3,
+ devpriv->mmio + S626_VECTPORT(2));
+ /* Slot 3: Send low uint8_t to target TrimDac */
+ writel(S626_XSD2 | S626_XFIFO_0 | S626_WS3,
+ devpriv->mmio + S626_VECTPORT(3));
+ /* Slot 4: Send NOP high uint8_t to DAC0 to keep clock running */
+ writel(S626_XSD2 | S626_XFIFO_3 | S626_WS1,
+ devpriv->mmio + S626_VECTPORT(4));
+ /* Slot 5: Send NOP low uint8_t to DAC0 */
+ writel(S626_XSD2 | S626_XFIFO_2 | S626_WS1 | S626_EOS,
+ devpriv->mmio + S626_VECTPORT(5));
+
+ /*
+ * 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.
+ * Include DAC setpoint data.
+ */
+ return s626_send_dac(dev, (chan << 8) | dac_data);
+}
+
+static int s626_load_trim_dacs(struct comedi_device *dev)
+{
+ uint8_t i;
+ int ret;
+
+ /* Copy TrimDac setpoint values from EEPROM to TrimDacs. */
+ for (i = 0; i < ARRAY_SIZE(s626_trimchan); i++) {
+ ret = s626_write_trim_dac(dev, i,
+ s626_i2c_read(dev, s626_trimadrs[i]));
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* ****** 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.
+ */
+
+/*
+ * Read a counter's output latch.
+ */
+static uint32_t s626_read_latch(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ uint32_t value;
+
+ /* Latch counts and fetch LSW of latched counts value. */
+ value = s626_debi_read(dev, k->my_latch_lsw);
+
+ /* Fetch MSW of latched counts and combine with LSW. */
+ value |= ((uint32_t)s626_debi_read(dev, k->my_latch_lsw + 2) << 16);
+
+ /* Return latched counts. */
+ return value;
+}
+
+/*
+ * 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 s626_set_latch_source(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ s626_debi_replace(dev, k->my_crb,
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_LATCHSRC),
+ S626_SET_CRB_LATCHSRC(value));
+}
+
+/*
+ * Write value into counter preload register.
+ */
+static void s626_preload(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint32_t value)
+{
+ s626_debi_write(dev, k->my_latch_lsw, value);
+ s626_debi_write(dev, k->my_latch_lsw + 2, value >> 16);
+}
+
+/* ****** PRIVATE COUNTER FUNCTIONS ****** */
+
+/*
+ * Reset a counter's index and overflow event capture flags.
+ */
+static void s626_reset_cap_flags_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL,
+ (S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_A(1)));
+}
+
+static void s626_reset_cap_flags_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL,
+ (S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_B(1)));
+}
+
+/*
+ * Return counter setup in a format (COUNTER_SETUP) that is consistent
+ * for both A and B counters.
+ */
+static uint16_t s626_get_mode_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ uint16_t cra;
+ uint16_t crb;
+ uint16_t setup;
+ unsigned cntsrc, clkmult, clkpol, encmode;
+
+ /* Fetch CRA and CRB register images. */
+ cra = s626_debi_read(dev, k->my_cra);
+ crb = s626_debi_read(dev, k->my_crb);
+
+ /*
+ * Populate the standardized counter setup bit fields.
+ */
+ setup =
+ /* LoadSrc = LoadSrcA. */
+ S626_SET_STD_LOADSRC(S626_GET_CRA_LOADSRC_A(cra)) |
+ /* LatchSrc = LatchSrcA. */
+ S626_SET_STD_LATCHSRC(S626_GET_CRB_LATCHSRC(crb)) |
+ /* IntSrc = IntSrcA. */
+ S626_SET_STD_INTSRC(S626_GET_CRA_INTSRC_A(cra)) |
+ /* IndxSrc = IndxSrcA. */
+ S626_SET_STD_INDXSRC(S626_GET_CRA_INDXSRC_A(cra)) |
+ /* IndxPol = IndxPolA. */
+ S626_SET_STD_INDXPOL(S626_GET_CRA_INDXPOL_A(cra)) |
+ /* ClkEnab = ClkEnabA. */
+ S626_SET_STD_CLKENAB(S626_GET_CRB_CLKENAB_A(crb));
+
+ /* Adjust mode-dependent parameters. */
+ cntsrc = S626_GET_CRA_CNTSRC_A(cra);
+ if (cntsrc & S626_CNTSRC_SYSCLK) {
+ /* Timer mode (CntSrcA<1> == 1): */
+ encmode = S626_ENCMODE_TIMER;
+ /* Set ClkPol to indicate count direction (CntSrcA<0>). */
+ clkpol = cntsrc & 1;
+ /* ClkMult must be 1x in Timer mode. */
+ clkmult = S626_CLKMULT_1X;
+ } else {
+ /* Counter mode (CntSrcA<1> == 0): */
+ encmode = S626_ENCMODE_COUNTER;
+ /* Pass through ClkPol. */
+ clkpol = S626_GET_CRA_CLKPOL_A(cra);
+ /* Force ClkMult to 1x if not legal, else pass through. */
+ clkmult = S626_GET_CRA_CLKMULT_A(cra);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ }
+ setup |= S626_SET_STD_ENCMODE(encmode) | S626_SET_STD_CLKMULT(clkmult) |
+ S626_SET_STD_CLKPOL(clkpol);
+
+ /* Return adjusted counter setup. */
+ return setup;
+}
+
+static uint16_t s626_get_mode_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ uint16_t cra;
+ uint16_t crb;
+ uint16_t setup;
+ unsigned cntsrc, clkmult, clkpol, encmode;
+
+ /* Fetch CRA and CRB register images. */
+ cra = s626_debi_read(dev, k->my_cra);
+ crb = s626_debi_read(dev, k->my_crb);
+
+ /*
+ * Populate the standardized counter setup bit fields.
+ */
+ setup =
+ /* IntSrc = IntSrcB. */
+ S626_SET_STD_INTSRC(S626_GET_CRB_INTSRC_B(crb)) |
+ /* LatchSrc = LatchSrcB. */
+ S626_SET_STD_LATCHSRC(S626_GET_CRB_LATCHSRC(crb)) |
+ /* LoadSrc = LoadSrcB. */
+ S626_SET_STD_LOADSRC(S626_GET_CRB_LOADSRC_B(crb)) |
+ /* IndxPol = IndxPolB. */
+ S626_SET_STD_INDXPOL(S626_GET_CRB_INDXPOL_B(crb)) |
+ /* ClkEnab = ClkEnabB. */
+ S626_SET_STD_CLKENAB(S626_GET_CRB_CLKENAB_B(crb)) |
+ /* IndxSrc = IndxSrcB. */
+ S626_SET_STD_INDXSRC(S626_GET_CRA_INDXSRC_B(cra));
+
+ /* Adjust mode-dependent parameters. */
+ cntsrc = S626_GET_CRA_CNTSRC_B(cra);
+ clkmult = S626_GET_CRB_CLKMULT_B(crb);
+ if (clkmult == S626_CLKMULT_SPECIAL) {
+ /* Extender mode (ClkMultB == S626_CLKMULT_SPECIAL): */
+ encmode = S626_ENCMODE_EXTENDER;
+ /* Indicate multiplier is 1x. */
+ clkmult = S626_CLKMULT_1X;
+ /* Set ClkPol equal to Timer count direction (CntSrcB<0>). */
+ clkpol = cntsrc & 1;
+ } else if (cntsrc & S626_CNTSRC_SYSCLK) {
+ /* Timer mode (CntSrcB<1> == 1): */
+ encmode = S626_ENCMODE_TIMER;
+ /* Indicate multiplier is 1x. */
+ clkmult = S626_CLKMULT_1X;
+ /* Set ClkPol equal to Timer count direction (CntSrcB<0>). */
+ clkpol = cntsrc & 1;
+ } else {
+ /* If Counter mode (CntSrcB<1> == 0): */
+ encmode = S626_ENCMODE_COUNTER;
+ /* Clock multiplier is passed through. */
+ /* Clock polarity is passed through. */
+ clkpol = S626_GET_CRB_CLKPOL_B(crb);
+ }
+ setup |= S626_SET_STD_ENCMODE(encmode) | S626_SET_STD_CLKMULT(clkmult) |
+ S626_SET_STD_CLKPOL(clkpol);
+
+ /* 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 s626_set_mode_a(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t setup,
+ uint16_t disable_int_src)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t cra;
+ uint16_t crb;
+ unsigned cntsrc, clkmult, clkpol;
+
+ /* Initialize CRA and CRB images. */
+ /* Preload trigger is passed through. */
+ cra = S626_SET_CRA_LOADSRC_A(S626_GET_STD_LOADSRC(setup));
+ /* IndexSrc is passed through. */
+ cra |= S626_SET_CRA_INDXSRC_A(S626_GET_STD_INDXSRC(setup));
+
+ /* Reset any pending CounterA event captures. */
+ crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_A(1);
+ /* Clock enable is passed through. */
+ crb |= S626_SET_CRB_CLKENAB_A(S626_GET_STD_CLKENAB(setup));
+
+ /* Force IntSrc to Disabled if disable_int_src is asserted. */
+ if (!disable_int_src)
+ cra |= S626_SET_CRA_INTSRC_A(S626_GET_STD_INTSRC(setup));
+
+ /* Populate all mode-dependent attributes of CRA & CRB images. */
+ clkpol = S626_GET_STD_CLKPOL(setup);
+ switch (S626_GET_STD_ENCMODE(setup)) {
+ case S626_ENCMODE_EXTENDER: /* Extender Mode: */
+ /* Force to Timer mode (Extender valid only for B counters). */
+ /* Fall through to case S626_ENCMODE_TIMER: */
+ case S626_ENCMODE_TIMER: /* Timer Mode: */
+ /* CntSrcA<1> selects system clock */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* Count direction (CntSrcA<0>) obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolA behaves as always-on clock enable. */
+ clkpol = 1;
+ /* ClkMult must be 1x. */
+ clkmult = S626_CLKMULT_1X;
+ break;
+ default: /* Counter Mode: */
+ /* Select ENC_C and ENC_D as clock/direction inputs. */
+ cntsrc = S626_CNTSRC_ENCODER;
+ /* Clock polarity is passed through. */
+ /* Force multiplier to x1 if not legal, else pass through. */
+ clkmult = S626_GET_STD_CLKMULT(setup);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ break;
+ }
+ cra |= S626_SET_CRA_CNTSRC_A(cntsrc) | S626_SET_CRA_CLKPOL_A(clkpol) |
+ S626_SET_CRA_CLKMULT_A(clkmult);
+
+ /*
+ * Force positive index polarity if IndxSrc is software-driven only,
+ * otherwise pass it through.
+ */
+ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
+ cra |= S626_SET_CRA_INDXPOL_A(S626_GET_STD_INDXPOL(setup));
+
+ /*
+ * If IntSrc has been forced to Disabled, update the MISC2 interrupt
+ * enable mask to indicate the counter interrupt is disabled.
+ */
+ if (disable_int_src)
+ devpriv->counter_int_enabs &= ~k->my_event_bits[3];
+
+ /*
+ * While retaining CounterB and LatchSrc configurations, program the
+ * new counter operating mode.
+ */
+ s626_debi_replace(dev, k->my_cra,
+ S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B, cra);
+ s626_debi_replace(dev, k->my_crb,
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A), crb);
+}
+
+static void s626_set_mode_b(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t setup,
+ uint16_t disable_int_src)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t cra;
+ uint16_t crb;
+ unsigned cntsrc, clkmult, clkpol;
+
+ /* Initialize CRA and CRB images. */
+ /* IndexSrc is passed through. */
+ cra = S626_SET_CRA_INDXSRC_B(S626_GET_STD_INDXSRC(setup));
+
+ /* Reset event captures and disable interrupts. */
+ crb = S626_SET_CRB_INTRESETCMD(1) | S626_SET_CRB_INTRESET_B(1);
+ /* Clock enable is passed through. */
+ crb |= S626_SET_CRB_CLKENAB_B(S626_GET_STD_CLKENAB(setup));
+ /* Preload trigger source is passed through. */
+ crb |= S626_SET_CRB_LOADSRC_B(S626_GET_STD_LOADSRC(setup));
+
+ /* Force IntSrc to Disabled if disable_int_src is asserted. */
+ if (!disable_int_src)
+ crb |= S626_SET_CRB_INTSRC_B(S626_GET_STD_INTSRC(setup));
+
+ /* Populate all mode-dependent attributes of CRA & CRB images. */
+ clkpol = S626_GET_STD_CLKPOL(setup);
+ switch (S626_GET_STD_ENCMODE(setup)) {
+ case S626_ENCMODE_TIMER: /* Timer Mode: */
+ /* CntSrcB<1> selects system clock */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* with direction (CntSrcB<0>) obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolB behaves as always-on clock enable. */
+ clkpol = 1;
+ /* ClkMultB must be 1x. */
+ clkmult = S626_CLKMULT_1X;
+ break;
+ case S626_ENCMODE_EXTENDER: /* Extender Mode: */
+ /* CntSrcB source is OverflowA (same as "timer") */
+ cntsrc = S626_CNTSRC_SYSCLK;
+ /* with direction obtained from ClkPol. */
+ cntsrc |= clkpol;
+ /* ClkPolB controls IndexB -- always set to active. */
+ clkpol = 1;
+ /* ClkMultB selects OverflowA as the clock source. */
+ clkmult = S626_CLKMULT_SPECIAL;
+ break;
+ default: /* Counter Mode: */
+ /* Select ENC_C and ENC_D as clock/direction inputs. */
+ cntsrc = S626_CNTSRC_ENCODER;
+ /* ClkPol is passed through. */
+ /* Force ClkMult to x1 if not legal, otherwise pass through. */
+ clkmult = S626_GET_STD_CLKMULT(setup);
+ if (clkmult == S626_CLKMULT_SPECIAL)
+ clkmult = S626_CLKMULT_1X;
+ break;
+ }
+ cra |= S626_SET_CRA_CNTSRC_B(cntsrc);
+ crb |= S626_SET_CRB_CLKPOL_B(clkpol) | S626_SET_CRB_CLKMULT_B(clkmult);
+
+ /*
+ * Force positive index polarity if IndxSrc is software-driven only,
+ * otherwise pass it through.
+ */
+ if (S626_GET_STD_INDXSRC(setup) != S626_INDXSRC_SOFT)
+ crb |= S626_SET_CRB_INDXPOL_B(S626_GET_STD_INDXPOL(setup));
+
+ /*
+ * If IntSrc has been forced to Disabled, update the MISC2 interrupt
+ * enable mask to indicate the counter interrupt is disabled.
+ */
+ if (disable_int_src)
+ devpriv->counter_int_enabs &= ~k->my_event_bits[3];
+
+ /*
+ * While retaining CounterA and LatchSrc configurations, program the
+ * new counter operating mode.
+ */
+ s626_debi_replace(dev, k->my_cra,
+ ~(S626_CRAMSK_INDXSRC_B | S626_CRAMSK_CNTSRC_B), cra);
+ s626_debi_replace(dev, k->my_crb,
+ S626_CRBMSK_CLKENAB_A | S626_CRBMSK_LATCHSRC, crb);
+}
+
+/*
+ * Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index.
+ */
+static void s626_set_enable_a(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t enab)
+{
+ s626_debi_replace(dev, k->my_crb,
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_A),
+ S626_SET_CRB_CLKENAB_A(enab));
+}
+
+static void s626_set_enable_b(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t enab)
+{
+ s626_debi_replace(dev, k->my_crb,
+ ~(S626_CRBMSK_INTCTRL | S626_CRBMSK_CLKENAB_B),
+ S626_SET_CRB_CLKENAB_B(enab));
+}
+
+static uint16_t s626_get_enable_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRB_CLKENAB_A(s626_debi_read(dev, k->my_crb));
+}
+
+static uint16_t s626_get_enable_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRB_CLKENAB_B(s626_debi_read(dev, k->my_crb));
+}
+
+#ifdef unused
+static uint16_t s626_get_latch_source(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRB_LATCHSRC(s626_debi_read(dev, k->my_crb));
+}
+#endif
+
+/*
+ * 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 s626_set_load_trig_a(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t trig)
+{
+ s626_debi_replace(dev, k->my_cra, ~S626_CRAMSK_LOADSRC_A,
+ S626_SET_CRA_LOADSRC_A(trig));
+}
+
+static void s626_set_load_trig_b(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t trig)
+{
+ s626_debi_replace(dev, k->my_crb,
+ ~(S626_CRBMSK_LOADSRC_B | S626_CRBMSK_INTCTRL),
+ S626_SET_CRB_LOADSRC_B(trig));
+}
+
+static uint16_t s626_get_load_trig_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRA_LOADSRC_A(s626_debi_read(dev, k->my_cra));
+}
+
+static uint16_t s626_get_load_trig_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRB_LOADSRC_B(s626_debi_read(dev, k->my_crb));
+}
+
+/*
+ * Return/set counter interrupt source and clear any captured
+ * index/overflow events. int_source: 0=Disabled, 1=OverflowOnly,
+ * 2=IndexOnly, 3=IndexAndOverflow.
+ */
+static void s626_set_int_src_a(struct comedi_device *dev,
+ const struct s626_enc_info *k,
+ uint16_t int_source)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /* Reset any pending counter overflow or index captures. */
+ s626_debi_replace(dev, k->my_crb, ~S626_CRBMSK_INTCTRL,
+ (S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_A(1)));
+
+ /* Program counter interrupt source. */
+ s626_debi_replace(dev, k->my_cra, ~S626_CRAMSK_INTSRC_A,
+ S626_SET_CRA_INTSRC_A(int_source));
+
+ /* Update MISC2 interrupt enable mask. */
+ devpriv->counter_int_enabs =
+ (devpriv->counter_int_enabs & ~k->my_event_bits[3]) |
+ k->my_event_bits[int_source];
+}
+
+static void s626_set_int_src_b(struct comedi_device *dev,
+ const struct s626_enc_info *k,
+ uint16_t int_source)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t crb;
+
+ /* Cache writeable CRB register image. */
+ crb = s626_debi_read(dev, k->my_crb) & ~S626_CRBMSK_INTCTRL;
+
+ /* Reset any pending counter overflow or index captures. */
+ s626_debi_write(dev, k->my_crb, (crb | S626_SET_CRB_INTRESETCMD(1) |
+ S626_SET_CRB_INTRESET_B(1)));
+
+ /* Program counter interrupt source. */
+ s626_debi_write(dev, k->my_crb, ((crb & ~S626_CRBMSK_INTSRC_B) |
+ S626_SET_CRB_INTSRC_B(int_source)));
+
+ /* Update MISC2 interrupt enable mask. */
+ devpriv->counter_int_enabs =
+ (devpriv->counter_int_enabs & ~k->my_event_bits[3]) |
+ k->my_event_bits[int_source];
+}
+
+static uint16_t s626_get_int_src_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRA_INTSRC_A(s626_debi_read(dev, k->my_cra));
+}
+
+static uint16_t s626_get_int_src_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_CRB_INTSRC_B(s626_debi_read(dev, k->my_crb));
+}
+
+#ifdef unused
+/*
+ * Return/set the clock multiplier.
+ */
+static void s626_set_clk_mult(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_CLKMULT) |
+ S626_SET_STD_CLKMULT(value)), false);
+}
+
+static uint16_t s626_get_clk_mult(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_STD_CLKMULT(k->get_mode(dev, k));
+}
+
+/*
+ * Return/set the clock polarity.
+ */
+static void s626_set_clk_pol(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_CLKPOL) |
+ S626_SET_STD_CLKPOL(value)), false);
+}
+
+static uint16_t s626_get_clk_pol(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_STD_CLKPOL(k->get_mode(dev, k));
+}
+
+/*
+ * Return/set the encoder mode.
+ */
+static void s626_set_enc_mode(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_ENCMODE) |
+ S626_SET_STD_ENCMODE(value)), false);
+}
+
+static uint16_t s626_get_enc_mode(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_STD_ENCMODE(k->get_mode(dev, k));
+}
+
+/*
+ * Return/set the index polarity.
+ */
+static void s626_set_index_pol(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_INDXPOL) |
+ S626_SET_STD_INDXPOL(value != 0)), false);
+}
+
+static uint16_t s626_get_index_pol(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_STD_INDXPOL(k->get_mode(dev, k));
+}
+
+/*
+ * Return/set the index source.
+ */
+static void s626_set_index_src(struct comedi_device *dev,
+ const struct s626_enc_info *k, uint16_t value)
+{
+ k->set_mode(dev, k, ((k->get_mode(dev, k) & ~S626_STDMSK_INDXSRC) |
+ S626_SET_STD_INDXSRC(value != 0)), false);
+}
+
+static uint16_t s626_get_index_src(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ return S626_GET_STD_INDXSRC(k->get_mode(dev, k));
+}
+#endif
+
+/*
+ * Generate an index pulse.
+ */
+static void s626_pulse_index_a(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ uint16_t cra;
+
+ cra = s626_debi_read(dev, k->my_cra);
+ /* Pulse index. */
+ s626_debi_write(dev, k->my_cra, (cra ^ S626_CRAMSK_INDXPOL_A));
+ s626_debi_write(dev, k->my_cra, cra);
+}
+
+static void s626_pulse_index_b(struct comedi_device *dev,
+ const struct s626_enc_info *k)
+{
+ uint16_t crb;
+
+ crb = s626_debi_read(dev, k->my_crb) & ~S626_CRBMSK_INTCTRL;
+ /* Pulse index. */
+ s626_debi_write(dev, k->my_crb, (crb ^ S626_CRBMSK_INDXPOL_B));
+ s626_debi_write(dev, k->my_crb, crb);
+}
+
+static const struct s626_enc_info s626_enc_chan_info[] = {
+ {
+ .get_enable = s626_get_enable_a,
+ .get_int_src = s626_get_int_src_a,
+ .get_load_trig = s626_get_load_trig_a,
+ .get_mode = s626_get_mode_a,
+ .pulse_index = s626_pulse_index_a,
+ .set_enable = s626_set_enable_a,
+ .set_int_src = s626_set_int_src_a,
+ .set_load_trig = s626_set_load_trig_a,
+ .set_mode = s626_set_mode_a,
+ .reset_cap_flags = s626_reset_cap_flags_a,
+ .my_cra = S626_LP_CR0A,
+ .my_crb = S626_LP_CR0B,
+ .my_latch_lsw = S626_LP_CNTR0ALSW,
+ .my_event_bits = S626_EVBITS(0),
+ }, {
+ .get_enable = s626_get_enable_a,
+ .get_int_src = s626_get_int_src_a,
+ .get_load_trig = s626_get_load_trig_a,
+ .get_mode = s626_get_mode_a,
+ .pulse_index = s626_pulse_index_a,
+ .set_enable = s626_set_enable_a,
+ .set_int_src = s626_set_int_src_a,
+ .set_load_trig = s626_set_load_trig_a,
+ .set_mode = s626_set_mode_a,
+ .reset_cap_flags = s626_reset_cap_flags_a,
+ .my_cra = S626_LP_CR1A,
+ .my_crb = S626_LP_CR1B,
+ .my_latch_lsw = S626_LP_CNTR1ALSW,
+ .my_event_bits = S626_EVBITS(1),
+ }, {
+ .get_enable = s626_get_enable_a,
+ .get_int_src = s626_get_int_src_a,
+ .get_load_trig = s626_get_load_trig_a,
+ .get_mode = s626_get_mode_a,
+ .pulse_index = s626_pulse_index_a,
+ .set_enable = s626_set_enable_a,
+ .set_int_src = s626_set_int_src_a,
+ .set_load_trig = s626_set_load_trig_a,
+ .set_mode = s626_set_mode_a,
+ .reset_cap_flags = s626_reset_cap_flags_a,
+ .my_cra = S626_LP_CR2A,
+ .my_crb = S626_LP_CR2B,
+ .my_latch_lsw = S626_LP_CNTR2ALSW,
+ .my_event_bits = S626_EVBITS(2),
+ }, {
+ .get_enable = s626_get_enable_b,
+ .get_int_src = s626_get_int_src_b,
+ .get_load_trig = s626_get_load_trig_b,
+ .get_mode = s626_get_mode_b,
+ .pulse_index = s626_pulse_index_b,
+ .set_enable = s626_set_enable_b,
+ .set_int_src = s626_set_int_src_b,
+ .set_load_trig = s626_set_load_trig_b,
+ .set_mode = s626_set_mode_b,
+ .reset_cap_flags = s626_reset_cap_flags_b,
+ .my_cra = S626_LP_CR0A,
+ .my_crb = S626_LP_CR0B,
+ .my_latch_lsw = S626_LP_CNTR0BLSW,
+ .my_event_bits = S626_EVBITS(3),
+ }, {
+ .get_enable = s626_get_enable_b,
+ .get_int_src = s626_get_int_src_b,
+ .get_load_trig = s626_get_load_trig_b,
+ .get_mode = s626_get_mode_b,
+ .pulse_index = s626_pulse_index_b,
+ .set_enable = s626_set_enable_b,
+ .set_int_src = s626_set_int_src_b,
+ .set_load_trig = s626_set_load_trig_b,
+ .set_mode = s626_set_mode_b,
+ .reset_cap_flags = s626_reset_cap_flags_b,
+ .my_cra = S626_LP_CR1A,
+ .my_crb = S626_LP_CR1B,
+ .my_latch_lsw = S626_LP_CNTR1BLSW,
+ .my_event_bits = S626_EVBITS(4),
+ }, {
+ .get_enable = s626_get_enable_b,
+ .get_int_src = s626_get_int_src_b,
+ .get_load_trig = s626_get_load_trig_b,
+ .get_mode = s626_get_mode_b,
+ .pulse_index = s626_pulse_index_b,
+ .set_enable = s626_set_enable_b,
+ .set_int_src = s626_set_int_src_b,
+ .set_load_trig = s626_set_load_trig_b,
+ .set_mode = s626_set_mode_b,
+ .reset_cap_flags = s626_reset_cap_flags_b,
+ .my_cra = S626_LP_CR2A,
+ .my_crb = S626_LP_CR2B,
+ .my_latch_lsw = S626_LP_CNTR2BLSW,
+ .my_event_bits = S626_EVBITS(5),
+ },
+};
+
+static unsigned int s626_ai_reg_to_uint(unsigned int data)
+{
+ return ((data >> 18) & 0x3fff) ^ 0x2000;
+}
+
+static int s626_dio_set_irq(struct comedi_device *dev, unsigned int chan)
+{
+ unsigned int group = chan / 16;
+ unsigned int mask = 1 << (chan - (16 * group));
+ unsigned int status;
+
+ /* set channel to capture positive edge */
+ status = s626_debi_read(dev, S626_LP_RDEDGSEL(group));
+ s626_debi_write(dev, S626_LP_WREDGSEL(group), mask | status);
+
+ /* enable interrupt on selected channel */
+ status = s626_debi_read(dev, S626_LP_RDINTSEL(group));
+ s626_debi_write(dev, S626_LP_WRINTSEL(group), mask | status);
+
+ /* enable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_EDCAP);
+
+ /* enable edge capture on selected channel */
+ status = s626_debi_read(dev, S626_LP_RDCAPSEL(group));
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask | status);
+
+ return 0;
+}
+
+static int s626_dio_reset_irq(struct comedi_device *dev, unsigned int group,
+ unsigned int mask)
+{
+ /* disable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* enable edge capture on selected channel */
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), mask);
+
+ return 0;
+}
+
+static int s626_dio_clear_irq(struct comedi_device *dev)
+{
+ unsigned int group;
+
+ /* disable edge capture write command */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* clear all dio pending events and interrupt */
+ for (group = 0; group < S626_DIO_BANKS; group++)
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff);
+
+ return 0;
+}
+
+static void s626_handle_dio_interrupt(struct comedi_device *dev,
+ uint16_t irqbit, uint8_t group)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ s626_dio_reset_irq(dev, group, irqbit);
+
+ 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) {
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ if (cmd->scan_begin_src == TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->scan_begin_arg);
+ }
+ if ((irqbit >> (cmd->scan_begin_arg - (16 * group))) == 1 &&
+ cmd->scan_begin_src == TRIG_EXT) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ if (cmd->convert_src == TRIG_EXT) {
+ devpriv->ai_convert_count = cmd->chanlist_len;
+
+ s626_dio_set_irq(dev, cmd->convert_arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ const struct s626_enc_info *k =
+ &s626_enc_chan_info[5];
+
+ devpriv->ai_convert_count = cmd->chanlist_len;
+ k->set_enable(dev, k, S626_CLKENAB_ALWAYS);
+ }
+ }
+ if ((irqbit >> (cmd->convert_arg - (16 * group))) == 1 &&
+ cmd->convert_src == TRIG_EXT) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ devpriv->ai_convert_count--;
+ if (devpriv->ai_convert_count > 0)
+ s626_dio_set_irq(dev, cmd->convert_arg);
+ }
+ }
+}
+
+static void s626_check_dio_interrupts(struct comedi_device *dev)
+{
+ uint16_t irqbit;
+ uint8_t group;
+
+ for (group = 0; group < S626_DIO_BANKS; group++) {
+ irqbit = 0;
+ /* read interrupt type */
+ irqbit = s626_debi_read(dev, S626_LP_RDCAPFLG(group));
+
+ /* check if interrupt is generated from dio channels */
+ if (irqbit) {
+ s626_handle_dio_interrupt(dev, irqbit, group);
+ return;
+ }
+ }
+}
+
+static void s626_check_counter_interrupts(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ const struct s626_enc_info *k;
+ uint16_t irqbit;
+
+ /* read interrupt type */
+ irqbit = s626_debi_read(dev, S626_LP_RDMISC2);
+
+ /* check interrupt on counters */
+ if (irqbit & S626_IRQ_COINT1A) {
+ k = &s626_enc_chan_info[0];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+ }
+ if (irqbit & S626_IRQ_COINT2A) {
+ k = &s626_enc_chan_info[1];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+ }
+ if (irqbit & S626_IRQ_COINT3A) {
+ k = &s626_enc_chan_info[2];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+ }
+ if (irqbit & S626_IRQ_COINT1B) {
+ k = &s626_enc_chan_info[3];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+ }
+ if (irqbit & S626_IRQ_COINT2B) {
+ k = &s626_enc_chan_info[4];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+
+ if (devpriv->ai_convert_count > 0) {
+ devpriv->ai_convert_count--;
+ if (devpriv->ai_convert_count == 0)
+ k->set_enable(dev, k, S626_CLKENAB_INDEX);
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS,
+ S626_P_MC2);
+ }
+ }
+ }
+ if (irqbit & S626_IRQ_COINT3B) {
+ k = &s626_enc_chan_info[5];
+
+ /* clear interrupt capture flag */
+ k->reset_cap_flags(dev, k);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ k = &s626_enc_chan_info[4];
+ devpriv->ai_convert_count = cmd->chanlist_len;
+ k->set_enable(dev, k, S626_CLKENAB_ALWAYS);
+ }
+ }
+}
+
+static bool s626_handle_eos_interrupt(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &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.
+ */
+ uint32_t *readaddr = (uint32_t *)devpriv->ana_buf.logical_base + 1;
+ bool finished = false;
+ int i;
+
+ /* get the data and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned short tempdata;
+
+ /*
+ * Convert ADC data to 16-bit integer values and copy
+ * to application buffer.
+ */
+ tempdata = s626_ai_reg_to_uint(*readaddr);
+ readaddr++;
+
+ /* put data into read buffer */
+ cfc_write_to_buffer(s, tempdata);
+ }
+
+ /* end of scan occurs */
+ async->events |= COMEDI_CB_EOS;
+
+ if (!devpriv->ai_continuous)
+ devpriv->ai_sample_count--;
+ if (devpriv->ai_sample_count <= 0) {
+ devpriv->ai_cmd_running = 0;
+
+ /* Stop RPS program */
+ s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ /* send end of acquisition */
+ async->events |= COMEDI_CB_EOA;
+
+ /* disable master interrupt */
+ finished = true;
+ }
+
+ if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT)
+ s626_dio_set_irq(dev, cmd->scan_begin_arg);
+
+ /* tell comedi that data is there */
+ comedi_event(dev, s);
+
+ return finished;
+}
+
+static irqreturn_t s626_irq_handler(int irq, void *d)
+{
+ struct comedi_device *dev = d;
+ struct s626_private *devpriv = dev->private;
+ unsigned long flags;
+ uint32_t irqtype, irqstatus;
+
+ if (!dev->attached)
+ return IRQ_NONE;
+ /* lock to avoid race with comedi_poll */
+ spin_lock_irqsave(&dev->spinlock, flags);
+
+ /* save interrupt enable register state */
+ irqstatus = readl(devpriv->mmio + S626_P_IER);
+
+ /* read interrupt type */
+ irqtype = readl(devpriv->mmio + S626_P_ISR);
+
+ /* disable master interrupt */
+ writel(0, devpriv->mmio + S626_P_IER);
+
+ /* clear interrupt */
+ writel(irqtype, devpriv->mmio + S626_P_ISR);
+
+ switch (irqtype) {
+ case S626_IRQ_RPS1: /* end_of_scan occurs */
+ if (s626_handle_eos_interrupt(dev))
+ irqstatus = 0;
+ break;
+ case S626_IRQ_GPIO3: /* check dio and counter interrupt */
+ /* s626_dio_clear_irq(dev); */
+ s626_check_dio_interrupts(dev);
+ s626_check_counter_interrupts(dev);
+ break;
+ }
+
+ /* enable interrupt */
+ writel(irqstatus, devpriv->mmio + S626_P_IER);
+
+ spin_unlock_irqrestore(&dev->spinlock, flags);
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function builds the RPS program for hardware driven acquisition.
+ */
+static void s626_reset_adc(struct comedi_device *dev, uint8_t *ppl)
+{
+ struct s626_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ uint32_t *rps;
+ uint32_t jmp_adrs;
+ uint16_t i;
+ uint16_t n;
+ uint32_t local_ppl;
+
+ /* Stop RPS program in case it is currently running */
+ s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ /* Set starting logical address to write RPS commands. */
+ rps = (uint32_t *)devpriv->rps_buf.logical_base;
+
+ /* Initialize RPS instruction pointer */
+ writel((uint32_t)devpriv->rps_buf.physical_base,
+ devpriv->mmio + S626_P_RPSADDR1);
+
+ /* Construct RPS program in rps_buf DMA buffer */
+ if (cmd != NULL && cmd->scan_begin_src != TRIG_FOLLOW) {
+ /* Wait for Start trigger. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
+ *rps++ = S626_RPS_CLRSIGNAL | S626_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.
+ */
+ /* Write DEBI Write command and address to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL;
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ /* Write DEBI immediate data to shadow RAM: */
+ *rps++ = S626_GSEL_BIPOLAR5V; /* arbitrary immediate data value. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Reset "shadow RAM uploaded" flag. */
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+
+ /*
+ * 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 S626_EOPL flag in the final slot.
+ */
+ for (devpriv->adc_items = 0; devpriv->adc_items < 16;
+ devpriv->adc_items++) {
+ /*
+ * 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.
+ */
+ local_ppl = (*ppl << 8) | (*ppl & 0x10 ? S626_GSEL_BIPOLAR5V :
+ S626_GSEL_BIPOLAR10V);
+
+ /* Switch ADC analog gain. */
+ /* Write DEBI command and address to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_GSEL;
+ /* Write DEBI immediate data to shadow RAM. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ *rps++ = local_ppl;
+ /* Reset "shadow RAM uploaded" flag. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+ /* Select ADC analog input channel. */
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBICMD >> 2);
+ /* Write DEBI command and address to shadow RAM. */
+ *rps++ = S626_DEBI_CMD_WRWORD | S626_LP_ISEL;
+ *rps++ = S626_RPS_LDREG | (S626_P_DEBIAD >> 2);
+ /* Write DEBI immediate data to shadow RAM. */
+ *rps++ = local_ppl;
+ /* Reset "shadow RAM uploaded" flag. */
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_DEBI;
+ /* Invoke shadow RAM upload. */
+ *rps++ = S626_RPS_UPLOAD | S626_RPS_DEBI;
+ /* Wait for shadow upload to finish. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_DEBI;
+
+ /*
+ * Delay at least 10 microseconds for analog input settling.
+ * Instead of padding with NOPs, we use S626_RPS_JUMP
+ * instructions here; this allows us to produce a longer delay
+ * than is possible with NOPs because each S626_RPS_JUMP
+ * flushes the RPS' instruction prefetch pipeline.
+ */
+ jmp_adrs =
+ (uint32_t)devpriv->rps_buf.physical_base +
+ (uint32_t)((unsigned long)rps -
+ (unsigned long)devpriv->
+ rps_buf.logical_base);
+ for (i = 0; i < (10 * S626_RPSCLK_PER_US / 2); i++) {
+ jmp_adrs += 8; /* Repeat to implement time delay: */
+ /* Jump to next RPS instruction. */
+ *rps++ = S626_RPS_JUMP;
+ *rps++ = jmp_adrs;
+ }
+
+ if (cmd != NULL && cmd->convert_src != TRIG_NOW) {
+ /* Wait for Start trigger. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_SIGADC;
+ *rps++ = S626_RPS_CLRSIGNAL | S626_RPS_SIGADC;
+ }
+ /* Start ADC by pulsing GPIO1. */
+ /* Begin ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
+ *rps++ = S626_RPS_NOP;
+ /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */
+ /* End ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_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. */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2;
+
+ /* Transfer ADC data from FB BUFFER 1 register to DMA buffer. */
+ *rps++ = S626_RPS_STREG |
+ (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
+ *rps++ = (uint32_t)devpriv->ana_buf.physical_base +
+ (devpriv->adc_items << 2);
+
+ /*
+ * If this slot's EndOfPollList flag is set, all channels have
+ * now been processed.
+ */
+ if (*ppl++ & S626_EOPL) {
+ devpriv->adc_items++; /* Adjust poll list item count. */
+ break; /* Exit poll list processing loop. */
+ }
+ }
+
+ /*
+ * 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 * S626_RPSCLK_PER_US); n++)
+ *rps++ = S626_RPS_NOP;
+
+ /*
+ * Start a dummy conversion to cause the data from the last
+ * conversion of interest to be shifted in.
+ */
+ /* Begin ADC Start pulse. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2);
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_LO;
+ *rps++ = S626_RPS_NOP;
+ /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */
+ *rps++ = S626_RPS_LDREG | (S626_P_GPIO >> 2); /* End ADC Start pulse. */
+ *rps++ = S626_GPIO_BASE | S626_GPIO1_HI;
+
+ /*
+ * Wait for the data from the last conversion of interest to arrive
+ * in FB BUFFER 1 register.
+ */
+ *rps++ = S626_RPS_PAUSE | S626_RPS_GPIO2; /* Wait for ADC done. */
+
+ /* Transfer final ADC data from FB BUFFER 1 register to DMA buffer. */
+ *rps++ = S626_RPS_STREG | (S626_BUGFIX_STREG(S626_P_FB_BUFFER1) >> 2);
+ *rps++ = (uint32_t)devpriv->ana_buf.physical_base +
+ (devpriv->adc_items << 2);
+
+ /* Indicate ADC scan loop is finished. */
+ /* Signal ReadADC() that scan is done. */
+ /* *rps++= S626_RPS_CLRSIGNAL | S626_RPS_SIGADC; */
+
+ /* invoke interrupt */
+ if (devpriv->ai_cmd_running == 1)
+ *rps++ = S626_RPS_IRQ;
+
+ /* Restart RPS program at its beginning. */
+ *rps++ = S626_RPS_JUMP; /* Branch to start of RPS program. */
+ *rps++ = (uint32_t)devpriv->rps_buf.physical_base;
+
+ /* End of RPS program build */
+}
+
+#ifdef unused_code
+static int s626_ai_rinsn(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct s626_private *devpriv = dev->private;
+ uint8_t i;
+ int32_t *readaddr;
+
+ /* Trigger ADC scan loop start */
+ s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2);
+
+ /* Wait until ADC scan loop is finished (RPS Signal 0 reset) */
+ while (s626_mc_test(dev, S626_MC2_ADC_RPS, S626_P_MC2))
+ ;
+
+ /*
+ * 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->ana_buf.logical_base + 1;
+
+ /*
+ * Convert ADC data to 16-bit integer values and
+ * copy to application buffer.
+ */
+ for (i = 0; i < devpriv->adc_items; i++) {
+ *data = s626_ai_reg_to_uint(*readaddr++);
+ data++;
+ }
+
+ return i;
+}
+#endif
+
+static int s626_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ struct s626_private *devpriv = dev->private;
+ unsigned int status;
+
+ status = readl(devpriv->mmio + S626_P_PSR);
+ if (status & S626_PSR_GPIO2)
+ return 0;
+ return -EBUSY;
+}
+
+static int s626_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s626_private *devpriv = dev->private;
+ uint16_t chan = CR_CHAN(insn->chanspec);
+ uint16_t range = CR_RANGE(insn->chanspec);
+ uint16_t adc_spec = 0;
+ uint32_t gpio_image;
+ uint32_t tmp;
+ int ret;
+ int n;
+
+ /*
+ * Convert application's ADC specification into form
+ * appropriate for register programming.
+ */
+ if (range == 0)
+ adc_spec = (chan << 8) | (S626_GSEL_BIPOLAR5V);
+ else
+ adc_spec = (chan << 8) | (S626_GSEL_BIPOLAR10V);
+
+ /* Switch ADC analog gain. */
+ s626_debi_write(dev, S626_LP_GSEL, adc_spec); /* Set gain. */
+
+ /* Select ADC analog input channel. */
+ s626_debi_write(dev, S626_LP_ISEL, adc_spec); /* Select channel. */
+
+ for (n = 0; n < insn->n; n++) {
+ /* Delay 10 microseconds for analog input settling. */
+ udelay(10);
+
+ /* Start ADC by pulsing GPIO1 low */
+ gpio_image = readl(devpriv->mmio + S626_P_GPIO);
+ /* Assert ADC Start command */
+ writel(gpio_image & ~S626_GPIO1_HI,
+ devpriv->mmio + S626_P_GPIO);
+ /* and stretch it out */
+ writel(gpio_image & ~S626_GPIO1_HI,
+ devpriv->mmio + S626_P_GPIO);
+ writel(gpio_image & ~S626_GPIO1_HI,
+ devpriv->mmio + S626_P_GPIO);
+ /* Negate ADC Start command */
+ writel(gpio_image | S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+
+ /*
+ * 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 */
+ ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Fetch ADC data */
+ if (n != 0) {
+ tmp = readl(devpriv->mmio + S626_P_FB_BUFFER1);
+ data[n - 1] = s626_ai_reg_to_uint(tmp);
+ }
+
+ /*
+ * 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.
+ */
+ gpio_image = readl(devpriv->mmio + S626_P_GPIO);
+ /* Assert ADC Start command */
+ writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+ /* and stretch it out */
+ writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+ writel(gpio_image & ~S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+ /* Negate ADC Start command */
+ writel(gpio_image | S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+
+ /* Wait for the data to arrive in FB BUFFER 1 register. */
+
+ /* Wait for ADC done */
+ ret = comedi_timeout(dev, s, insn, s626_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* Fetch ADC data from audio interface's input shift register. */
+
+ /* Fetch ADC data */
+ if (n != 0) {
+ tmp = readl(devpriv->mmio + S626_P_FB_BUFFER1);
+ data[n - 1] = s626_ai_reg_to_uint(tmp);
+ }
+
+ 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]) | S626_RANGE_5V;
+ else
+ ppl[n] = CR_CHAN(cmd->chanlist[n]) | S626_RANGE_10V;
+ }
+ if (n != 0)
+ ppl[n - 1] |= S626_EOPL;
+
+ return n;
+}
+
+static int s626_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ s->async->inttrig = NULL;
+
+ return 1;
+}
+
+/*
+ * 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(unsigned 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 void s626_timer_load(struct comedi_device *dev,
+ const struct s626_enc_info *k, int tick)
+{
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is Timer. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_TIMER) |
+ /* Count direction is Down. */
+ S626_SET_STD_CLKPOL(S626_CNTDIR_DOWN) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+ uint16_t value_latchsrc = S626_LATCHSRC_A_INDXA;
+ /* uint16_t enab = S626_CLKENAB_ALWAYS; */
+
+ k->set_mode(dev, k, setup, false);
+
+ /* Set the preload register */
+ s626_preload(dev, k, tick);
+
+ /*
+ * Software index pulse forces the preload register to load
+ * into the counter
+ */
+ k->set_load_trig(dev, k, 0);
+ k->pulse_index(dev, k);
+
+ /* set reload on counter overflow */
+ k->set_load_trig(dev, k, 1);
+
+ /* set interrupt on overflow */
+ k->set_int_src(dev, k, S626_INTSRC_OVER);
+
+ s626_set_latch_source(dev, k, value_latchsrc);
+ /* k->set_enable(dev, k, (uint16_t)(enab != 0)); */
+}
+
+/* TO COMPLETE */
+static int s626_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct s626_private *devpriv = dev->private;
+ uint8_t ppl[16];
+ struct comedi_cmd *cmd = &s->async->cmd;
+ const struct s626_enc_info *k;
+ int tick;
+
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev,
+ "s626_ai_cmd: Another ai_cmd is running\n");
+ return -EBUSY;
+ }
+ /* disable interrupt */
+ writel(0, devpriv->mmio + S626_P_IER);
+
+ /* clear interrupt request */
+ writel(S626_IRQ_RPS1 | S626_IRQ_GPIO3, devpriv->mmio + S626_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)
+ return -EINVAL;
+
+ 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 counter to generate adc trigger at scan_begin_arg
+ * interval
+ */
+ k = &s626_enc_chan_info[5];
+ tick = s626_ns_to_timer(&cmd->scan_begin_arg,
+ cmd->flags & TRIG_ROUND_MASK);
+
+ /* load timer value and enable interrupt */
+ s626_timer_load(dev, k, tick);
+ k->set_enable(dev, k, S626_CLKENAB_ALWAYS);
+ 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);
+ break;
+ }
+
+ switch (cmd->convert_src) {
+ case TRIG_NOW:
+ break;
+ case TRIG_TIMER:
+ /*
+ * set a counter to generate adc trigger at convert_arg
+ * interval
+ */
+ k = &s626_enc_chan_info[4];
+ tick = s626_ns_to_timer(&cmd->convert_arg,
+ cmd->flags & TRIG_ROUND_MASK);
+
+ /* load timer value and enable interrupt */
+ s626_timer_load(dev, k, tick);
+ k->set_enable(dev, k, S626_CLKENAB_INDEX);
+ 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);
+ break;
+ }
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* data arrives as one packet */
+ devpriv->ai_sample_count = cmd->stop_arg;
+ devpriv->ai_continuous = 0;
+ break;
+ case TRIG_NONE:
+ /* continuous acquisition */
+ devpriv->ai_continuous = 1;
+ devpriv->ai_sample_count = 1;
+ break;
+ }
+
+ s626_reset_adc(dev, ppl);
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ /* Trigger ADC scan loop start */
+ /* s626_mc_enable(dev, S626_MC2_ADC_RPS, S626_P_MC2); */
+
+ /* Start executing the RPS program */
+ s626_mc_enable(dev, S626_MC1_ERPS1, S626_P_MC1);
+ s->async->inttrig = NULL;
+ break;
+ case TRIG_EXT:
+ /* configure DIO channel for acquisition trigger */
+ s626_dio_set_irq(dev, cmd->start_arg);
+ s->async->inttrig = NULL;
+ break;
+ case TRIG_INT:
+ s->async->inttrig = s626_ai_inttrig;
+ break;
+ }
+
+ /* enable interrupt */
+ writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1, devpriv->mmio + S626_P_IER);
+
+ return 0;
+}
+
+static int s626_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_INT | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW);
+ err |= cfc_check_trigger_src(&cmd->convert_src,
+ TRIG_TIMER | TRIG_EXT | TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ switch (cmd->start_src) {
+ case TRIG_NOW:
+ case TRIG_INT:
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+ break;
+ case TRIG_EXT:
+ err |= cfc_check_trigger_arg_max(&cmd->start_arg, 39);
+ break;
+ }
+
+ if (cmd->scan_begin_src == TRIG_EXT)
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 39);
+ if (cmd->convert_src == TRIG_EXT)
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, 39);
+
+#define S626_MAX_SPEED 200000 /* in nanoseconds */
+#define S626_MIN_SPEED 2000000000 /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ S626_MAX_SPEED);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ S626_MIN_SPEED);
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ /* should specify multiple external triggers */
+ /* err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); */
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg,
+ S626_MAX_SPEED);
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg,
+ S626_MIN_SPEED);
+ } else {
+ /* external trigger */
+ /* see above */
+ /* err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9); */
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ s626_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ s626_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int s626_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct s626_private *devpriv = dev->private;
+
+ /* Stop RPS program in case it is currently running */
+ s626_mc_disable(dev, S626_MC1_ERPS1, S626_P_MC1);
+
+ /* disable master interrupt */
+ writel(0, devpriv->mmio + S626_P_IER);
+
+ devpriv->ai_cmd_running = 0;
+
+ return 0;
+}
+
+static int s626_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s626_private *devpriv = dev->private;
+ int i;
+ int ret;
+ 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);
+
+ ret = s626_set_dac(dev, chan, dacdata);
+ if (ret)
+ return ret;
+ }
+
+ return i;
+}
+
+static int s626_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct s626_private *devpriv = dev->private;
+ 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;
+
+ /* Prepare to treat writes to WRCapSel as capture disables. */
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_NOEDCAP);
+
+ /* For each group of sixteen channels ... */
+ for (group = 0; group < S626_DIO_BANKS; group++) {
+ /* Disable all interrupts */
+ s626_debi_write(dev, S626_LP_WRINTSEL(group), 0);
+ /* Disable all event captures */
+ s626_debi_write(dev, S626_LP_WRCAPSEL(group), 0xffff);
+ /* Init all DIOs to default edge polarity */
+ s626_debi_write(dev, S626_LP_WREDGSEL(group), 0);
+ /* Program all outputs to inactive state */
+ s626_debi_write(dev, S626_LP_WRDOUT(group), 0);
+ }
+}
+
+static int s626_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long group = (unsigned long)s->private;
+
+ if (comedi_dio_update_state(s, data))
+ s626_debi_write(dev, S626_LP_WRDOUT(group), s->state);
+
+ data[1] = s626_debi_read(dev, S626_LP_RDDIN(group));
+
+ return insn->n;
+}
+
+static int s626_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned long group = (unsigned long)s->private;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ s626_debi_write(dev, S626_LP_WRDOUT(group), s->io_bits);
+
+ return insn->n;
+}
+
+/*
+ * Now this function initializes the value of the counter (data[0])
+ * and set the subdevice. To complete with trigger and interrupt
+ * configuration.
+ *
+ * FIXME: data[0] is supposed to be an INSN_CONFIG_xxx constant indicating
+ * what is being configured, but this function appears to be using data[0]
+ * as a variable.
+ */
+static int s626_enc_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is Counter. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) |
+ /* Active high clock. */
+ S626_SET_STD_CLKPOL(S626_CLKPOL_POS) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+ /* uint16_t disable_int_src = true; */
+ /* uint32_t Preloadvalue; //Counter initial value */
+ uint16_t value_latchsrc = S626_LATCHSRC_AB_READ;
+ uint16_t enab = S626_CLKENAB_ALWAYS;
+ const struct s626_enc_info *k =
+ &s626_enc_chan_info[CR_CHAN(insn->chanspec)];
+
+ /* (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); */
+
+ k->set_mode(dev, k, setup, true);
+ s626_preload(dev, k, data[0]);
+ k->pulse_index(dev, k);
+ s626_set_latch_source(dev, k, value_latchsrc);
+ k->set_enable(dev, k, (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;
+ const struct s626_enc_info *k =
+ &s626_enc_chan_info[CR_CHAN(insn->chanspec)];
+
+ for (n = 0; n < insn->n; n++)
+ data[n] = s626_read_latch(dev, k);
+
+ return n;
+}
+
+static int s626_enc_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ const struct s626_enc_info *k =
+ &s626_enc_chan_info[CR_CHAN(insn->chanspec)];
+
+ /* Set the preload register */
+ s626_preload(dev, k, data[0]);
+
+ /*
+ * Software index pulse forces the preload register to load
+ * into the counter
+ */
+ k->set_load_trig(dev, k, 0);
+ k->pulse_index(dev, k);
+ k->set_load_trig(dev, k, 2);
+
+ return 1;
+}
+
+static void s626_write_misc2(struct comedi_device *dev, uint16_t new_image)
+{
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WENABLE);
+ s626_debi_write(dev, S626_LP_WRMISC2, new_image);
+ s626_debi_write(dev, S626_LP_MISC1, S626_MISC1_WDISABLE);
+}
+
+static void s626_close_dma_b(struct comedi_device *dev,
+ struct s626_buffer_dma *pdma, size_t bsize)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ void *vbptr;
+ dma_addr_t vpptr;
+
+ if (pdma == NULL)
+ return;
+
+ /* find the matching allocation from the board struct */
+ vbptr = pdma->logical_base;
+ vpptr = pdma->physical_base;
+ if (vbptr) {
+ pci_free_consistent(pcidev, bsize, vbptr, vpptr);
+ pdma->logical_base = NULL;
+ pdma->physical_base = 0;
+ }
+}
+
+static void s626_counters_init(struct comedi_device *dev)
+{
+ int chan;
+ const struct s626_enc_info *k;
+ uint16_t setup =
+ /* Preload upon index. */
+ S626_SET_STD_LOADSRC(S626_LOADSRC_INDX) |
+ /* Disable hardware index. */
+ S626_SET_STD_INDXSRC(S626_INDXSRC_SOFT) |
+ /* Operating mode is counter. */
+ S626_SET_STD_ENCMODE(S626_ENCMODE_COUNTER) |
+ /* Active high clock. */
+ S626_SET_STD_CLKPOL(S626_CLKPOL_POS) |
+ /* Clock multiplier is 1x. */
+ S626_SET_STD_CLKMULT(S626_CLKMULT_1X) |
+ /* Enabled by index */
+ S626_SET_STD_CLKENAB(S626_CLKENAB_INDEX);
+
+ /*
+ * Disable all counter interrupts and clear any captured counter events.
+ */
+ for (chan = 0; chan < S626_ENCODER_CHANNELS; chan++) {
+ k = &s626_enc_chan_info[chan];
+ k->set_mode(dev, k, setup, true);
+ k->set_int_src(dev, k, 0);
+ k->reset_cap_flags(dev, k);
+ k->set_enable(dev, k, S626_CLKENAB_ALWAYS);
+ }
+}
+
+static int s626_allocate_dma_buffers(struct comedi_device *dev)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct s626_private *devpriv = dev->private;
+ void *addr;
+ dma_addr_t appdma;
+
+ addr = pci_alloc_consistent(pcidev, S626_DMABUF_SIZE, &appdma);
+ if (!addr)
+ return -ENOMEM;
+ devpriv->ana_buf.logical_base = addr;
+ devpriv->ana_buf.physical_base = appdma;
+
+ addr = pci_alloc_consistent(pcidev, S626_DMABUF_SIZE, &appdma);
+ if (!addr)
+ return -ENOMEM;
+ devpriv->rps_buf.logical_base = addr;
+ devpriv->rps_buf.physical_base = appdma;
+
+ return 0;
+}
+
+static int s626_initialize(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+ dma_addr_t phys_buf;
+ uint16_t chan;
+ int i;
+ int ret;
+
+ /* Enable DEBI and audio pins, enable I2C interface */
+ s626_mc_enable(dev, S626_MC1_DEBI | S626_MC1_AUDIO | S626_MC1_I2C,
+ S626_P_MC1);
+
+ /*
+ * Configure DEBI operating mode
+ *
+ * Local bus is 16 bits wide
+ * Declare DEBI transfer timeout interval
+ * Set up byte lane steering
+ * Intel-compatible local bus (DEBI never times out)
+ */
+ writel(S626_DEBI_CFG_SLAVE16 |
+ (S626_DEBI_TOUT << S626_DEBI_CFG_TOUT_BIT) | S626_DEBI_SWAP |
+ S626_DEBI_CFG_INTEL, devpriv->mmio + S626_P_DEBICFG);
+
+ /* Disable MMU paging */
+ writel(S626_DEBI_PAGE_DISABLE, devpriv->mmio + S626_P_DEBIPAGE);
+
+ /* Init GPIO so that ADC Start* is negated */
+ writel(S626_GPIO_BASE | S626_GPIO1_HI, devpriv->mmio + S626_P_GPIO);
+
+ /* I2C device address for onboard eeprom (revb) */
+ devpriv->i2c_adrs = 0xA0;
+
+ /*
+ * Issue an I2C ABORT command to halt any I2C
+ * operation in progress and reset BUSY flag.
+ */
+ writel(S626_I2C_CLKSEL | S626_I2C_ABORT,
+ devpriv->mmio + S626_P_I2CSTAT);
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Per SAA7146 data sheet, write to STATUS
+ * reg twice to reset all I2C error flags.
+ */
+ for (i = 0; i < 2; i++) {
+ writel(S626_I2C_CLKSEL, devpriv->mmio + S626_P_I2CSTAT);
+ s626_mc_enable(dev, S626_MC2_UPLD_IIC, S626_P_MC2);
+ ret = comedi_timeout(dev, NULL, NULL, s626_i2c_handshake_eoc, 0);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * 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.
+ */
+ writel(S626_ACON2_INIT, devpriv->mmio + S626_P_ACON2);
+
+ /*
+ * Set up TSL1 slot list, which is used to control the
+ * accumulation of ADC data: S626_RSD1 = shift data in on SD1.
+ * S626_SIB_A1 = store data uint8_t at next available location
+ * in FB BUFFER1 register.
+ */
+ writel(S626_RSD1 | S626_SIB_A1, devpriv->mmio + S626_P_TSL1);
+ writel(S626_RSD1 | S626_SIB_A1 | S626_EOS,
+ devpriv->mmio + S626_P_TSL1 + 4);
+
+ /* Enable TSL1 slot list so that it executes all the time */
+ writel(S626_ACON1_ADCSTART, devpriv->mmio + S626_P_ACON1);
+
+ /*
+ * Initialize RPS registers used for ADC
+ */
+
+ /* Physical start of RPS program */
+ writel((uint32_t)devpriv->rps_buf.physical_base,
+ devpriv->mmio + S626_P_RPSADDR1);
+ /* RPS program performs no explicit mem writes */
+ writel(0, devpriv->mmio + S626_P_RPSPAGE1);
+ /* Disable RPS timeouts */
+ writel(0, devpriv->mmio + S626_P_RPS1_TOUT);
+
+#if 0
+ /*
+ * 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.
+ */
+ {
+ struct comedi_subdevice *s = dev->read_subdev;
+ uint8_t poll_list;
+ uint16_t adc_data;
+ uint16_t start_val;
+ uint16_t index;
+ unsigned int data[16];
+
+ /* Create a simple polling list for analog input channel 0 */
+ poll_list = S626_EOPL;
+ s626_reset_adc(dev, &poll_list);
+
+ /* Get initial ADC value */
+ s626_ai_rinsn(dev, s, NULL, data);
+ start_val = 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, s, NULL, data);
+ adc_data = data[0];
+ if (adc_data != start_val)
+ break;
+ }
+ }
+#endif /* SAA7146 BUG WORKAROUND */
+
+ /*
+ * Initialize the DAC interface
+ */
+
+ /*
+ * Init Audio2's output DMAC attributes:
+ * burst length = 1 DWORD
+ * threshold = 1 DWORD.
+ */
+ writel(0, devpriv->mmio + S626_P_PCI_BT_A);
+
+ /*
+ * 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.
+ */
+ phys_buf = devpriv->ana_buf.physical_base +
+ (S626_DAC_WDMABUF_OS * sizeof(uint32_t));
+ writel((uint32_t)phys_buf, devpriv->mmio + S626_P_BASEA2_OUT);
+ writel((uint32_t)(phys_buf + sizeof(uint32_t)),
+ devpriv->mmio + S626_P_PROTA2_OUT);
+
+ /*
+ * Cache Audio2's output DMA buffer logical address. This is
+ * where DAC data is buffered for A2 output DMA transfers.
+ */
+ devpriv->dac_wbuf = (uint32_t *)devpriv->ana_buf.logical_base +
+ S626_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.
+ */
+ writel(8, devpriv->mmio + S626_P_PAGEA2_OUT);
+
+ /*
+ * 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.
+ */
+
+ /* Slot 0: Trap TSL execution, shift 0xFF into FB_BUFFER2 */
+ writel(S626_XSD2 | S626_RSD3 | S626_SIB_A2 | S626_EOS,
+ devpriv->mmio + S626_VECTPORT(0));
+
+ /*
+ * 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.
+ */
+
+ /* Slot 1: Fetch DWORD from Audio2's output FIFO */
+ writel(S626_LF_A2, devpriv->mmio + S626_VECTPORT(1));
+
+ /* Start DAC's audio interface (TSL2) running */
+ writel(S626_ACON1_DACSTART, devpriv->mmio + S626_P_ACON1);
+
+ /*
+ * 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.
+ */
+ s626_load_trim_dacs(dev);
+ ret = s626_load_trim_dacs(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * 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++) {
+ ret = s626_set_dac(dev, chan, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Init counters */
+ s626_counters_init(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.
+ */
+ s626_write_misc2(dev, (s626_debi_read(dev, S626_LP_RDMISC2) &
+ S626_MISC2_BATT_ENABLE));
+
+ /* Initialize the digital I/O subsystem */
+ s626_dio_init(dev);
+
+ return 0;
+}
+
+static int s626_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ struct s626_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ devpriv->mmio = pci_ioremap_bar(pcidev, 0);
+ if (!devpriv->mmio)
+ return -ENOMEM;
+
+ /* disable master interrupt */
+ writel(0, devpriv->mmio + S626_P_IER);
+
+ /* soft reset */
+ writel(S626_MC1_SOFT_RESET, devpriv->mmio + S626_P_MC1);
+
+ /* DMA FIXME DMA// */
+
+ ret = s626_allocate_dma_buffers(dev);
+ if (ret)
+ return ret;
+
+ if (pcidev->irq) {
+ ret = request_irq(pcidev->irq, s626_irq_handler, IRQF_SHARED,
+ dev->board_name, dev);
+
+ if (ret == 0)
+ dev->irq = pcidev->irq;
+ }
+
+ ret = comedi_alloc_subdevices(dev, 6);
+ if (ret)
+ return ret;
+
+ s = &dev->subdevices[0];
+ /* analog input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_DIFF;
+ s->n_chan = S626_ADC_CHANNELS;
+ s->maxdata = 0x3fff;
+ s->range_table = &s626_range_table;
+ s->len_chanlist = S626_ADC_CHANNELS;
+ s->insn_read = s626_ai_insn_read;
+ if (dev->irq) {
+ dev->read_subdev = s;
+ s->subdev_flags |= SDF_CMD_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 = S626_DAC_CHANNELS;
+ 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 = 16;
+ s->maxdata = 1;
+ s->io_bits = 0xffff;
+ s->private = (void *)0; /* DIO group 0 */
+ 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 = (void *)1; /* DIO group 1 */
+ 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 = (void *)2; /* DIO group 2 */
+ 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 = S626_ENCODER_CHANNELS;
+ s->maxdata = 0xffffff;
+ s->range_table = &range_unknown;
+ s->insn_config = s626_enc_insn_config;
+ s->insn_read = s626_enc_insn_read;
+ s->insn_write = s626_enc_insn_write;
+
+ ret = s626_initialize(dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void s626_detach(struct comedi_device *dev)
+{
+ struct s626_private *devpriv = dev->private;
+
+ if (devpriv) {
+ /* stop ai_command */
+ devpriv->ai_cmd_running = 0;
+
+ if (devpriv->mmio) {
+ /* interrupt mask */
+ /* Disable master interrupt */
+ writel(0, devpriv->mmio + S626_P_IER);
+ /* Clear board's IRQ status flag */
+ writel(S626_IRQ_GPIO3 | S626_IRQ_RPS1,
+ devpriv->mmio + S626_P_ISR);
+
+ /* Disable the watchdog timer and battery charger. */
+ s626_write_misc2(dev, 0);
+
+ /* Close all interfaces on 7146 device */
+ writel(S626_MC1_SHUTDOWN, devpriv->mmio + S626_P_MC1);
+ writel(S626_ACON1_BASE, devpriv->mmio + S626_P_ACON1);
+
+ s626_close_dma_b(dev, &devpriv->rps_buf,
+ S626_DMABUF_SIZE);
+ s626_close_dma_b(dev, &devpriv->ana_buf,
+ S626_DMABUF_SIZE);
+ }
+
+ if (dev->irq)
+ free_irq(dev->irq, dev);
+ if (devpriv->mmio)
+ iounmap(devpriv->mmio);
+ }
+ comedi_pci_disable(dev);
+}
+
+static struct comedi_driver s626_driver = {
+ .driver_name = "s626",
+ .module = THIS_MODULE,
+ .auto_attach = s626_auto_attach,
+ .detach = s626_detach,
+};
+
+static int s626_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &s626_driver, id->driver_data);
+}
+
+/*
+ * 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 const struct pci_device_id s626_pci_table[] = {
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146,
+ 0x6000, 0x0272) },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, s626_pci_table);
+
+static struct pci_driver s626_pci_driver = {
+ .name = "s626",
+ .id_table = s626_pci_table,
+ .probe = s626_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(s626_driver, s626_pci_driver);
+
+MODULE_AUTHOR("Gianluca Palli <gpalli@deis.unibo.it>");
+MODULE_DESCRIPTION("Sensoray 626 Comedi driver module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/drivers/s626.h b/drivers/staging/comedi/drivers/s626.h
new file mode 100644
index 00000000000..33b72739c1c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/s626.h
@@ -0,0 +1,774 @@
+/*
+ * comedi/drivers/s626.h
+ * Sensoray s626 Comedi driver, header file
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+ *
+ * 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.
+ */
+
+#ifndef S626_H_INCLUDED
+#define S626_H_INCLUDED
+
+#define S626_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 S626_NUM_TRIMDACS 12 /* Number of valid TrimDAC channels. */
+
+/* PCI bus interface types. */
+#define S626_INTEL 1 /* Intel bus type. */
+#define S626_MOTOROLA 2 /* Motorola bus type. */
+
+#define S626_PLATFORM S626_INTEL /* *** SELECT PLATFORM TYPE *** */
+
+#define S626_RANGE_5V 0x10 /* +/-5V range */
+#define S626_RANGE_10V 0x00 /* +/-10V range */
+
+#define S626_EOPL 0x80 /* End of ADC poll list marker. */
+#define S626_GSEL_BIPOLAR5V 0x00F0 /* S626_LP_GSEL setting 5V bipolar. */
+#define S626_GSEL_BIPOLAR10V 0x00A0 /* S626_LP_GSEL setting 10V bipolar. */
+
+/* Error codes that must be visible to this base class. */
+#define S626_ERR_ILLEGAL_PARM 0x00010000 /* Illegal function parameter
+ * value was specified. */
+#define S626_ERR_I2C 0x00020000 /* I2C error. */
+#define S626_ERR_COUNTERSETUP 0x00200000 /* Illegal setup specified for
+ * counter channel. */
+#define S626_ERR_DEBI_TIMEOUT 0x00400000 /* DEBI transfer timed out. */
+
+/*
+ * Organization (physical order) and size (in DWORDs) of logical DMA buffers
+ * contained by ANA_DMABUF.
+ */
+#define S626_ADC_DMABUF_DWORDS 40 /* ADC DMA buffer must hold 16 samples,
+ * plus pre/post garbage samples. */
+#define S626_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 S626_DAC_WDMABUF_OS S626_ADC_DMABUF_DWORDS
+
+/* Interrupt enable bit in ISR and IER. */
+#define S626_IRQ_GPIO3 0x00000040 /* IRQ enable for GPIO3. */
+#define S626_IRQ_RPS1 0x10000000
+#define S626_ISR_AFOU 0x00000800
+/* Audio fifo under/overflow detected. */
+
+#define S626_IRQ_COINT1A 0x0400 /* counter 1A overflow interrupt mask */
+#define S626_IRQ_COINT1B 0x0800 /* counter 1B overflow interrupt mask */
+#define S626_IRQ_COINT2A 0x1000 /* counter 2A overflow interrupt mask */
+#define S626_IRQ_COINT2B 0x2000 /* counter 2B overflow interrupt mask */
+#define S626_IRQ_COINT3A 0x4000 /* counter 3A overflow interrupt mask */
+#define S626_IRQ_COINT3B 0x8000 /* counter 3B overflow interrupt mask */
+
+/* RPS command codes. */
+#define S626_RPS_CLRSIGNAL 0x00000000 /* CLEAR SIGNAL */
+#define S626_RPS_SETSIGNAL 0x10000000 /* SET SIGNAL */
+#define S626_RPS_NOP 0x00000000 /* NOP */
+#define S626_RPS_PAUSE 0x20000000 /* PAUSE */
+#define S626_RPS_UPLOAD 0x40000000 /* UPLOAD */
+#define S626_RPS_JUMP 0x80000000 /* JUMP */
+#define S626_RPS_LDREG 0x90000100 /* LDREG (1 uint32_t only) */
+#define S626_RPS_STREG 0xA0000100 /* STREG (1 uint32_t only) */
+#define S626_RPS_STOP 0x50000000 /* STOP */
+#define S626_RPS_IRQ 0x60000000 /* IRQ */
+
+#define S626_RPS_LOGICAL_OR 0x08000000 /* Logical OR conditionals. */
+#define S626_RPS_INVERT 0x04000000 /* Test for negated
+ * semaphores. */
+#define S626_RPS_DEBI 0x00000002 /* DEBI done */
+
+#define S626_RPS_SIG0 0x00200000 /* RPS semaphore 0
+ * (used by ADC). */
+#define S626_RPS_SIG1 0x00400000 /* RPS semaphore 1
+ * (used by DAC). */
+#define S626_RPS_SIG2 0x00800000 /* RPS semaphore 2
+ * (not used). */
+#define S626_RPS_GPIO2 0x00080000 /* RPS GPIO2 */
+#define S626_RPS_GPIO3 0x00100000 /* RPS GPIO3 */
+
+#define S626_RPS_SIGADC S626_RPS_SIG0 /* Trigger/status for
+ * ADC's RPS program. */
+#define S626_RPS_SIGDAC S626_RPS_SIG1 /* Trigger/status for
+ * DAC's RPS program. */
+
+/* RPS clock parameters. */
+#define S626_RPSCLK_SCALAR 8 /* This is apparent ratio of
+ * PCI/RPS clks (undocumented!!). */
+#define S626_RPSCLK_PER_US (33 / S626_RPSCLK_SCALAR)
+ /* Number of RPS clocks in one
+ * microsecond. */
+
+/* Event counter source addresses. */
+#define S626_SBA_RPS_A0 0x27 /* Time of RPS0 busy, in PCI clocks. */
+
+/* GPIO constants. */
+#define S626_GPIO_BASE 0x10004000 /* GPIO 0,2,3 = inputs,
+ * GPIO3 = IRQ; GPIO1 = out. */
+#define S626_GPIO1_LO 0x00000000 /* GPIO1 set to LOW. */
+#define S626_GPIO1_HI 0x00001000 /* GPIO1 set to HIGH. */
+
+/* Primary Status Register (PSR) constants. */
+#define S626_PSR_DEBI_E 0x00040000 /* DEBI event flag. */
+#define S626_PSR_DEBI_S 0x00080000 /* DEBI status flag. */
+#define S626_PSR_A2_IN 0x00008000 /* Audio output DMA2 protection
+ * address reached. */
+#define S626_PSR_AFOU 0x00000800 /* Audio FIFO under/overflow
+ * detected. */
+#define S626_PSR_GPIO2 0x00000020 /* GPIO2 input pin: 0=AdcBusy,
+ * 1=AdcIdle. */
+#define S626_PSR_EC0S 0x00000001 /* Event counter 0 threshold
+ * reached. */
+
+/* Secondary Status Register (SSR) constants. */
+#define S626_SSR_AF2_OUT 0x00000200 /* Audio 2 output FIFO
+ * under/overflow detected. */
+
+/* Master Control Register 1 (MC1) constants. */
+#define S626_MC1_SOFT_RESET 0x80000000 /* Invoke 7146 soft reset. */
+#define S626_MC1_SHUTDOWN 0x3FFF0000 /* Shut down all MC1-controlled
+ * enables. */
+
+#define S626_MC1_ERPS1 0x2000 /* Enab/disable RPS task 1. */
+#define S626_MC1_ERPS0 0x1000 /* Enab/disable RPS task 0. */
+#define S626_MC1_DEBI 0x0800 /* Enab/disable DEBI pins. */
+#define S626_MC1_AUDIO 0x0200 /* Enab/disable audio port pins. */
+#define S626_MC1_I2C 0x0100 /* Enab/disable I2C interface. */
+#define S626_MC1_A2OUT 0x0008 /* Enab/disable transfer on A2 out. */
+#define S626_MC1_A2IN 0x0004 /* Enab/disable transfer on A2 in. */
+#define S626_MC1_A1IN 0x0001 /* Enab/disable transfer on A1 in. */
+
+/* Master Control Register 2 (MC2) constants. */
+#define S626_MC2_UPLD_DEBI 0x0002 /* Upload DEBI. */
+#define S626_MC2_UPLD_IIC 0x0001 /* Upload I2C. */
+#define S626_MC2_RPSSIG2 0x2000 /* RPS signal 2 (not used). */
+#define S626_MC2_RPSSIG1 0x1000 /* RPS signal 1 (DAC RPS busy). */
+#define S626_MC2_RPSSIG0 0x0800 /* RPS signal 0 (ADC RPS busy). */
+
+#define S626_MC2_ADC_RPS S626_MC2_RPSSIG0 /* ADC RPS busy. */
+#define S626_MC2_DAC_RPS S626_MC2_RPSSIG1 /* DAC RPS busy. */
+
+/* PCI BUS (SAA7146) REGISTER ADDRESS OFFSETS */
+#define S626_P_PCI_BT_A 0x004C /* Audio DMA burst/threshold control. */
+#define S626_P_DEBICFG 0x007C /* DEBI configuration. */
+#define S626_P_DEBICMD 0x0080 /* DEBI command. */
+#define S626_P_DEBIPAGE 0x0084 /* DEBI page. */
+#define S626_P_DEBIAD 0x0088 /* DEBI target address. */
+#define S626_P_I2CCTRL 0x008C /* I2C control. */
+#define S626_P_I2CSTAT 0x0090 /* I2C status. */
+#define S626_P_BASEA2_IN 0x00AC /* Audio input 2 base physical DMAbuf
+ * address. */
+#define S626_P_PROTA2_IN 0x00B0 /* Audio input 2 physical DMAbuf
+ * protection address. */
+#define S626_P_PAGEA2_IN 0x00B4 /* Audio input 2 paging attributes. */
+#define S626_P_BASEA2_OUT 0x00B8 /* Audio output 2 base physical DMAbuf
+ * address. */
+#define S626_P_PROTA2_OUT 0x00BC /* Audio output 2 physical DMAbuf
+ * protection address. */
+#define S626_P_PAGEA2_OUT 0x00C0 /* Audio output 2 paging attributes. */
+#define S626_P_RPSPAGE0 0x00C4 /* RPS0 page. */
+#define S626_P_RPSPAGE1 0x00C8 /* RPS1 page. */
+#define S626_P_RPS0_TOUT 0x00D4 /* RPS0 time-out. */
+#define S626_P_RPS1_TOUT 0x00D8 /* RPS1 time-out. */
+#define S626_P_IER 0x00DC /* Interrupt enable. */
+#define S626_P_GPIO 0x00E0 /* General-purpose I/O. */
+#define S626_P_EC1SSR 0x00E4 /* Event counter set 1 source select. */
+#define S626_P_ECT1R 0x00EC /* Event counter threshold set 1. */
+#define S626_P_ACON1 0x00F4 /* Audio control 1. */
+#define S626_P_ACON2 0x00F8 /* Audio control 2. */
+#define S626_P_MC1 0x00FC /* Master control 1. */
+#define S626_P_MC2 0x0100 /* Master control 2. */
+#define S626_P_RPSADDR0 0x0104 /* RPS0 instruction pointer. */
+#define S626_P_RPSADDR1 0x0108 /* RPS1 instruction pointer. */
+#define S626_P_ISR 0x010C /* Interrupt status. */
+#define S626_P_PSR 0x0110 /* Primary status. */
+#define S626_P_SSR 0x0114 /* Secondary status. */
+#define S626_P_EC1R 0x0118 /* Event counter set 1. */
+#define S626_P_ADP4 0x0138 /* Logical audio DMA pointer of audio
+ * input FIFO A2_IN. */
+#define S626_P_FB_BUFFER1 0x0144 /* Audio feedback buffer 1. */
+#define S626_P_FB_BUFFER2 0x0148 /* Audio feedback buffer 2. */
+#define S626_P_TSL1 0x0180 /* Audio time slot list 1. */
+#define S626_P_TSL2 0x01C0 /* Audio time slot list 2. */
+
+/* LOCAL BUS (GATE ARRAY) REGISTER ADDRESS OFFSETS */
+/* Analog I/O registers: */
+#define S626_LP_DACPOL 0x0082 /* Write DAC polarity. */
+#define S626_LP_GSEL 0x0084 /* Write ADC gain. */
+#define S626_LP_ISEL 0x0086 /* Write ADC channel select. */
+
+/* Digital I/O registers */
+#define S626_LP_RDDIN(x) (0x0040 + (x) * 0x10) /* R: digital input */
+#define S626_LP_WRINTSEL(x) (0x0042 + (x) * 0x10) /* W: int enable */
+#define S626_LP_WREDGSEL(x) (0x0044 + (x) * 0x10) /* W: edge selection */
+#define S626_LP_WRCAPSEL(x) (0x0046 + (x) * 0x10) /* W: capture enable */
+#define S626_LP_RDCAPFLG(x) (0x0048 + (x) * 0x10) /* R: edges captured */
+#define S626_LP_WRDOUT(x) (0x0048 + (x) * 0x10) /* W: digital output */
+#define S626_LP_RDINTSEL(x) (0x004a + (x) * 0x10) /* R: int enable */
+#define S626_LP_RDEDGSEL(x) (0x004c + (x) * 0x10) /* R: edge selection */
+#define S626_LP_RDCAPSEL(x) (0x004e + (x) * 0x10) /* R: capture enable */
+
+/* Counter Registers (read/write): */
+#define S626_LP_CR0A 0x0000 /* 0A setup register. */
+#define S626_LP_CR0B 0x0002 /* 0B setup register. */
+#define S626_LP_CR1A 0x0004 /* 1A setup register. */
+#define S626_LP_CR1B 0x0006 /* 1B setup register. */
+#define S626_LP_CR2A 0x0008 /* 2A setup register. */
+#define S626_LP_CR2B 0x000A /* 2B setup register. */
+
+/* Counter PreLoad (write) and Latch (read) Registers: */
+#define S626_LP_CNTR0ALSW 0x000C /* 0A lsw. */
+#define S626_LP_CNTR0AMSW 0x000E /* 0A msw. */
+#define S626_LP_CNTR0BLSW 0x0010 /* 0B lsw. */
+#define S626_LP_CNTR0BMSW 0x0012 /* 0B msw. */
+#define S626_LP_CNTR1ALSW 0x0014 /* 1A lsw. */
+#define S626_LP_CNTR1AMSW 0x0016 /* 1A msw. */
+#define S626_LP_CNTR1BLSW 0x0018 /* 1B lsw. */
+#define S626_LP_CNTR1BMSW 0x001A /* 1B msw. */
+#define S626_LP_CNTR2ALSW 0x001C /* 2A lsw. */
+#define S626_LP_CNTR2AMSW 0x001E /* 2A msw. */
+#define S626_LP_CNTR2BLSW 0x0020 /* 2B lsw. */
+#define S626_LP_CNTR2BMSW 0x0022 /* 2B msw. */
+
+/* Miscellaneous Registers (read/write): */
+#define S626_LP_MISC1 0x0088 /* Read/write Misc1. */
+#define S626_LP_WRMISC2 0x0090 /* Write Misc2. */
+#define S626_LP_RDMISC2 0x0082 /* Read Misc2. */
+
+/* Bit masks for MISC1 register that are the same for reads and writes. */
+#define S626_MISC1_WENABLE 0x8000 /* enab writes to MISC2 (except Clear
+ * Watchdog bit). */
+#define S626_MISC1_WDISABLE 0x0000 /* Disable writes to MISC2. */
+#define S626_MISC1_EDCAP 0x1000 /* Enable edge capture on DIO chans
+ * specified by S626_LP_WRCAPSELx. */
+#define S626_MISC1_NOEDCAP 0x0000 /* Disable edge capture on specified
+ * DIO chans. */
+
+/* Bit masks for MISC1 register reads. */
+#define S626_RDMISC1_WDTIMEOUT 0x4000 /* Watchdog timer timed out. */
+
+/* Bit masks for MISC2 register writes. */
+#define S626_WRMISC2_WDCLEAR 0x8000 /* Reset watchdog timer to zero. */
+#define S626_WRMISC2_CHARGE_ENABLE 0x4000 /* Enable battery trickle charging. */
+
+/* Bit masks for MISC2 register that are the same for reads and writes. */
+#define S626_MISC2_BATT_ENABLE 0x0008 /* Backup battery enable. */
+#define S626_MISC2_WDENABLE 0x0004 /* Watchdog timer enable. */
+#define S626_MISC2_WDPERIOD_MASK 0x0003 /* Watchdog interval select mask. */
+
+/* Bit masks for ACON1 register. */
+#define S626_A2_RUN 0x40000000 /* Run A2 based on TSL2. */
+#define S626_A1_RUN 0x20000000 /* Run A1 based on TSL1. */
+#define S626_A1_SWAP 0x00200000 /* Use big-endian for A1. */
+#define S626_A2_SWAP 0x00100000 /* Use big-endian for A2. */
+#define S626_WS_MODES 0x00019999 /* WS0 = TSL1 trigger input,
+ * WS1-WS4 = CS* outputs. */
+
+#if S626_PLATFORM == S626_INTEL /* Base ACON1 config: always run
+ * A1 based on TSL1. */
+#define S626_ACON1_BASE (S626_WS_MODES | S626_A1_RUN)
+#elif S626_PLATFORM == S626_MOTOROLA
+#define S626_ACON1_BASE \
+ (S626_WS_MODES | S626_A1_RUN | S626_A1_SWAP | S626_A2_SWAP)
+#endif
+
+#define S626_ACON1_ADCSTART S626_ACON1_BASE /* Start ADC: run A1
+ * based on TSL1. */
+#define S626_ACON1_DACSTART (S626_ACON1_BASE | S626_A2_RUN)
+/* Start transmit to DAC: run A2 based on TSL2. */
+#define S626_ACON1_DACSTOP S626_ACON1_BASE /* Halt A2. */
+
+/* Bit masks for ACON2 register. */
+#define S626_A1_CLKSRC_BCLK1 0x00000000 /* A1 bit rate = BCLK1 (ADC). */
+#define S626_A2_CLKSRC_X1 0x00800000 /* A2 bit rate = ACLK/1
+ * (DACs). */
+#define S626_A2_CLKSRC_X2 0x00C00000 /* A2 bit rate = ACLK/2
+ * (DACs). */
+#define S626_A2_CLKSRC_X4 0x01400000 /* A2 bit rate = ACLK/4
+ * (DACs). */
+#define S626_INVERT_BCLK2 0x00100000 /* Invert BCLK2 (DACs). */
+#define S626_BCLK2_OE 0x00040000 /* Enable BCLK2 (DACs). */
+#define S626_ACON2_XORMASK 0x000C0000 /* XOR mask for ACON2
+ * active-low bits. */
+
+#define S626_ACON2_INIT (S626_ACON2_XORMASK ^ \
+ (S626_A1_CLKSRC_BCLK1 | S626_A2_CLKSRC_X2 | \
+ S626_INVERT_BCLK2 | S626_BCLK2_OE))
+
+/* Bit masks for timeslot records. */
+#define S626_WS1 0x40000000 /* WS output to assert. */
+#define S626_WS2 0x20000000
+#define S626_WS3 0x10000000
+#define S626_WS4 0x08000000
+#define S626_RSD1 0x01000000 /* Shift A1 data in on SD1. */
+#define S626_SDW_A1 0x00800000 /* Store rcv'd char at next char
+ * slot of DWORD1 buffer. */
+#define S626_SIB_A1 0x00400000 /* Store rcv'd char at next
+ * char slot of FB1 buffer. */
+#define S626_SF_A1 0x00200000 /* Write unsigned long
+ * buffer to input FIFO. */
+
+/* Select parallel-to-serial converter's data source: */
+#define S626_XFIFO_0 0x00000000 /* Data fifo byte 0. */
+#define S626_XFIFO_1 0x00000010 /* Data fifo byte 1. */
+#define S626_XFIFO_2 0x00000020 /* Data fifo byte 2. */
+#define S626_XFIFO_3 0x00000030 /* Data fifo byte 3. */
+#define S626_XFB0 0x00000040 /* FB_BUFFER byte 0. */
+#define S626_XFB1 0x00000050 /* FB_BUFFER byte 1. */
+#define S626_XFB2 0x00000060 /* FB_BUFFER byte 2. */
+#define S626_XFB3 0x00000070 /* FB_BUFFER byte 3. */
+#define S626_SIB_A2 0x00000200 /* Store next dword from A2's
+ * input shifter to FB2
+ * buffer. */
+#define S626_SF_A2 0x00000100 /* Store next dword from A2's
+ * input shifter to its input
+ * fifo. */
+#define S626_LF_A2 0x00000080 /* Load next dword from A2's
+ * output fifo into its
+ * output dword buffer. */
+#define S626_XSD2 0x00000008 /* Shift data out on SD2. */
+#define S626_RSD3 0x00001800 /* Shift data in on SD3. */
+#define S626_RSD2 0x00001000 /* Shift data in on SD2. */
+#define S626_LOW_A2 0x00000002 /* Drive last SD low for 7 clks,
+ * then tri-state. */
+#define S626_EOS 0x00000001 /* End of superframe. */
+
+/* I2C configuration constants. */
+#define S626_I2C_CLKSEL 0x0400 /* I2C bit rate =
+ * PCIclk/480 = 68.75 KHz. */
+#define S626_I2C_BITRATE 68.75 /* I2C bus data bit rate
+ * (determined by
+ * S626_I2C_CLKSEL) in KHz. */
+#define S626_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 S626_I2C_RETRIES (S626_I2C_WRTIME * S626_I2C_BITRATE / 9.0)
+#define S626_I2C_ERR 0x0002 /* I2C control/status flag ERROR. */
+#define S626_I2C_BUSY 0x0001 /* I2C control/status flag BUSY. */
+#define S626_I2C_ABORT 0x0080 /* I2C status flag ABORT. */
+#define S626_I2C_ATTRSTART 0x3 /* I2C attribute START. */
+#define S626_I2C_ATTRCONT 0x2 /* I2C attribute CONT. */
+#define S626_I2C_ATTRSTOP 0x1 /* I2C attribute STOP. */
+#define S626_I2C_ATTRNOP 0x0 /* I2C attribute NOP. */
+
+/* Code macros used for constructing I2C command bytes. */
+#define S626_I2C_B2(ATTR, VAL) (((ATTR) << 6) | ((VAL) << 24))
+#define S626_I2C_B1(ATTR, VAL) (((ATTR) << 4) | ((VAL) << 16))
+#define S626_I2C_B0(ATTR, VAL) (((ATTR) << 2) | ((VAL) << 8))
+
+/* DEBI command constants. */
+#define S626_DEBI_CMD_SIZE16 (2 << 17) /* Transfer size is always
+ * 2 bytes. */
+#define S626_DEBI_CMD_READ 0x00010000 /* Read operation. */
+#define S626_DEBI_CMD_WRITE 0x00000000 /* Write operation. */
+
+/* Read immediate 2 bytes. */
+#define S626_DEBI_CMD_RDWORD (S626_DEBI_CMD_READ | S626_DEBI_CMD_SIZE16)
+
+/* Write immediate 2 bytes. */
+#define S626_DEBI_CMD_WRWORD (S626_DEBI_CMD_WRITE | S626_DEBI_CMD_SIZE16)
+
+/* DEBI configuration constants. */
+#define S626_DEBI_CFG_XIRQ_EN 0x80000000 /* Enable external interrupt
+ * on GPIO3. */
+#define S626_DEBI_CFG_XRESUME 0x40000000 /* Resume block */
+ /* Transfer when XIRQ
+ * deasserted. */
+#define S626_DEBI_CFG_TOQ 0x03C00000 /* Timeout (15 PCI cycles). */
+#define S626_DEBI_CFG_FAST 0x10000000 /* Fast mode enable. */
+
+/* 4-bit field that specifies DEBI timeout value in PCI clock cycles: */
+#define S626_DEBI_CFG_TOUT_BIT 22 /* Finish DEBI cycle after this many
+ * clocks. */
+
+/* 2-bit field that specifies Endian byte lane steering: */
+#define S626_DEBI_CFG_SWAP_NONE 0x00000000 /* Straight - don't swap any
+ * bytes (Intel). */
+#define S626_DEBI_CFG_SWAP_2 0x00100000 /* 2-byte swap (Motorola). */
+#define S626_DEBI_CFG_SWAP_4 0x00200000 /* 4-byte swap. */
+#define S626_DEBI_CFG_SLAVE16 0x00080000 /* Slave is able to serve
+ * 16-bit cycles. */
+#define S626_DEBI_CFG_INC 0x00040000 /* Enable address increment
+ * for block transfers. */
+#define S626_DEBI_CFG_INTEL 0x00020000 /* Intel style local bus. */
+#define S626_DEBI_CFG_TIMEROFF 0x00010000 /* Disable timer. */
+
+#if S626_PLATFORM == S626_INTEL
+
+#define S626_DEBI_TOUT 7 /* Wait 7 PCI clocks (212 ns) before
+ * polling RDY. */
+
+/* Intel byte lane steering (pass through all byte lanes). */
+#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_NONE
+
+#elif S626_PLATFORM == S626_MOTOROLA
+
+#define S626_DEBI_TOUT 15 /* Wait 15 PCI clocks (454 ns) maximum
+ * before timing out. */
+
+/* Motorola byte lane steering. */
+#define S626_DEBI_SWAP S626_DEBI_CFG_SWAP_2
+
+#endif
+
+/* DEBI page table constants. */
+#define S626_DEBI_PAGE_DISABLE 0x00000000 /* Paging disable. */
+
+/* ******* EXTRA FROM OTHER SENSORAY * .h ******* */
+
+/* LoadSrc values: */
+#define S626_LOADSRC_INDX 0 /* Preload core in response to Index. */
+#define S626_LOADSRC_OVER 1 /* Preload core in response to
+ * Overflow. */
+#define S626_LOADSRCB_OVERA 2 /* Preload B core in response to
+ * A Overflow. */
+#define S626_LOADSRC_NONE 3 /* Never preload core. */
+
+/* IntSrc values: */
+#define S626_INTSRC_NONE 0 /* Interrupts disabled. */
+#define S626_INTSRC_OVER 1 /* Interrupt on Overflow. */
+#define S626_INTSRC_INDX 2 /* Interrupt on Index. */
+#define S626_INTSRC_BOTH 3 /* Interrupt on Index or Overflow. */
+
+/* LatchSrc values: */
+#define S626_LATCHSRC_AB_READ 0 /* Latch on read. */
+#define S626_LATCHSRC_A_INDXA 1 /* Latch A on A Index. */
+#define S626_LATCHSRC_B_INDXB 2 /* Latch B on B Index. */
+#define S626_LATCHSRC_B_OVERA 3 /* Latch B on A Overflow. */
+
+/* IndxSrc values: */
+#define S626_INDXSRC_ENCODER 0 /* Encoder. */
+#define S626_INDXSRC_DIGIN 1 /* Digital inputs. */
+#define S626_INDXSRC_SOFT 2 /* S/w controlled by IndxPol bit. */
+#define S626_INDXSRC_DISABLED 3 /* Index disabled. */
+
+/* IndxPol values: */
+#define S626_INDXPOL_POS 0 /* Index input is active high. */
+#define S626_INDXPOL_NEG 1 /* Index input is active low. */
+
+/* Logical encoder mode values: */
+#define S626_ENCMODE_COUNTER 0 /* Counter mode. */
+#define S626_ENCMODE_TIMER 2 /* Timer mode. */
+#define S626_ENCMODE_EXTENDER 3 /* Extender mode. */
+
+/* Physical CntSrc values (for Counter A source and Counter B source): */
+#define S626_CNTSRC_ENCODER 0 /* Encoder */
+#define S626_CNTSRC_DIGIN 1 /* Digital inputs */
+#define S626_CNTSRC_SYSCLK 2 /* System clock up */
+#define S626_CNTSRC_SYSCLK_DOWN 3 /* System clock down */
+
+/* ClkPol values: */
+#define S626_CLKPOL_POS 0 /* Counter/Extender clock is
+ * active high. */
+#define S626_CLKPOL_NEG 1 /* Counter/Extender clock is
+ * active low. */
+#define S626_CNTDIR_UP 0 /* Timer counts up. */
+#define S626_CNTDIR_DOWN 1 /* Timer counts down. */
+
+/* ClkEnab values: */
+#define S626_CLKENAB_ALWAYS 0 /* Clock always enabled. */
+#define S626_CLKENAB_INDEX 1 /* Clock is enabled by index. */
+
+/* ClkMult values: */
+#define S626_CLKMULT_4X 0 /* 4x clock multiplier. */
+#define S626_CLKMULT_2X 1 /* 2x clock multiplier. */
+#define S626_CLKMULT_1X 2 /* 1x clock multiplier. */
+#define S626_CLKMULT_SPECIAL 3 /* Special clock multiplier value. */
+
+/* Sanity-check limits for parameters. */
+
+#define S626_NUM_COUNTERS 6 /* Maximum valid counter
+ * logical channel number. */
+#define S626_NUM_INTSOURCES 4
+#define S626_NUM_LATCHSOURCES 4
+#define S626_NUM_CLKMULTS 4
+#define S626_NUM_CLKSOURCES 4
+#define S626_NUM_CLKPOLS 2
+#define S626_NUM_INDEXPOLS 2
+#define S626_NUM_INDEXSOURCES 2
+#define S626_NUM_LOADTRIGS 4
+
+/* General macros for manipulating bitfields: */
+#define S626_MAKE(x, w, p) (((x) & ((1 << (w)) - 1)) << (p))
+#define S626_UNMAKE(v, w, p) (((v) >> (p)) & ((1 << (w)) - 1))
+
+/* Bit field positions in CRA: */
+#define S626_CRABIT_INDXSRC_B 14 /* B index source. */
+#define S626_CRABIT_CNTSRC_B 12 /* B counter source. */
+#define S626_CRABIT_INDXPOL_A 11 /* A index polarity. */
+#define S626_CRABIT_LOADSRC_A 9 /* A preload trigger. */
+#define S626_CRABIT_CLKMULT_A 7 /* A clock multiplier. */
+#define S626_CRABIT_INTSRC_A 5 /* A interrupt source. */
+#define S626_CRABIT_CLKPOL_A 4 /* A clock polarity. */
+#define S626_CRABIT_INDXSRC_A 2 /* A index source. */
+#define S626_CRABIT_CNTSRC_A 0 /* A counter source. */
+
+/* Bit field widths in CRA: */
+#define S626_CRAWID_INDXSRC_B 2
+#define S626_CRAWID_CNTSRC_B 2
+#define S626_CRAWID_INDXPOL_A 1
+#define S626_CRAWID_LOADSRC_A 2
+#define S626_CRAWID_CLKMULT_A 2
+#define S626_CRAWID_INTSRC_A 2
+#define S626_CRAWID_CLKPOL_A 1
+#define S626_CRAWID_INDXSRC_A 2
+#define S626_CRAWID_CNTSRC_A 2
+
+/* Bit field masks for CRA: */
+#define S626_CRAMSK_INDXSRC_B S626_SET_CRA_INDXSRC_B(~0)
+#define S626_CRAMSK_CNTSRC_B S626_SET_CRA_CNTSRC_B(~0)
+#define S626_CRAMSK_INDXPOL_A S626_SET_CRA_INDXPOL_A(~0)
+#define S626_CRAMSK_LOADSRC_A S626_SET_CRA_LOADSRC_A(~0)
+#define S626_CRAMSK_CLKMULT_A S626_SET_CRA_CLKMULT_A(~0)
+#define S626_CRAMSK_INTSRC_A S626_SET_CRA_INTSRC_A(~0)
+#define S626_CRAMSK_CLKPOL_A S626_SET_CRA_CLKPOL_A(~0)
+#define S626_CRAMSK_INDXSRC_A S626_SET_CRA_INDXSRC_A(~0)
+#define S626_CRAMSK_CNTSRC_A S626_SET_CRA_CNTSRC_A(~0)
+
+/* Construct parts of the CRA value: */
+#define S626_SET_CRA_INDXSRC_B(x) \
+ S626_MAKE((x), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B)
+#define S626_SET_CRA_CNTSRC_B(x) \
+ S626_MAKE((x), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B)
+#define S626_SET_CRA_INDXPOL_A(x) \
+ S626_MAKE((x), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A)
+#define S626_SET_CRA_LOADSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A)
+#define S626_SET_CRA_CLKMULT_A(x) \
+ S626_MAKE((x), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A)
+#define S626_SET_CRA_INTSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A)
+#define S626_SET_CRA_CLKPOL_A(x) \
+ S626_MAKE((x), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A)
+#define S626_SET_CRA_INDXSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A)
+#define S626_SET_CRA_CNTSRC_A(x) \
+ S626_MAKE((x), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A)
+
+/* Extract parts of the CRA value: */
+#define S626_GET_CRA_INDXSRC_B(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXSRC_B, S626_CRABIT_INDXSRC_B)
+#define S626_GET_CRA_CNTSRC_B(v) \
+ S626_UNMAKE((v), S626_CRAWID_CNTSRC_B, S626_CRABIT_CNTSRC_B)
+#define S626_GET_CRA_INDXPOL_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXPOL_A, S626_CRABIT_INDXPOL_A)
+#define S626_GET_CRA_LOADSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_LOADSRC_A, S626_CRABIT_LOADSRC_A)
+#define S626_GET_CRA_CLKMULT_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CLKMULT_A, S626_CRABIT_CLKMULT_A)
+#define S626_GET_CRA_INTSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INTSRC_A, S626_CRABIT_INTSRC_A)
+#define S626_GET_CRA_CLKPOL_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CLKPOL_A, S626_CRABIT_CLKPOL_A)
+#define S626_GET_CRA_INDXSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_INDXSRC_A, S626_CRABIT_INDXSRC_A)
+#define S626_GET_CRA_CNTSRC_A(v) \
+ S626_UNMAKE((v), S626_CRAWID_CNTSRC_A, S626_CRABIT_CNTSRC_A)
+
+/* Bit field positions in CRB: */
+#define S626_CRBBIT_INTRESETCMD 15 /* (w) Interrupt reset command. */
+#define S626_CRBBIT_CNTDIR_B 15 /* (r) B counter direction. */
+#define S626_CRBBIT_INTRESET_B 14 /* (w) B interrupt reset enable. */
+#define S626_CRBBIT_OVERDO_A 14 /* (r) A overflow routed to dig. out. */
+#define S626_CRBBIT_INTRESET_A 13 /* (w) A interrupt reset enable. */
+#define S626_CRBBIT_OVERDO_B 13 /* (r) B overflow routed to dig. out. */
+#define S626_CRBBIT_CLKENAB_A 12 /* A clock enable. */
+#define S626_CRBBIT_INTSRC_B 10 /* B interrupt source. */
+#define S626_CRBBIT_LATCHSRC 8 /* A/B latch source. */
+#define S626_CRBBIT_LOADSRC_B 6 /* B preload trigger. */
+#define S626_CRBBIT_CLEAR_B 7 /* B cleared when A overflows. */
+#define S626_CRBBIT_CLKMULT_B 3 /* B clock multiplier. */
+#define S626_CRBBIT_CLKENAB_B 2 /* B clock enable. */
+#define S626_CRBBIT_INDXPOL_B 1 /* B index polarity. */
+#define S626_CRBBIT_CLKPOL_B 0 /* B clock polarity. */
+
+/* Bit field widths in CRB: */
+#define S626_CRBWID_INTRESETCMD 1
+#define S626_CRBWID_CNTDIR_B 1
+#define S626_CRBWID_INTRESET_B 1
+#define S626_CRBWID_OVERDO_A 1
+#define S626_CRBWID_INTRESET_A 1
+#define S626_CRBWID_OVERDO_B 1
+#define S626_CRBWID_CLKENAB_A 1
+#define S626_CRBWID_INTSRC_B 2
+#define S626_CRBWID_LATCHSRC 2
+#define S626_CRBWID_LOADSRC_B 2
+#define S626_CRBWID_CLEAR_B 1
+#define S626_CRBWID_CLKMULT_B 2
+#define S626_CRBWID_CLKENAB_B 1
+#define S626_CRBWID_INDXPOL_B 1
+#define S626_CRBWID_CLKPOL_B 1
+
+/* Bit field masks for CRB: */
+#define S626_CRBMSK_INTRESETCMD S626_SET_CRB_INTRESETCMD(~0) /* (w) */
+#define S626_CRBMSK_CNTDIR_B S626_CRBMSK_INTRESETCMD /* (r) */
+#define S626_CRBMSK_INTRESET_B S626_SET_CRB_INTRESET_B(~0) /* (w) */
+#define S626_CRBMSK_OVERDO_A S626_CRBMSK_INTRESET_B /* (r) */
+#define S626_CRBMSK_INTRESET_A S626_SET_CRB_INTRESET_A(~0) /* (w) */
+#define S626_CRBMSK_OVERDO_B S626_CRBMSK_INTRESET_A /* (r) */
+#define S626_CRBMSK_CLKENAB_A S626_SET_CRB_CLKENAB_A(~0)
+#define S626_CRBMSK_INTSRC_B S626_SET_CRB_INTSRC_B(~0)
+#define S626_CRBMSK_LATCHSRC S626_SET_CRB_LATCHSRC(~0)
+#define S626_CRBMSK_LOADSRC_B S626_SET_CRB_LOADSRC_B(~0)
+#define S626_CRBMSK_CLEAR_B S626_SET_CRB_CLEAR_B(~0)
+#define S626_CRBMSK_CLKMULT_B S626_SET_CRB_CLKMULT_B(~0)
+#define S626_CRBMSK_CLKENAB_B S626_SET_CRB_CLKENAB_B(~0)
+#define S626_CRBMSK_INDXPOL_B S626_SET_CRB_INDXPOL_B(~0)
+#define S626_CRBMSK_CLKPOL_B S626_SET_CRB_CLKPOL_B(~0)
+
+/* Interrupt reset control bits. */
+#define S626_CRBMSK_INTCTRL (S626_CRBMSK_INTRESETCMD | \
+ S626_CRBMSK_INTRESET_A | \
+ S626_CRBMSK_INTRESET_B)
+
+/* Construct parts of the CRB value: */
+#define S626_SET_CRB_INTRESETCMD(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESETCMD, S626_CRBBIT_INTRESETCMD)
+#define S626_SET_CRB_INTRESET_B(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESET_B, S626_CRBBIT_INTRESET_B)
+#define S626_SET_CRB_INTRESET_A(x) \
+ S626_MAKE((x), S626_CRBWID_INTRESET_A, S626_CRBBIT_INTRESET_A)
+#define S626_SET_CRB_CLKENAB_A(x) \
+ S626_MAKE((x), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A)
+#define S626_SET_CRB_INTSRC_B(x) \
+ S626_MAKE((x), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B)
+#define S626_SET_CRB_LATCHSRC(x) \
+ S626_MAKE((x), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC)
+#define S626_SET_CRB_LOADSRC_B(x) \
+ S626_MAKE((x), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B)
+#define S626_SET_CRB_CLEAR_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B)
+#define S626_SET_CRB_CLKMULT_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B)
+#define S626_SET_CRB_CLKENAB_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B)
+#define S626_SET_CRB_INDXPOL_B(x) \
+ S626_MAKE((x), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B)
+#define S626_SET_CRB_CLKPOL_B(x) \
+ S626_MAKE((x), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B)
+
+/* Extract parts of the CRB value: */
+#define S626_GET_CRB_CNTDIR_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CNTDIR_B, S626_CRBBIT_CNTDIR_B)
+#define S626_GET_CRB_OVERDO_A(v) \
+ S626_UNMAKE((v), S626_CRBWID_OVERDO_A, S626_CRBBIT_OVERDO_A)
+#define S626_GET_CRB_OVERDO_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_OVERDO_B, S626_CRBBIT_OVERDO_B)
+#define S626_GET_CRB_CLKENAB_A(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKENAB_A, S626_CRBBIT_CLKENAB_A)
+#define S626_GET_CRB_INTSRC_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_INTSRC_B, S626_CRBBIT_INTSRC_B)
+#define S626_GET_CRB_LATCHSRC(v) \
+ S626_UNMAKE((v), S626_CRBWID_LATCHSRC, S626_CRBBIT_LATCHSRC)
+#define S626_GET_CRB_LOADSRC_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_LOADSRC_B, S626_CRBBIT_LOADSRC_B)
+#define S626_GET_CRB_CLEAR_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLEAR_B, S626_CRBBIT_CLEAR_B)
+#define S626_GET_CRB_CLKMULT_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKMULT_B, S626_CRBBIT_CLKMULT_B)
+#define S626_GET_CRB_CLKENAB_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKENAB_B, S626_CRBBIT_CLKENAB_B)
+#define S626_GET_CRB_INDXPOL_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_INDXPOL_B, S626_CRBBIT_INDXPOL_B)
+#define S626_GET_CRB_CLKPOL_B(v) \
+ S626_UNMAKE((v), S626_CRBWID_CLKPOL_B, S626_CRBBIT_CLKPOL_B)
+
+/* Bit field positions for standardized SETUP structure: */
+#define S626_STDBIT_INTSRC 13
+#define S626_STDBIT_LATCHSRC 11
+#define S626_STDBIT_LOADSRC 9
+#define S626_STDBIT_INDXSRC 7
+#define S626_STDBIT_INDXPOL 6
+#define S626_STDBIT_ENCMODE 4
+#define S626_STDBIT_CLKPOL 3
+#define S626_STDBIT_CLKMULT 1
+#define S626_STDBIT_CLKENAB 0
+
+/* Bit field widths for standardized SETUP structure: */
+#define S626_STDWID_INTSRC 2
+#define S626_STDWID_LATCHSRC 2
+#define S626_STDWID_LOADSRC 2
+#define S626_STDWID_INDXSRC 2
+#define S626_STDWID_INDXPOL 1
+#define S626_STDWID_ENCMODE 2
+#define S626_STDWID_CLKPOL 1
+#define S626_STDWID_CLKMULT 2
+#define S626_STDWID_CLKENAB 1
+
+/* Bit field masks for standardized SETUP structure: */
+#define S626_STDMSK_INTSRC S626_SET_STD_INTSRC(~0)
+#define S626_STDMSK_LATCHSRC S626_SET_STD_LATCHSRC(~0)
+#define S626_STDMSK_LOADSRC S626_SET_STD_LOADSRC(~0)
+#define S626_STDMSK_INDXSRC S626_SET_STD_INDXSRC(~0)
+#define S626_STDMSK_INDXPOL S626_SET_STD_INDXPOL(~0)
+#define S626_STDMSK_ENCMODE S626_SET_STD_ENCMODE(~0)
+#define S626_STDMSK_CLKPOL S626_SET_STD_CLKPOL(~0)
+#define S626_STDMSK_CLKMULT S626_SET_STD_CLKMULT(~0)
+#define S626_STDMSK_CLKENAB S626_SET_STD_CLKENAB(~0)
+
+/* Construct parts of standardized SETUP structure: */
+#define S626_SET_STD_INTSRC(x) \
+ S626_MAKE((x), S626_STDWID_INTSRC, S626_STDBIT_INTSRC)
+#define S626_SET_STD_LATCHSRC(x) \
+ S626_MAKE((x), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC)
+#define S626_SET_STD_LOADSRC(x) \
+ S626_MAKE((x), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC)
+#define S626_SET_STD_INDXSRC(x) \
+ S626_MAKE((x), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC)
+#define S626_SET_STD_INDXPOL(x) \
+ S626_MAKE((x), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL)
+#define S626_SET_STD_ENCMODE(x) \
+ S626_MAKE((x), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE)
+#define S626_SET_STD_CLKPOL(x) \
+ S626_MAKE((x), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL)
+#define S626_SET_STD_CLKMULT(x) \
+ S626_MAKE((x), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT)
+#define S626_SET_STD_CLKENAB(x) \
+ S626_MAKE((x), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB)
+
+/* Extract parts of standardized SETUP structure: */
+#define S626_GET_STD_INTSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_INTSRC, S626_STDBIT_INTSRC)
+#define S626_GET_STD_LATCHSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_LATCHSRC, S626_STDBIT_LATCHSRC)
+#define S626_GET_STD_LOADSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_LOADSRC, S626_STDBIT_LOADSRC)
+#define S626_GET_STD_INDXSRC(v) \
+ S626_UNMAKE((v), S626_STDWID_INDXSRC, S626_STDBIT_INDXSRC)
+#define S626_GET_STD_INDXPOL(v) \
+ S626_UNMAKE((v), S626_STDWID_INDXPOL, S626_STDBIT_INDXPOL)
+#define S626_GET_STD_ENCMODE(v) \
+ S626_UNMAKE((v), S626_STDWID_ENCMODE, S626_STDBIT_ENCMODE)
+#define S626_GET_STD_CLKPOL(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKPOL, S626_STDBIT_CLKPOL)
+#define S626_GET_STD_CLKMULT(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKMULT, S626_STDBIT_CLKMULT)
+#define S626_GET_STD_CLKENAB(v) \
+ S626_UNMAKE((v), S626_STDWID_CLKENAB, S626_STDBIT_CLKENAB)
+
+#endif
diff --git a/drivers/staging/comedi/drivers/serial2002.c b/drivers/staging/comedi/drivers/serial2002.c
new file mode 100644
index 00000000000..441813ffb17
--- /dev/null
+++ b/drivers/staging/comedi/drivers/serial2002.c
@@ -0,0 +1,807 @@
+/*
+ comedi/drivers/serial2002.c
+ Skeleton code for a Comedi driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2002 Anders Blomdell <anders.blomdell@control.lth.se>
+
+ This program is free software; you can redistribute 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.
+*/
+
+/*
+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 <linux/module.h>
+#include "../comedidev.h"
+
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/termios.h>
+#include <asm/ioctls.h>
+#include <linux/serial.h>
+#include <linux/poll.h>
+
+struct serial2002_range_table_t {
+
+ /* HACK... */
+ int length;
+ struct comedi_krange range;
+};
+
+struct serial2002_private {
+
+ int port; /* /dev/ttyS<port> */
+ 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];
+};
+
+struct serial_data {
+ enum { is_invalid, is_digital, is_channel } kind;
+ int index;
+ unsigned long value;
+};
+
+/*
+ * The configuration serial_data.value read from the device is
+ * a bitmask that defines specific options of a channel:
+ *
+ * 4:0 - the channel to configure
+ * 7:5 - the kind of channel
+ * 9:8 - the command used to configure the channel
+ *
+ * The remaining bits vary in use depending on the command:
+ *
+ * BITS 15:10 - the channel bits (maxdata)
+ * MIN/MAX 12:10 - the units multiplier for the scale
+ * 13 - the sign of the scale
+ * 33:14 - the base value for the range
+ */
+#define S2002_CFG_CHAN(x) ((x) & 0x1f)
+#define S2002_CFG_KIND(x) (((x) >> 5) & 0x7)
+#define S2002_CFG_KIND_INVALID 0
+#define S2002_CFG_KIND_DIGITAL_IN 1
+#define S2002_CFG_KIND_DIGITAL_OUT 2
+#define S2002_CFG_KIND_ANALOG_IN 3
+#define S2002_CFG_KIND_ANALOG_OUT 4
+#define S2002_CFG_KIND_ENCODER_IN 5
+#define S2002_CFG_CMD(x) (((x) >> 8) & 0x3)
+#define S2002_CFG_CMD_BITS 0
+#define S2002_CFG_CMD_MIN 1
+#define S2002_CFG_CMD_MAX 2
+#define S2002_CFG_BITS(x) (((x) >> 10) & 0x3f)
+#define S2002_CFG_UNITS(x) (((x) >> 10) & 0x7)
+#define S2002_CFG_SIGN(x) (((x) >> 13) & 0x1)
+#define S2002_CFG_BASE(x) (((x) >> 14) & 0xfffff)
+
+static long serial2002_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 serial2002_tty_write(struct file *f, unsigned char *buf, int count)
+{
+ const char __user *p = (__force const char __user *)buf;
+ int result;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+ f->f_pos = 0;
+ result = f->f_op->write(f, p, count, &f->f_pos);
+ set_fs(oldfs);
+ return result;
+}
+
+static int serial2002_tty_readb(struct file *f, unsigned char *buf)
+{
+ char __user *p = (__force char __user *)buf;
+
+ f->f_pos = 0;
+ return f->f_op->read(f, p, 1, &f->f_pos);
+}
+
+static void serial2002_tty_read_poll_wait(struct file *f, int timeout)
+{
+ 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);
+}
+
+static int serial2002_tty_read(struct file *f, int timeout)
+{
+ unsigned char ch;
+ int result;
+
+ result = -1;
+ if (!IS_ERR(f)) {
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+ if (f->f_op->poll) {
+ serial2002_tty_read_poll_wait(f, timeout);
+
+ if (serial2002_tty_readb(f, &ch) == 1)
+ result = ch;
+ } else {
+ /* Device does not support poll, busy wait */
+ int retries = 0;
+ while (1) {
+ retries++;
+ if (retries >= timeout)
+ break;
+
+ if (serial2002_tty_readb(f, &ch) == 1) {
+ result = ch;
+ break;
+ }
+ udelay(100);
+ }
+ }
+ set_fs(oldfs);
+ }
+ return result;
+}
+
+static void serial2002_tty_setspeed(struct file *f, int speed)
+{
+ struct termios termios;
+ struct serial_struct serial;
+ mm_segment_t oldfs;
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
+
+ /* Set speed */
+ serial2002_tty_ioctl(f, TCGETS, (unsigned long)&termios);
+ termios.c_iflag = 0;
+ termios.c_oflag = 0;
+ termios.c_lflag = 0;
+ termios.c_cflag = CLOCAL | CS8 | CREAD;
+ termios.c_cc[VMIN] = 0;
+ termios.c_cc[VTIME] = 0;
+ switch (speed) {
+ case 2400:
+ termios.c_cflag |= B2400;
+ break;
+ case 4800:
+ termios.c_cflag |= B4800;
+ break;
+ case 9600:
+ termios.c_cflag |= B9600;
+ break;
+ case 19200:
+ termios.c_cflag |= B19200;
+ break;
+ case 38400:
+ termios.c_cflag |= B38400;
+ break;
+ case 57600:
+ termios.c_cflag |= B57600;
+ break;
+ case 115200:
+ termios.c_cflag |= B115200;
+ break;
+ default:
+ termios.c_cflag |= B9600;
+ break;
+ }
+ serial2002_tty_ioctl(f, TCSETS, (unsigned long)&termios);
+
+ /* Set low latency */
+ serial2002_tty_ioctl(f, TIOCGSERIAL, (unsigned long)&serial);
+ serial.flags |= ASYNC_LOW_LATENCY;
+ serial2002_tty_ioctl(f, TIOCSSERIAL, (unsigned long)&serial);
+
+ set_fs(oldfs);
+}
+
+static void serial2002_poll_digital(struct file *f, int channel)
+{
+ char cmd;
+
+ cmd = 0x40 | (channel & 0x1f);
+ serial2002_tty_write(f, &cmd, 1);
+}
+
+static void serial2002_poll_channel(struct file *f, int channel)
+{
+ char cmd;
+
+ cmd = 0x60 | (channel & 0x1f);
+ serial2002_tty_write(f, &cmd, 1);
+}
+
+static struct serial_data serial2002_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 = serial2002_tty_read(f, timeout);
+
+ length++;
+ if (data < 0) {
+ 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 serial2002_write(struct file *f, struct serial_data data)
+{
+ if (data.kind == is_digital) {
+ unsigned char ch =
+ ((data.value << 5) & 0x20) | (data.index & 0x1f);
+ serial2002_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++;
+ serial2002_tty_write(f, ch, i);
+ }
+}
+
+struct config_t {
+ short int kind;
+ short int bits;
+ int min;
+ int max;
+};
+
+static int serial2002_setup_subdevice(struct comedi_subdevice *s,
+ struct config_t *cfg,
+ struct serial2002_range_table_t *range,
+ unsigned char *mapping,
+ int kind)
+{
+ const struct comedi_lrange **range_table_list = NULL;
+ unsigned int *maxdata_list;
+ int j, chan;
+
+ for (chan = 0, j = 0; j < 32; j++) {
+ if (cfg[j].kind == kind)
+ chan++;
+ }
+ s->n_chan = chan;
+ s->maxdata = 0;
+ kfree(s->maxdata_list);
+ maxdata_list = kmalloc(sizeof(unsigned int) * s->n_chan, GFP_KERNEL);
+ if (!maxdata_list)
+ return -ENOMEM;
+ s->maxdata_list = maxdata_list;
+ kfree(s->range_table_list);
+ s->range_table = NULL;
+ s->range_table_list = NULL;
+ if (kind == 1 || kind == 2) {
+ s->range_table = &range_digital;
+ } else if (range) {
+ range_table_list =
+ kmalloc(sizeof(struct serial2002_range_table_t) *
+ s->n_chan, GFP_KERNEL);
+ if (!range_table_list)
+ return -ENOMEM;
+ s->range_table_list = range_table_list;
+ }
+ for (chan = 0, j = 0; j < 32; j++) {
+ if (cfg[j].kind == kind) {
+ if (mapping)
+ mapping[chan] = j;
+ if (range) {
+ range[j].length = 1;
+ range[j].range.min = cfg[j].min;
+ range[j].range.max = cfg[j].max;
+ range_table_list[chan] =
+ (const struct comedi_lrange *)&range[j];
+ }
+ maxdata_list[chan] = ((long long)1 << cfg[j].bits) - 1;
+ chan++;
+ }
+ }
+ return 0;
+}
+
+static int serial2002_setup_subdevs(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+ struct config_t *di_cfg;
+ struct config_t *do_cfg;
+ struct config_t *ai_cfg;
+ struct config_t *ao_cfg;
+ struct config_t *cfg;
+ struct comedi_subdevice *s;
+ int result = 0;
+ int i;
+
+ /* Allocate the temporary structs to hold the configuration data */
+ di_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ do_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ ai_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ ao_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
+ if (!di_cfg || !do_cfg || !ai_cfg || !ao_cfg) {
+ result = -ENOMEM;
+ goto err_alloc_configs;
+ }
+
+ /* Read the configuration from the connected device */
+ serial2002_tty_setspeed(devpriv->tty, devpriv->speed);
+ serial2002_poll_channel(devpriv->tty, 31);
+ while (1) {
+ struct serial_data data;
+
+ data = serial2002_read(devpriv->tty, 1000);
+ if (data.kind != is_channel || data.index != 31 ||
+ S2002_CFG_KIND(data.value) == S2002_CFG_KIND_INVALID) {
+ break;
+ } else {
+ int channel = S2002_CFG_CHAN(data.value);
+ int range = S2002_CFG_BASE(data.value);
+
+ switch (S2002_CFG_KIND(data.value)) {
+ case S2002_CFG_KIND_DIGITAL_IN:
+ cfg = di_cfg;
+ break;
+ case S2002_CFG_KIND_DIGITAL_OUT:
+ cfg = do_cfg;
+ break;
+ case S2002_CFG_KIND_ANALOG_IN:
+ cfg = ai_cfg;
+ break;
+ case S2002_CFG_KIND_ANALOG_OUT:
+ cfg = ao_cfg;
+ break;
+ case S2002_CFG_KIND_ENCODER_IN:
+ cfg = ai_cfg;
+ break;
+ default:
+ cfg = NULL;
+ break;
+ }
+ if (!cfg)
+ continue; /* unknown kind, skip it */
+
+ cfg[channel].kind = S2002_CFG_KIND(data.value);
+
+ switch (S2002_CFG_CMD(data.value)) {
+ case S2002_CFG_CMD_BITS:
+ cfg[channel].bits = S2002_CFG_BITS(data.value);
+ break;
+ case S2002_CFG_CMD_MIN:
+ case S2002_CFG_CMD_MAX:
+ switch (S2002_CFG_UNITS(data.value)) {
+ case 0:
+ range *= 1000000;
+ break;
+ case 1:
+ range *= 1000;
+ break;
+ case 2:
+ range *= 1;
+ break;
+ }
+ if (S2002_CFG_SIGN(data.value))
+ range = -range;
+ if (S2002_CFG_CMD(data.value) ==
+ S2002_CFG_CMD_MIN)
+ cfg[channel].min = range;
+ else
+ cfg[channel].max = range;
+ break;
+ }
+ }
+ }
+
+ /* Fill in subdevice data */
+ for (i = 0; i <= 4; i++) {
+ unsigned char *mapping = NULL;
+ struct serial2002_range_table_t *range = NULL;
+ int kind = 0;
+
+ s = &dev->subdevices[i];
+
+ switch (i) {
+ case 0:
+ cfg = di_cfg;
+ mapping = devpriv->digital_in_mapping;
+ kind = S2002_CFG_KIND_DIGITAL_IN;
+ break;
+ case 1:
+ cfg = do_cfg;
+ mapping = devpriv->digital_out_mapping;
+ kind = S2002_CFG_KIND_DIGITAL_OUT;
+ break;
+ case 2:
+ cfg = ai_cfg;
+ mapping = devpriv->analog_in_mapping;
+ range = devpriv->in_range;
+ kind = S2002_CFG_KIND_ANALOG_IN;
+ break;
+ case 3:
+ cfg = ao_cfg;
+ mapping = devpriv->analog_out_mapping;
+ range = devpriv->out_range;
+ kind = S2002_CFG_KIND_ANALOG_OUT;
+ break;
+ case 4:
+ cfg = ai_cfg;
+ mapping = devpriv->encoder_in_mapping;
+ range = devpriv->in_range;
+ kind = S2002_CFG_KIND_ENCODER_IN;
+ break;
+ }
+
+ if (serial2002_setup_subdevice(s, cfg, range, mapping, kind))
+ break; /* err handled below */
+ }
+ 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++) {
+ 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(di_cfg);
+ kfree(do_cfg);
+ kfree(ai_cfg);
+ kfree(ao_cfg);
+
+ if (result) {
+ if (devpriv->tty) {
+ filp_close(devpriv->tty, NULL);
+ devpriv->tty = NULL;
+ }
+ }
+
+ return result;
+}
+
+static int serial2002_open(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+ 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);
+ dev_err(dev->class_dev, "file open error = %d\n", result);
+ } else {
+ result = serial2002_setup_subdevs(dev);
+ }
+ return result;
+}
+
+static void serial2002_close(struct comedi_device *dev)
+{
+ struct serial2002_private *devpriv = dev->private;
+
+ if (!IS_ERR(devpriv->tty) && devpriv->tty)
+ filp_close(devpriv->tty, NULL);
+}
+
+static int serial2002_di_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_digital(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_read(devpriv->tty, 1000);
+ if (read.kind != is_digital || read.index == chan)
+ break;
+ }
+ data[n] = read.value;
+ }
+ return n;
+}
+
+static int serial2002_do_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ 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];
+ serial2002_write(devpriv->tty, write);
+ }
+ return n;
+}
+
+static int serial2002_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_channel(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_read(devpriv->tty, 1000);
+ if (read.kind != is_channel || read.index == chan)
+ break;
+ }
+ data[n] = read.value;
+ }
+ return n;
+}
+
+static int serial2002_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ 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];
+ serial2002_write(devpriv->tty, write);
+ devpriv->ao_readback[chan] = data[n];
+ }
+ return n;
+}
+
+static int serial2002_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ 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_encoder_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct serial2002_private *devpriv = dev->private;
+ int n;
+ int chan;
+
+ chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
+ for (n = 0; n < insn->n; n++) {
+ struct serial_data read;
+
+ serial2002_poll_channel(devpriv->tty, chan);
+ while (1) {
+ read = serial2002_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 serial2002_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->port = it->options[0];
+ devpriv->speed = it->options[1];
+
+ ret = comedi_alloc_subdevices(dev, 5);
+ if (ret)
+ return ret;
+
+ /* 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_insn_read;
+
+ /* 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_insn_write;
+
+ /* 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 = serial2002_ai_insn_read;
+
+ /* 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 = serial2002_ao_insn_write;
+ s->insn_read = serial2002_ao_insn_read;
+
+ /* 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 = NULL;
+ s->insn_read = serial2002_encoder_insn_read;
+
+ dev->open = serial2002_open;
+ dev->close = serial2002_close;
+
+ return 0;
+}
+
+static void serial2002_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ kfree(s->maxdata_list);
+ kfree(s->range_table_list);
+ }
+}
+
+static struct comedi_driver serial2002_driver = {
+ .driver_name = "serial2002",
+ .module = THIS_MODULE,
+ .attach = serial2002_attach,
+ .detach = serial2002_detach,
+};
+module_comedi_driver(serial2002_driver);
+
+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 00000000000..3bfa221faf4
--- /dev/null
+++ b/drivers/staging/comedi/drivers/skel.c
@@ -0,0 +1,729 @@
+/*
+ comedi/drivers/skel.c
+ Skeleton code for a Comedi driver
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+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 <linux/module.h>
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/* 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.
+ */
+enum skel_boardid {
+ BOARD_SKEL100,
+ BOARD_SKEL200,
+};
+
+struct skel_board {
+ const char *name;
+ int ai_chans;
+ int ai_bits;
+ int have_dio;
+};
+
+static const struct skel_board skel_boards[] = {
+ [BOARD_SKEL100] = {
+ .name = "skel-100",
+ .ai_chans = 16,
+ .ai_bits = 12,
+ .have_dio = 1,
+ },
+ [BOARD_SKEL200] = {
+ .name = "skel-200",
+ .ai_chans = 8,
+ .ai_bits = 16,
+ },
+};
+
+/* 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;
+
+ /* Used for AO readback */
+ unsigned int ao_readback[2];
+};
+
+/* 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;
+}
+
+/*
+ * This function doesn't require a particular form, this is just
+ * what happens to be used in some of the drivers. The comedi_timeout()
+ * helper uses this callback to check for the end-of-conversion while
+ * waiting for up to 1 second. This function should return 0 when the
+ * conversion is finished and -EBUSY to keep waiting. Any other errno
+ * will terminate comedi_timeout() and return that errno to the caller.
+ * If the timeout occurs, comedi_timeout() will return -ETIMEDOUT.
+ */
+static int skel_ai_eoc(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned long context)
+{
+ unsigned int status;
+
+ /* status = inb(dev->iobase + SKEL_STATUS); */
+ status = 1;
+ if (status)
+ return 0;
+ return -EBUSY;
+}
+
+/*
+ * "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)
+{
+ const struct skel_board *thisboard = comedi_board(dev);
+ int n;
+ unsigned int d;
+ int ret;
+
+ /* 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); */
+
+ /* wait for conversion to end */
+ ret = comedi_timeout(dev, s, insn, skel_ai_eoc, 0);
+ if (ret)
+ return ret;
+
+ /* 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;
+}
+
+/*
+ * 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.
+ */
+static int skel_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ int err = 0;
+ unsigned int arg;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+#define MAX_SPEED 10000 /* in nanoseconds */
+#define MIN_SPEED 1000000000 /* in nanoseconds */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ MAX_SPEED);
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg,
+ MIN_SPEED);
+ } else {
+ /* external trigger */
+ /* should be level/edge, hi/lo specification here */
+ /* should specify multiple external triggers */
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ }
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, MAX_SPEED);
+ err |= cfc_check_trigger_arg_max(&cmd->convert_arg, MIN_SPEED);
+ } else {
+ /* external trigger */
+ /* see above */
+ err |= cfc_check_trigger_arg_max(&cmd->scan_begin_arg, 9);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT)
+ err |= cfc_check_trigger_arg_max(&cmd->stop_arg, 0x00ffffff);
+ else /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+ if (err)
+ return 3;
+
+ /* step 4: fix up any arguments */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->scan_begin_arg;
+ skel_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
+ }
+ if (cmd->convert_src == TRIG_TIMER) {
+ arg = cmd->convert_arg;
+ skel_ns_to_timer(&arg, cmd->flags & TRIG_ROUND_MASK);
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, arg);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ arg = cmd->convert_arg * cmd->scan_end_arg;
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ arg);
+ }
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+ struct comedi_insn *insn, unsigned int *data)
+{
+ struct skel_private *devpriv = dev->private;
+ 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++) {
+ /* 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)
+{
+ struct skel_private *devpriv = dev->private;
+ 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)
+{
+ /*
+ * The insn data is a mask in data[0] and the new data
+ * in data[1], each channel cooresponding to a bit.
+ *
+ * The core provided comedi_dio_update_state() function can
+ * be used to handle the internal state update to DIO subdevices
+ * with <= 32 channels. This function will return '0' if the
+ * state does not change or the mask of the channels that need
+ * to be updated.
+ */
+ if (comedi_dio_update_state(s, data)) {
+ /* 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 insn->n;
+}
+
+static int skel_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ /*
+ * The input or output configuration of each digital line is
+ * configured by special insn_config instructions.
+ *
+ * chanspec contains the channel to be changed
+ * data[0] contains the instruction to perform on the channel
+ *
+ * Normally the core provided comedi_dio_insn_config() function
+ * can be used to handle the boilerplpate.
+ */
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /* Update the hardware to the new configuration */
+ /* outw(s->io_bits, dev->iobase + SKEL_DIO_CONFIG); */
+
+ return insn->n;
+}
+
+/*
+ * Handle common part of skel_attach() and skel_auto_attach().
+ */
+static int skel_common_attach(struct comedi_device *dev)
+{
+ const struct skel_board *thisboard = comedi_board(dev);
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 3);
+ if (ret)
+ return ret;
+
+ 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;
+ }
+
+ return 0;
+}
+
+/*
+ * _attach is called by the Comedi core to configure the driver
+ * for a particular board in response to the COMEDI_DEVCONFIG ioctl for
+ * a matching board or driver name. If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that address.
+ *
+ * Drivers that handle only PCI or USB devices do not usually support
+ * manual attachment of those devices via the COMEDI_DEVCONFIG ioctl, so
+ * those drivers do not have an _attach function; they just have an
+ * _auto_attach function instead. (See skel_auto_attach() for an example
+ * of such a function.)
+ */
+static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ const struct skel_board *thisboard;
+ struct skel_private *devpriv;
+
+/*
+ * 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); */
+
+ thisboard = comedi_board(dev);
+
+ /*
+ * The dev->board_name is initialized by the comedi core before
+ * calling the (*attach) function. It can be optionally set by
+ * the driver if additional probing has been done.
+ */
+ /* dev->board_name = thisboard->name; */
+
+ /* Allocate the private data */
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+/*
+ * Supported boards are usually either auto-attached via the
+ * Comedi driver's _auto_attach routine, or manually attached via the
+ * Comedi driver's _attach routine. In most cases, attempts to
+ * manual attach boards that are usually auto-attached should be
+ * rejected by this function.
+ */
+/*
+ * if (thisboard->bustype == pci_bustype) {
+ * dev_err(dev->class_dev,
+ * "Manual attachment of PCI board '%s' not supported\n",
+ * thisboard->name);
+ * }
+ */
+
+/*
+ * For ISA boards, get the i/o base address from it->options[],
+ * request the i/o region and set dev->iobase * from it->options[].
+ * If using interrupts, get the IRQ number from it->options[].
+ */
+
+ /*
+ * Call a common function to handle the remaining things to do for
+ * attaching ISA or PCI boards. (Extra parameters could be added
+ * to pass additional information such as IRQ number.)
+ */
+ return skel_common_attach(dev);
+}
+
+/*
+ * _auto_attach is called via comedi_pci_auto_config() (or
+ * comedi_usb_auto_config(), etc.) to handle devices that can be attached
+ * to the Comedi core automatically without the COMEDI_DEVCONFIG ioctl.
+ *
+ * The context parameter is driver dependent.
+ */
+static int skel_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+ const struct skel_board *thisboard = NULL;
+ struct skel_private *devpriv;
+ int ret;
+
+ /* Hack to allow unused code to be optimized out. */
+ if (!IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS))
+ return -EINVAL;
+
+ /*
+ * In this example, the _auto_attach is for a PCI device.
+ *
+ * The 'context' passed to this function is the id->driver_data
+ * associated with the PCI device found in the id_table during
+ * the modprobe. This 'context' is the index of the entry in
+ * skel_boards[i] that contains the boardinfo for the PCI device.
+ */
+ if (context < ARRAY_SIZE(skel_boards))
+ thisboard = &skel_boards[context];
+ if (!thisboard)
+ return -ENODEV;
+
+ /*
+ * Point the struct comedi_device to the matching board info
+ * and set the board name.
+ */
+ dev->board_ptr = thisboard;
+ dev->board_name = thisboard->name;
+
+ /* Allocate the private data */
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ /* Enable the PCI device. */
+ ret = comedi_pci_enable(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * Record the fact that the PCI device is enabled so that it can
+ * be disabled during _detach().
+ *
+ * For this example driver, we assume PCI BAR 0 is the main I/O
+ * region for the board registers and use dev->iobase to hold the
+ * I/O base address and to indicate that the PCI device has been
+ * enabled.
+ *
+ * (For boards with memory-mapped registers, dev->iobase is not
+ * usually needed for register access, so can just be set to 1
+ * to indicate that the PCI device has been enabled.)
+ */
+ dev->iobase = pci_resource_start(pcidev, 0);
+
+ /*
+ * Call a common function to handle the remaining things to do for
+ * attaching ISA or PCI boards. (Extra parameters could be added
+ * to pass additional information such as IRQ number.)
+ */
+ return skel_common_attach(dev);
+}
+
+/*
+ * _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 void skel_detach(struct comedi_device *dev)
+{
+ const struct skel_board *thisboard = comedi_board(dev);
+ struct skel_private *devpriv = dev->private;
+
+ if (!thisboard || !devpriv)
+ return;
+
+/*
+ * Do common stuff such as freeing IRQ, unmapping remapped memory
+ * regions, etc., being careful to check that the stuff is valid given
+ * that _detach() is called even when _attach() or _auto_attach() return
+ * an error.
+ */
+
+ if (IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS) /* &&
+ thisboard->bustype == pci_bustype */) {
+ /*
+ * PCI board
+ *
+ * If PCI device enabled by _auto_attach() (or _attach()),
+ * disable it here.
+ */
+ comedi_pci_disable(dev);
+ } else {
+ /*
+ * ISA board
+ *
+ * Release the first I/O region requested during the
+ * _attach(). This is safe to call even if the request
+ * failed. If any additional I/O regions are requested
+ * they need to be released by the driver.
+ */
+ comedi_legacy_detach(dev);
+ }
+}
+
+/*
+ * 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 skel_driver = {
+ .driver_name = "dummy",
+ .module = THIS_MODULE,
+ .attach = skel_attach,
+ .auto_attach = skel_auto_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),
+};
+
+#ifdef CONFIG_COMEDI_PCI_DRIVERS
+
+static int skel_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ return comedi_pci_auto_config(dev, &skel_driver, id->driver_data);
+}
+
+/*
+ * Please add your PCI vendor ID to comedidev.h, and it will
+ * be forwarded upstream.
+ */
+#define PCI_VENDOR_ID_SKEL 0xdafe
+
+/*
+ * This is used by modprobe to translate PCI IDs to drivers.
+ * Should only be used for PCI and ISA-PnP devices
+ */
+static const struct pci_device_id skel_pci_table[] = {
+ { PCI_VDEVICE(SKEL, 0x0100), BOARD_SKEL100 },
+ { PCI_VDEVICE(SKEL, 0x0200), BOARD_SKEL200 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, skel_pci_table);
+
+static struct pci_driver skel_pci_driver = {
+ .name = "dummy",
+ .id_table = skel_pci_table,
+ .probe = skel_pci_probe,
+ .remove = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(skel_driver, skel_pci_driver);
+#else
+module_comedi_driver(skel_driver);
+#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 00000000000..848c3080158
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ssv_dnp.c
@@ -0,0 +1,187 @@
+/*
+ comedi/drivers/ssv_dnp.c
+ generic comedi driver for SSV Embedded Systems' DIL/Net-PCs
+ Copyright (C) 2001 Robert Schwebel <robert@schwebel.de>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: ssv_dnp
+Description: SSV Embedded Systems DIL/Net-PC
+Author: Robert Schwebel <robert@schwebel.de>
+Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486)
+Status: unknown
+*/
+
+/* include files ----------------------------------------------------------- */
+
+#include <linux/module.h>
+#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 */
+
+static int dnp_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int mask;
+ unsigned int val;
+
+ /*
+ * 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).
+ */
+
+ mask = comedi_dio_update_state(s, data);
+ if (mask) {
+ outb(PADR, CSCIR);
+ outb(s->state & 0xff, CSCDR);
+
+ outb(PBDR, CSCIR);
+ outb((s->state >> 8) & 0xff, CSCDR);
+
+ outb(PCDR, CSCIR);
+ val = inb(CSCDR) & 0x0f;
+ outb(((s->state >> 12) & 0xf0) | val, CSCDR);
+ }
+
+ outb(PADR, CSCIR);
+ val = inb(CSCDR);
+ outb(PBDR, CSCIR);
+ val |= (inb(CSCDR) << 8);
+ outb(PCDR, CSCIR);
+ val |= ((inb(CSCDR) & 0xf0) << 12);
+
+ data[1] = val;
+
+ return insn->n;
+}
+
+static int dnp_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int mask;
+ unsigned int val;
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ if (chan < 8) { /* Port A */
+ mask = 1 << chan;
+ outb(PAMR, CSCIR);
+ } else if (chan < 16) { /* Port B */
+ mask = 1 << (chan - 8);
+ outb(PBMR, CSCIR);
+ } else { /* Port C */
+ /*
+ * 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
+ *
+ * Multiplication by 2 brings bits into correct position
+ * for PCMR!
+ */
+ mask = 1 << ((chan - 16) * 2);
+ outb(PCMR, CSCIR);
+ }
+
+ val = inb(CSCDR);
+ if (data[0] == COMEDI_OUTPUT)
+ val |= mask;
+ else
+ val &= ~mask;
+ outb(val, CSCDR);
+
+ return insn->n;
+
+}
+
+static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret)
+ 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 = 20;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = dnp_dio_insn_bits;
+ s->insn_config = dnp_dio_insn_config;
+
+ /* 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 0;
+}
+
+static void dnp_detach(struct comedi_device *dev)
+{
+ outb(PAMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PBMR, CSCIR);
+ outb(0x00, CSCDR);
+ outb(PCMR, CSCIR);
+ outb((inb(CSCDR) & 0xAA), CSCDR);
+}
+
+static struct comedi_driver dnp_driver = {
+ .driver_name = "dnp-1486",
+ .module = THIS_MODULE,
+ .attach = dnp_attach,
+ .detach = dnp_detach,
+};
+module_comedi_driver(dnp_driver);
+
+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 00000000000..adf7cb7086c
--- /dev/null
+++ b/drivers/staging/comedi/drivers/unioxx5.c
@@ -0,0 +1,508 @@
+/***************************************************************************
+ * *
+ * 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 <ds@schleef.org> *
+ * *
+ * This program is free software; you can redistribute 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. *
+ * *
+ ***************************************************************************/
+/*
+
+Driver: unioxx5
+Description: Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards.
+Author: Kruchinin Daniil (asgard) <asgard@etersoft.ru>
+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 <linux/module.h>
+#include <linux/delay.h>
+#include "../comedidev.h"
+
+#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_define_chan_offset(int chan_num)
+{
+
+ if (chan_num < 0 || chan_num > 23)
+ return -1;
+
+ return (chan_num >> 3) + 1;
+}
+
+#if 0 /* not used? */
+static void __unioxx5_digital_config(struct comedi_subdevice *s, int mode)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int i, mask;
+
+ mask = (mode == ALL_2_OUTPUT) ? 0xFF : 0x00;
+ dev_dbg(csdev, "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
+
+/* 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;
+}
+
+static int __unioxx5_digital_read(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int channel_offset, mask = 1 << (channel & 0x07);
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(csdev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ channel);
+ return 0;
+ }
+
+ *data = inb(usp->usp_iobase + channel_offset);
+ *data &= mask;
+
+ /* correct the read value to 0 or 1 */
+ if (channel_offset > 1)
+ channel -= 2 << channel_offset;
+ *data >>= channel;
+ return 1;
+}
+
+static int __unioxx5_analog_read(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ 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) {
+ dev_err(csdev,
+ "module in position %d with id 0x%02x is for output only",
+ 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)) {
+ dev_err(csdev, "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;
+}
+
+static int __unioxx5_digital_write(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ int channel_offset, val;
+ int mask = 1 << (channel & 0x07);
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(csdev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ 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;
+}
+
+static int __unioxx5_analog_write(struct comedi_subdevice *s,
+ unsigned int *data, int channel, int minor)
+{
+ struct unioxx5_subd_priv *usp = s->private;
+ struct device *csdev = s->device->class_dev;
+ 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)) {
+ dev_err(csdev,
+ "module in position %d with id 0x%0x is for input only!\n",
+ 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_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(subdev, data, channel, dev->minor))
+ return -1;
+ } else {
+ if (!__unioxx5_analog_read(subdev, 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(subdev, data, channel, dev->minor))
+ return -1;
+ } else {
+ if (!__unioxx5_analog_write(subdev, 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) {
+ dev_err(dev->class_dev,
+ "channel configuration accessible only for digital modules\n");
+ return -1;
+ }
+
+ channel_offset = __unioxx5_define_chan_offset(channel);
+ if (channel_offset < 0) {
+ dev_err(dev->class_dev,
+ "undefined channel %d. channel range is 0 .. 23\n",
+ 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:
+ dev_err(dev->class_dev, "unknown flag\n");
+ 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;
+}
+
+/* initializing subdevice with given address */
+static int __unioxx5_subdev_init(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ int iobase)
+{
+ struct unioxx5_subd_priv *usp;
+ int i, to, ndef_flag = 0;
+ int ret;
+
+ usp = comedi_alloc_spriv(s, sizeof(*usp));
+ if (!usp)
+ return -ENOMEM;
+
+ ret = __comedi_request_region(dev, iobase, UNIOXX5_SIZE);
+ if (ret)
+ return ret;
+ usp->usp_iobase = iobase;
+
+ /* 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, iobase + 5);
+ outb('H', iobase + 6); /* requests EEPROM world */
+ while (!(inb(iobase + 0) & TxBE))
+ ; /* waits while writting will be allowed */
+ outb(0, iobase + 6);
+
+ /* waits while reading of two bytes will be allowed */
+ while (!(inb(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(iobase + 6);
+
+ udelay(1);
+ }
+
+ /* initial subdevice for digital or analog i/o */
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = UNIOXX5_NUM_OF_CHANS;
+ s->maxdata = 0xFFF;
+ s->range_table = &range_digital;
+ s->insn_read = unioxx5_subdev_read;
+ s->insn_write = unioxx5_subdev_write;
+ /* for digital modules only!!! */
+ s->insn_config = unioxx5_insn_config;
+
+ return 0;
+}
+
+static int unioxx5_attach(struct comedi_device *dev,
+ struct comedi_devconfig *it)
+{
+ struct comedi_subdevice *s;
+ int iobase, i, n_subd;
+ int id, num, ba;
+ int ret;
+
+ iobase = it->options[0];
+
+ dev->iobase = iobase;
+ iobase += UNIOXX5_SUBDEV_BASE;
+ n_subd = 0;
+
+ /* getting number of subdevices with types 'g01' */
+ for (i = 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) {
+ dev_err(dev->class_dev,
+ "your card must has at least 2 'g01' subdevices\n");
+ return -1;
+ }
+
+ ret = comedi_alloc_subdevices(dev, n_subd);
+ if (ret)
+ return ret;
+
+ /* initializing each of for same subdevices */
+ for (i = 0; i < n_subd; i++, iobase += UNIOXX5_SUBDEV_ODDS) {
+ s = &dev->subdevices[i];
+ ret = __unioxx5_subdev_init(dev, s, iobase);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void unioxx5_detach(struct comedi_device *dev)
+{
+ struct comedi_subdevice *s;
+ struct unioxx5_subd_priv *spriv;
+ int i;
+
+ for (i = 0; i < dev->n_subdevices; i++) {
+ s = &dev->subdevices[i];
+ spriv = s->private;
+ if (spriv && spriv->usp_iobase)
+ release_region(spriv->usp_iobase, UNIOXX5_SIZE);
+ }
+}
+
+static struct comedi_driver unioxx5_driver = {
+ .driver_name = DRIVER_NAME,
+ .module = THIS_MODULE,
+ .attach = unioxx5_attach,
+ .detach = unioxx5_detach,
+};
+module_comedi_driver(unioxx5_driver);
+
+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 00000000000..5f65e4213c6
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbdux.c
@@ -0,0 +1,1822 @@
+/*
+ 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.
+ */
+/*
+Driver: usbdux
+Description: University of Stirling USB DAQ & INCITE Technology Limited
+Devices: [ITL] USB-DUX (usbdux.o)
+Author: Bernd Porr <BerndPorr@f2s.com>
+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, memory 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 support 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+
+#include "../comedidev.h"
+
+#include "comedi_fc.h"
+
+/* constants for firmware upload and download */
+#define USBDUX_FIRMWARE "usbdux_firmware.bin"
+#define USBDUX_FIRMWARE_MAX_LEN 0x2000
+#define USBDUX_FIRMWARE_CMD 0xa0
+#define VENDOR_DIR_IN 0xc0
+#define VENDOR_DIR_OUT 0x40
+#define USBDUX_CPU_CS 0xe600
+
+/* usbdux bulk transfer commands */
+#define USBDUX_CMD_MULT_AI 0
+#define USBDUX_CMD_AO 1
+#define USBDUX_CMD_DIO_CFG 2
+#define USBDUX_CMD_DIO_BITS 3
+#define USBDUX_CMD_SINGLE_AI 4
+#define USBDUX_CMD_TIMER_RD 5
+#define USBDUX_CMD_TIMER_WR 6
+#define USBDUX_CMD_PWM_ON 7
+#define USBDUX_CMD_PWM_OFF 8
+
+#define USBDUX_NUM_AO_CHAN 4
+
+/* timeout for the USB-transfer in ms */
+#define BULK_TIMEOUT 1000
+
+/* 300Hz max frequ under PWM */
+#define MIN_PWM_PERIOD ((long)(1E9/300))
+
+/* Default PWM frequency */
+#define PWM_DEFAULT_PERIOD ((long)(1E9/100))
+
+/* Size of one A/D value */
+#define SIZEADIN ((sizeof(uint16_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
+
+/* size of one value for the D/A converter: channel and value */
+#define SIZEDAOUT ((sizeof(uint8_t)+sizeof(uint16_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
+
+/* number of retries to get the right dux command */
+#define RETRIES 10
+
+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)
+ }
+};
+
+struct usbdux_private {
+ /* actual number of in-buffers */
+ int n_ai_urbs;
+ /* actual number of out-buffers */
+ int n_ao_urbs;
+ /* ISO-transfer handling: buffers */
+ struct urb **ai_urbs;
+ struct urb **ao_urbs;
+ /* pwm-transfer handling */
+ struct urb *pwm_urb;
+ /* PWM period */
+ unsigned int pwm_period;
+ /* PWM internal delay for the GPIF in the FX2 */
+ uint8_t pwm_delay;
+ /* size of the PWM buffer which holds the bit pattern */
+ int pwm_buf_sz;
+ /* input buffer for the ISO-transfer */
+ uint16_t *in_buf;
+ /* input buffer for single insn */
+ uint16_t *insn_buf;
+
+ unsigned int ao_readback[USBDUX_NUM_AO_CHAN];
+
+ unsigned int high_speed:1;
+ unsigned int ai_cmd_running:1;
+ unsigned int ao_cmd_running:1;
+ unsigned int pwm_cmd_running:1;
+
+ /* 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;
+ /* commands */
+ uint8_t *dux_commands;
+ struct semaphore sem;
+};
+
+static void usbdux_unlink_urbs(struct urb **urbs, int num_urbs)
+{
+ int i;
+
+ for (i = 0; i < num_urbs; i++)
+ usb_kill_urb(urbs[i]);
+}
+
+static void usbdux_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ai_urbs)
+ usbdux_unlink_urbs(devpriv->ai_urbs, devpriv->n_ai_urbs);
+
+ devpriv->ai_cmd_running = 0;
+}
+
+static int usbdux_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ /* prevent other CPUs from submitting new commands just now */
+ down(&devpriv->sem);
+ /* unlink only if the urb really has been submitted */
+ usbdux_ai_stop(dev, devpriv->ai_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+/* analogue IN - interrupt service routine */
+static void usbduxsub_ai_isoc_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int i, err;
+
+ /* first we test if something unusual has just happened */
+ switch (urb->status) {
+ case 0:
+ /* copy the result in the transfer buffer */
+ memcpy(devpriv->in_buf, 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(dev->class_dev, "CRC error in ISO IN stream\n");
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ if (devpriv->ai_cmd_running) {
+ s->async->events |= COMEDI_CB_EOA;
+ s->async->events |= COMEDI_CB_ERROR;
+ comedi_event(dev, s);
+ /* stop the transfer w/o unlink */
+ usbdux_ai_stop(dev, 0);
+ }
+ return;
+
+ default:
+ /* a real error on the bus */
+ /* pass error to comedi if we are really running a command */
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_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(dev, s);
+ /* don't do an unlink here */
+ usbdux_ai_stop(dev, 0);
+ }
+ return;
+ }
+
+ /*
+ * at this point we are reasonably sure that nothing dodgy has happened
+ * are we running a command?
+ */
+ if (unlikely(!devpriv->ai_cmd_running)) {
+ /*
+ * not running a command, do not continue execution if no
+ * asynchronous command is running in particular not resubmit
+ */
+ return;
+ }
+
+ urb->dev = comedi_to_usb_dev(dev);
+
+ /* resubmit the urb */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (unlikely(err < 0)) {
+ dev_err(dev->class_dev,
+ "urb resubmit failed in int-context! err=%d\n", err);
+ if (err == -EL2NSYNC)
+ dev_err(dev->class_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(dev, s);
+ /* don't do an unlink here */
+ usbdux_ai_stop(dev, 0);
+ return;
+ }
+
+ devpriv->ai_counter--;
+ if (likely(devpriv->ai_counter > 0))
+ return;
+
+ /* timer zero, transfer measurements to comedi */
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ /* test, if we transmit only a fixed number of samples */
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous, fixed number of samples */
+ devpriv->ai_sample_count--;
+ /* all samples received? */
+ if (devpriv->ai_sample_count < 0) {
+ /* prevent a resubmit next time */
+ usbdux_ai_stop(dev, 0);
+ /* say comedi that the acquistion is over */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return;
+ }
+ }
+ /* get the data from the USB bus and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+ uint16_t val = le16_to_cpu(devpriv->in_buf[i]);
+
+ /* bipolar data is two's-complement */
+ if (comedi_range_is_bipolar(s, range))
+ val ^= ((s->maxdata + 1) >> 1);
+
+ /* transfer data */
+ err = comedi_buf_put(s, val);
+ if (unlikely(err == 0)) {
+ /* buffer overflow */
+ usbdux_ai_stop(dev, 0);
+ return;
+ }
+ }
+ /* tell comedi that data is there */
+ s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+ comedi_event(dev, s);
+}
+
+static void usbdux_ao_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ao_urbs)
+ usbdux_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs);
+
+ devpriv->ao_cmd_running = 0;
+}
+
+static int usbdux_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ /* prevent other CPUs from submitting a command just now */
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbdux_ao_stop(dev, devpriv->ao_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsub_ao_isoc_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ uint8_t *datap;
+ int ret;
+ int i;
+
+ 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 (devpriv->ao_cmd_running) {
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ usbdux_ao_stop(dev, 0);
+ }
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->ao_cmd_running) {
+ dev_err(dev->class_dev,
+ "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(dev, s);
+ /* we do an unlink if we are in the high speed mode */
+ usbdux_ao_stop(dev, 0);
+ }
+ return;
+ }
+
+ /* are we actually running? */
+ if (!devpriv->ao_cmd_running)
+ return;
+
+ /* normal operation: executing a command in this subdevice */
+ devpriv->ao_counter--;
+ if ((int)devpriv->ao_counter <= 0) {
+ /* timer zero */
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ /* handle non continous acquisition */
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* fixed number of samples */
+ devpriv->ao_sample_count--;
+ if (devpriv->ao_sample_count < 0) {
+ /* all samples transmitted */
+ usbdux_ao_stop(dev, 0);
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ /* no resubmit of the urb */
+ return;
+ }
+ }
+
+ /* transmit data to the USB bus */
+ datap = urb->transfer_buffer;
+ *datap++ = cmd->chanlist_len;
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned short val;
+
+ ret = comedi_buf_get(s, &val);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "buffer underflow\n");
+ s->async->events |= (COMEDI_CB_EOA |
+ COMEDI_CB_OVERFLOW);
+ }
+ /* pointer to the DA */
+ *datap++ = val & 0xff;
+ *datap++ = (val >> 8) & 0xff;
+ *datap++ = chan << 6;
+ devpriv->ao_readback[chan] = val;
+
+ s->async->events |= COMEDI_CB_BLOCK;
+ comedi_event(dev, s);
+ }
+ }
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->ao_cmd_running) {
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ 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(dev->class_dev,
+ "ao urb resubm failed in int-cont. ret=%d",
+ ret);
+ if (ret == EL2NSYNC)
+ dev_err(dev->class_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(dev, s);
+ /* don't do an unlink here */
+ usbdux_ao_stop(dev, 0);
+ }
+ }
+}
+
+static int usbdux_submit_urbs(struct comedi_device *dev,
+ struct urb **urbs, int num_urbs,
+ int input_urb)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int ret;
+ int i;
+
+ /* Submit all URBs and start the transfer on the bus */
+ for (i = 0; i < num_urbs; i++) {
+ urb = urbs[i];
+
+ /* in case of a resubmission after an unlink... */
+ if (input_urb)
+ urb->interval = devpriv->ai_interval;
+ urb->context = dev;
+ urb->dev = usb;
+ urb->status = 0;
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int usbdux_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct usbdux_private *this_usbduxsub = dev->private;
+ int err = 0, i;
+ unsigned int tmp_timer;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ 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;
+
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000 / 8 * i);
+ /* now calc the real sampling rate with all the
+ * rounding errors */
+ tmp_timer =
+ ((unsigned int)(cmd->scan_begin_arg / 125000)) *
+ 125000;
+ } else {
+ /* full speed */
+ /* 1kHz scans every USB frame */
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+ /*
+ * calc the real sampling rate with the rounding errors
+ */
+ tmp_timer = ((unsigned int)(cmd->scan_begin_arg /
+ 1000000)) * 1000000;
+ }
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg,
+ tmp_timer);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+/*
+ * creates the ADC command for the MAX1271
+ * range is the range value from comedi
+ */
+static uint8_t create_adc_command(unsigned int chan, unsigned int range)
+{
+ uint8_t p = (range <= 1);
+ uint8_t r = ((range % 2) == 0);
+
+ return (chan << 4) | ((p == 1) << 2) | ((r == 1) << 3);
+}
+
+static int send_dux_commands(struct comedi_device *dev, unsigned int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ int nsent;
+
+ devpriv->dux_commands[0] = cmd_type;
+
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, 1),
+ devpriv->dux_commands, SIZEOFDUXBUFFER,
+ &nsent, BULK_TIMEOUT);
+}
+
+static int receive_dux_commands(struct comedi_device *dev, unsigned int command)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+ int nrec;
+ int i;
+
+ for (i = 0; i < RETRIES; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, 8),
+ devpriv->insn_buf, SIZEINSNBUF,
+ &nrec, BULK_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ if (le16_to_cpu(devpriv->insn_buf[0]) == command)
+ return ret;
+ }
+ /* command not received */
+ return -EFAULT;
+}
+
+static int usbdux_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ goto ai_trig_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ ret = -EBUSY;
+ }
+
+ai_trig_exit:
+ up(&devpriv->sem);
+ return ret;
+}
+
+static int usbdux_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int len = cmd->chanlist_len;
+ int ret = -EBUSY;
+ int i;
+
+ /* block other CPUs from starting an ai_cmd */
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running)
+ goto ai_cmd_exit;
+
+ /* set current channel of the running acquisition to zero */
+ s->async->cur_chan = 0;
+
+ devpriv->dux_commands[1] = len;
+ for (i = 0; i < len; ++i) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned int range = CR_RANGE(cmd->chanlist[i]);
+
+ devpriv->dux_commands[i + 2] = create_adc_command(chan, range);
+ }
+
+ ret = send_dux_commands(dev, USBDUX_CMD_MULT_AI);
+ if (ret < 0)
+ goto ai_cmd_exit;
+
+ if (devpriv->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
+ */
+ devpriv->ai_interval = 1;
+ /* find a power of 2 for the interval */
+ while (devpriv->ai_interval < len)
+ devpriv->ai_interval *= 2;
+
+ devpriv->ai_timer = cmd->scan_begin_arg /
+ (125000 * devpriv->ai_interval);
+ } else {
+ /* interval always 1ms */
+ devpriv->ai_interval = 1;
+ devpriv->ai_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ai_timer < 1) {
+ ret = -EINVAL;
+ goto ai_cmd_exit;
+ }
+
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* data arrives as one packet */
+ devpriv->ai_sample_count = cmd->stop_arg;
+ } else {
+ /* continous acquisition */
+ devpriv->ai_sample_count = 0;
+ }
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ /* fixme: unlink here?? */
+ goto ai_cmd_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ /* TRIG_INT */
+ /* don't enable the acquision operation */
+ /* wait for an internal signal */
+ s->async->inttrig = usbdux_ai_inttrig;
+ }
+
+ai_cmd_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+/* 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)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ unsigned int val;
+ int ret = -EBUSY;
+ int i;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running)
+ goto ai_read_exit;
+
+ /* set command for the first channel */
+ devpriv->dux_commands[1] = create_adc_command(chan, range);
+
+ /* adc commands */
+ ret = send_dux_commands(dev, USBDUX_CMD_SINGLE_AI);
+ if (ret < 0)
+ goto ai_read_exit;
+
+ for (i = 0; i < insn->n; i++) {
+ ret = receive_dux_commands(dev, USBDUX_CMD_SINGLE_AI);
+ if (ret < 0)
+ goto ai_read_exit;
+
+ val = le16_to_cpu(devpriv->insn_buf[1]);
+
+ /* bipolar data is two's-complement */
+ if (comedi_range_is_bipolar(s, range))
+ val ^= ((s->maxdata + 1) >> 1);
+
+ data[i] = val;
+ }
+
+ai_read_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ down(&devpriv->sem);
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbdux_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int val = devpriv->ao_readback[chan];
+ uint16_t *p = (uint16_t *)&devpriv->dux_commands[2];
+ int ret = -EBUSY;
+ int i;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ao_cmd_running)
+ goto ao_write_exit;
+
+ /* number of channels: 1 */
+ devpriv->dux_commands[1] = 1;
+ /* channel number */
+ devpriv->dux_commands[4] = chan << 6;
+
+ for (i = 0; i < insn->n; i++) {
+ val = data[i];
+
+ /* one 16 bit value */
+ *p = cpu_to_le16(val);
+
+ ret = send_dux_commands(dev, USBDUX_CMD_AO);
+ if (ret < 0)
+ goto ao_write_exit;
+ }
+ devpriv->ao_readback[chan] = val;
+
+ao_write_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ao_cmd_running) {
+ devpriv->ao_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ goto ao_trig_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ ret = -EBUSY;
+ }
+
+ao_trig_exit:
+ up(&devpriv->sem);
+ return ret;
+}
+
+static int usbdux_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s, struct comedi_cmd *cmd)
+{
+ struct usbdux_private *this_usbduxsub = dev->private;
+ int err = 0;
+ unsigned int flags;
+
+ if (!this_usbduxsub)
+ return -EFAULT;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ if (0) { /* (this_usbduxsub->high_speed) */
+ /* the sampling rate is set by the coversion rate */
+ flags = TRIG_FOLLOW;
+ } else {
+ /* start a new scan (output at once) with a timer */
+ flags = TRIG_TIMER;
+ }
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ if (0) { /* (this_usbduxsub->high_speed) */
+ /*
+ * in usb-2.0 only one conversion it transmitted
+ * but with 8kHz/n
+ */
+ flags = TRIG_TIMER;
+ } else {
+ /*
+ * all conversion events happen simultaneously with
+ * a rate of 1kHz/n
+ */
+ flags = TRIG_NOW;
+ }
+ err |= cfc_check_trigger_src(&cmd->convert_src, flags);
+
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+
+ /* not used now, is for later use */
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 125000);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ return 0;
+}
+
+static int usbdux_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret = -EBUSY;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ao_cmd_running)
+ goto ao_cmd_exit;
+
+ /* set current channel of the running acquisition to zero */
+ s->async->cur_chan = 0;
+
+ /* we count in steps of 1ms (125us) */
+ /* 125us mode not used yet */
+ if (0) { /* (devpriv->high_speed) */
+ /* 125us */
+ /* timing of the conversion itself: every 125 us */
+ devpriv->ao_timer = cmd->convert_arg / 125000;
+ } else {
+ /* 1ms */
+ /* timing of the scan: we get all channels at once */
+ devpriv->ao_timer = cmd->scan_begin_arg / 1000000;
+ if (devpriv->ao_timer < 1) {
+ ret = -EINVAL;
+ goto ao_cmd_exit;
+ }
+ }
+
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous */
+ /* counter */
+ /* high speed also scans everything at once */
+ if (0) { /* (devpriv->high_speed) */
+ devpriv->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 */
+ devpriv->ao_sample_count = cmd->stop_arg;
+ }
+ } else {
+ /* continous acquisition */
+ devpriv->ao_sample_count = 0;
+ }
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ao_cmd_running = 1;
+ ret = usbdux_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ /* fixme: unlink here?? */
+ goto ao_cmd_exit;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ /* TRIG_INT */
+ /* submit the urbs later */
+ /* wait for an internal signal */
+ s->async->inttrig = usbdux_ao_inttrig;
+ }
+
+ao_cmd_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static int usbdux_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * 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 usbdux_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+
+ comedi_dio_update_state(s, data);
+
+ /* Always update the hardware. See the (*insn_config). */
+ devpriv->dux_commands[1] = s->io_bits;
+ devpriv->dux_commands[2] = s->state;
+
+ /*
+ * This command also tells the firmware to return
+ * the digital input lines.
+ */
+ ret = send_dux_commands(dev, USBDUX_CMD_DIO_BITS);
+ if (ret < 0)
+ goto dio_exit;
+ ret = receive_dux_commands(dev, USBDUX_CMD_DIO_BITS);
+ if (ret < 0)
+ goto dio_exit;
+
+ data[1] = le16_to_cpu(devpriv->insn_buf[1]);
+
+dio_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_counter_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret = 0;
+ int i;
+
+ down(&devpriv->sem);
+
+ for (i = 0; i < insn->n; i++) {
+ ret = send_dux_commands(dev, USBDUX_CMD_TIMER_RD);
+ if (ret < 0)
+ goto counter_read_exit;
+ ret = receive_dux_commands(dev, USBDUX_CMD_TIMER_RD);
+ if (ret < 0)
+ goto counter_read_exit;
+
+ data[i] = le16_to_cpu(devpriv->insn_buf[chan + 1]);
+ }
+
+counter_read_exit:
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int usbdux_counter_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint16_t *p = (uint16_t *)&devpriv->dux_commands[2];
+ int ret = 0;
+ int i;
+
+ down(&devpriv->sem);
+
+ devpriv->dux_commands[1] = chan;
+
+ for (i = 0; i < insn->n; i++) {
+ *p = cpu_to_le16(data[i]);
+
+ ret = send_dux_commands(dev, USBDUX_CMD_TIMER_WR);
+ if (ret < 0)
+ break;
+ }
+
+ up(&devpriv->sem);
+
+ return ret ? ret : insn->n;
+}
+
+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;
+}
+
+static void usbduxsub_unlink_pwm_urbs(struct comedi_device *dev)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ usb_kill_urb(devpriv->pwm_urb);
+}
+
+static void usbdux_pwm_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbdux_private *devpriv = dev->private;
+
+ if (do_unlink)
+ usbduxsub_unlink_pwm_urbs(dev);
+
+ devpriv->pwm_cmd_running = 0;
+}
+
+static int usbdux_pwm_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbdux_pwm_stop(dev, devpriv->pwm_cmd_running);
+ ret = send_dux_commands(dev, USBDUX_CMD_PWM_OFF);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbduxsub_pwm_irq(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbdux_private *devpriv = dev->private;
+ int ret;
+
+ 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 (devpriv->pwm_cmd_running)
+ usbdux_pwm_stop(dev, 0);
+
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->pwm_cmd_running) {
+ dev_err(dev->class_dev,
+ "Non-zero urb status received in pwm intr context: %d\n",
+ urb->status);
+ usbdux_pwm_stop(dev, 0);
+ }
+ return;
+ }
+
+ /* are we actually running? */
+ if (!devpriv->pwm_cmd_running)
+ return;
+
+ urb->transfer_buffer_length = devpriv->pwm_buf_sz;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->pwm_cmd_running) {
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "pwm urb resubm failed in int-cont. ret=%d",
+ ret);
+ if (ret == EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handling!\n");
+
+ /* don't do an unlink here */
+ usbdux_pwm_stop(dev, 0);
+ }
+ }
+}
+
+static int usbduxsub_submit_pwm_urbs(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb = devpriv->pwm_urb;
+
+ /* in case of a resubmission after an unlink... */
+ usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4),
+ urb->transfer_buffer,
+ devpriv->pwm_buf_sz,
+ usbduxsub_pwm_irq,
+ dev);
+
+ return usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int usbdux_pwm_period(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int period)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int fx2delay = 255;
+
+ if (period < MIN_PWM_PERIOD) {
+ return -EAGAIN;
+ } else {
+ fx2delay = (period / (6 * 512 * 1000 / 33)) - 6;
+ if (fx2delay > 255)
+ return -EAGAIN;
+ }
+ devpriv->pwm_delay = fx2delay;
+ devpriv->pwm_period = period;
+
+ return 0;
+}
+
+static int usbdux_pwm_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbdux_private *devpriv = dev->private;
+ int ret = 0;
+
+ down(&devpriv->sem);
+
+ if (devpriv->pwm_cmd_running)
+ goto pwm_start_exit;
+
+ devpriv->dux_commands[1] = devpriv->pwm_delay;
+ ret = send_dux_commands(dev, USBDUX_CMD_PWM_ON);
+ if (ret < 0)
+ goto pwm_start_exit;
+
+ /* initialise the buffer */
+ memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz);
+
+ devpriv->pwm_cmd_running = 1;
+ ret = usbduxsub_submit_pwm_urbs(dev);
+ if (ret < 0)
+ devpriv->pwm_cmd_running = 0;
+
+pwm_start_exit:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbdux_pwm_pattern(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int value,
+ unsigned int sign)
+{
+ struct usbdux_private *devpriv = dev->private;
+ char pwm_mask = (1 << chan); /* DIO bit for the PWM data */
+ char sgn_mask = (16 << chan); /* DIO bit for the sign */
+ char *buf = (char *)(devpriv->pwm_urb->transfer_buffer);
+ int szbuf = devpriv->pwm_buf_sz;
+ int i;
+
+ for (i = 0; i < szbuf; i++) {
+ char c = *buf;
+
+ c &= ~pwm_mask;
+ if (i < value)
+ c |= pwm_mask;
+ if (!sign)
+ c &= ~sgn_mask;
+ else
+ c |= sgn_mask;
+ *buf++ = c;
+ }
+}
+
+static int usbdux_pwm_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * It doesn't make sense to support more than one value here
+ * because it would just overwrite the PWM buffer.
+ */
+ if (insn->n != 1)
+ return -EINVAL;
+
+ /*
+ * The sign is set via a special INSN only, this gives us 8 bits
+ * for normal operation, sign is 0 by default.
+ */
+ usbdux_pwm_pattern(dev, s, chan, data[0], 0);
+
+ return insn->n;
+}
+
+static int usbdux_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbdux_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ /*
+ * 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:
+ return usbdux_pwm_cancel(dev, s);
+ case INSN_CONFIG_GET_PWM_STATUS:
+ data[1] = devpriv->pwm_cmd_running;
+ return 0;
+ case INSN_CONFIG_PWM_SET_PERIOD:
+ return usbdux_pwm_period(dev, s, data[1]);
+ case INSN_CONFIG_PWM_GET_PERIOD:
+ data[1] = devpriv->pwm_period;
+ return 0;
+ case INSN_CONFIG_PWM_SET_H_BRIDGE:
+ /*
+ * data[1] = value
+ * data[2] = sign (for a relay)
+ */
+ usbdux_pwm_pattern(dev, s, chan, data[1], (data[2] != 0));
+ return 0;
+ case INSN_CONFIG_PWM_GET_H_BRIDGE:
+ /* values are not kept in this driver, nothing to return here */
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int usbdux_firmware_upload(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ uint8_t *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > USBDUX_FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev,
+ "usbdux firmware binary it too large for FX2.\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ USBDUX_CPU_CS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUX_FIRMWARE_CMD,
+ VENDOR_DIR_OUT,
+ USBDUX_CPU_CS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbdux_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ devpriv->dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL);
+ devpriv->in_buf = kzalloc(SIZEINBUF, GFP_KERNEL);
+ devpriv->insn_buf = kzalloc(SIZEINSNBUF, GFP_KERNEL);
+ devpriv->ai_urbs = kcalloc(devpriv->n_ai_urbs, sizeof(void *),
+ GFP_KERNEL);
+ devpriv->ao_urbs = kcalloc(devpriv->n_ao_urbs, sizeof(void *),
+ GFP_KERNEL);
+ if (!devpriv->dux_commands || !devpriv->in_buf || !devpriv->insn_buf ||
+ !devpriv->ai_urbs || !devpriv->ao_urbs)
+ return -ENOMEM;
+
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ai_urbs[i] = urb;
+
+ urb->dev = usb;
+ urb->context = dev;
+ urb->pipe = usb_rcvisocpipe(usb, 6);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEINBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+
+ urb->complete = usbduxsub_ai_isoc_irq;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEINBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEINBUF;
+ }
+
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ao_urbs[i] = urb;
+
+ urb->dev = usb;
+ urb->context = dev;
+ urb->pipe = usb_sndisocpipe(usb, 2);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEOUTBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+
+ urb->complete = usbduxsub_ao_isoc_irq;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ }
+
+ /* pwm */
+ if (devpriv->pwm_buf_sz) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->pwm_urb = urb;
+
+ /* max bulk ep size in high speed */
+ urb->transfer_buffer = kzalloc(devpriv->pwm_buf_sz,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void usbdux_free_usb_buffers(struct comedi_device *dev)
+{
+ struct usbdux_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ urb = devpriv->pwm_urb;
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ if (devpriv->ao_urbs) {
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ urb = devpriv->ao_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ao_urbs);
+ }
+ if (devpriv->ai_urbs) {
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ urb = devpriv->ai_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ai_urbs);
+ }
+ kfree(devpriv->insn_buf);
+ kfree(devpriv->in_buf);
+ kfree(devpriv->dux_commands);
+}
+
+static int usbdux_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbdux_private *devpriv;
+ struct comedi_subdevice *s;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->high_speed = (usb->speed == USB_SPEED_HIGH);
+ if (devpriv->high_speed) {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH;
+ devpriv->pwm_buf_sz = 512;
+ } else {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSFULL;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL;
+ }
+
+ ret = usbdux_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ /* setting to alternate setting 3: enabling iso ep and bulk ep. */
+ ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber,
+ 3);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not set alternate setting 3 in high speed\n");
+ return ret;
+ }
+
+ ret = comedi_load_firmware(dev, &usb->dev, USBDUX_FIRMWARE,
+ usbdux_firmware_upload, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 5 : 4);
+ if (ret)
+ return ret;
+
+ /* 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->maxdata = 0x0fff;
+ s->len_chanlist = 8;
+ s->range_table = &range_usbdux_ai_range;
+ s->insn_read = usbdux_ai_insn_read;
+ s->do_cmdtest = usbdux_ai_cmdtest;
+ s->do_cmd = usbdux_ai_cmd;
+ s->cancel = usbdux_ai_cancel;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ dev->write_subdev = s;
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = USBDUX_NUM_AO_CHAN;
+ s->maxdata = 0x0fff;
+ s->len_chanlist = s->n_chan;
+ s->range_table = &range_usbdux_ao_range;
+ 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 subdevice */
+ s = &dev->subdevices[2];
+ 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;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[3];
+ 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 (devpriv->high_speed) {
+ /* PWM subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE;
+ s->n_chan = 8;
+ s->maxdata = devpriv->pwm_buf_sz;
+ s->insn_write = usbdux_pwm_write;
+ s->insn_config = usbdux_pwm_config;
+
+ usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD);
+ }
+
+ return 0;
+}
+
+static void usbdux_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbdux_private *devpriv = dev->private;
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ /* force unlink all urbs */
+ usbdux_pwm_stop(dev, 1);
+ usbdux_ao_stop(dev, 1);
+ usbdux_ai_stop(dev, 1);
+
+ usbdux_free_usb_buffers(dev);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbdux_driver = {
+ .driver_name = "usbdux",
+ .module = THIS_MODULE,
+ .auto_attach = usbdux_auto_attach,
+ .detach = usbdux_detach,
+};
+
+static int usbdux_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbdux_driver, 0);
+}
+
+static const struct usb_device_id usbdux_usb_table[] = {
+ { USB_DEVICE(0x13d8, 0x0001) },
+ { USB_DEVICE(0x13d8, 0x0002) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbdux_usb_table);
+
+static struct usb_driver usbdux_usb_driver = {
+ .name = "usbdux",
+ .probe = usbdux_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbdux_usb_table,
+};
+module_comedi_usb_driver(usbdux_driver, usbdux_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("Stirling/ITL USB-DUX -- Bernd.Porr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(USBDUX_FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/usbduxfast.c b/drivers/staging/comedi/drivers/usbduxfast.c
new file mode 100644
index 00000000000..85f9dcf5940
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbduxfast.c
@@ -0,0 +1,1139 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+#include "comedi_fc.h"
+#include "../comedidev.h"
+
+/*
+ * timeout for the USB-transfer
+ */
+#define EZTIMEOUT 30
+
+/*
+ * constants for "firmware" upload and download
+ */
+#define FIRMWARE "usbduxfast_firmware.bin"
+#define FIRMWARE_MAX_LEN 0x2000
+#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 SIZEOFDUXBUF 256
+
+/*
+ * number of in-URBs which receive the data: min=5
+ */
+#define NUMOFINBUFFERSHIGH 10
+
+/*
+ * 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 usbduxfast_private {
+ struct urb *urb; /* BULK-transfer handling: urb */
+ uint8_t *duxbuf;
+ int8_t *inbuf;
+ short int ai_cmd_running; /* asynchronous command is running */
+ long int ai_sample_count; /* number of samples to acquire */
+ int ignore; /* counter which ignores the first
+ buffers */
+ struct semaphore sem;
+};
+
+/*
+ * bulk transfers to usbduxfast
+ */
+#define SENDADCOMMANDS 0
+#define SENDINITEP6 1
+
+static int usbduxfast_send_cmd(struct comedi_device *dev, int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ int nsent;
+ int ret;
+
+ devpriv->duxbuf[0] = cmd_type;
+
+ ret = usb_bulk_msg(usb, usb_sndbulkpipe(usb, CHANNELLISTEP),
+ devpriv->duxbuf, SIZEOFDUXBUF,
+ &nsent, 10000);
+ if (ret < 0)
+ dev_err(dev->class_dev,
+ "could not transmit command to the usb-device, err=%d\n",
+ ret);
+ return ret;
+}
+
+static void usbduxfast_cmd_data(struct comedi_device *dev, int index,
+ uint8_t len, uint8_t op, uint8_t out,
+ uint8_t log)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+
+ /* Set the GPIF bytes, the first byte is the command byte */
+ devpriv->duxbuf[1 + 0x00 + index] = len;
+ devpriv->duxbuf[1 + 0x08 + index] = op;
+ devpriv->duxbuf[1 + 0x10 + index] = out;
+ devpriv->duxbuf[1 + 0x18 + index] = log;
+}
+
+static int usbduxfast_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+
+ /* stop aquistion */
+ devpriv->ai_cmd_running = 0;
+
+ if (do_unlink && devpriv->urb) {
+ /* kill the running transfer */
+ usb_kill_urb(devpriv->urb);
+ }
+
+ return 0;
+}
+
+static int usbduxfast_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ down(&devpriv->sem);
+ ret = usbduxfast_ai_stop(dev, 1);
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+/*
+ * analogue IN
+ * interrupt service routine
+ */
+static void usbduxfast_ai_interrupt(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_async *async = s->async;
+ struct comedi_cmd *cmd = &async->cmd;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ int n, err;
+
+ /* are we running a command? */
+ if (unlikely(!devpriv->ai_cmd_running)) {
+ /*
+ * not running a command
+ * do not continue execution if no asynchronous command
+ * is running in particular not resubmit
+ */
+ return;
+ }
+
+ /* 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 */
+ async->events |= COMEDI_CB_EOA;
+ async->events |= COMEDI_CB_ERROR;
+ comedi_event(dev, s);
+ /* stop the transfer w/o unlink */
+ usbduxfast_ai_stop(dev, 0);
+ return;
+
+ default:
+ pr_err("non-zero urb status received in ai intr context: %d\n",
+ urb->status);
+ async->events |= COMEDI_CB_EOA;
+ async->events |= COMEDI_CB_ERROR;
+ comedi_event(dev, s);
+ usbduxfast_ai_stop(dev, 0);
+ return;
+ }
+
+ if (!devpriv->ignore) {
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous, fixed number of samples */
+ n = urb->actual_length / sizeof(uint16_t);
+ if (unlikely(devpriv->ai_sample_count < n)) {
+ unsigned int num_bytes;
+
+ /* partial sample received */
+ num_bytes = devpriv->ai_sample_count *
+ sizeof(uint16_t);
+ cfc_write_array_to_buffer(s,
+ urb->transfer_buffer,
+ num_bytes);
+ usbduxfast_ai_stop(dev, 0);
+ /* tell comedi that the acquistion is over */
+ async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return;
+ }
+ devpriv->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(dev, 0);
+ return;
+ }
+
+ /* tell comedi that data is there */
+ comedi_event(dev, s);
+ } else {
+ /* ignore this packet */
+ devpriv->ignore--;
+ }
+
+ /*
+ * command is still running
+ * resubmit urb for BULK transfer
+ */
+ urb->dev = usb;
+ urb->status = 0;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err < 0) {
+ dev_err(dev->class_dev,
+ "urb resubm failed: %d", err);
+ async->events |= COMEDI_CB_EOA;
+ async->events |= COMEDI_CB_ERROR;
+ comedi_event(dev, s);
+ usbduxfast_ai_stop(dev, 0);
+ }
+}
+
+static int usbduxfast_submit_urb(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ usb_fill_bulk_urb(devpriv->urb, usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ usbduxfast_ai_interrupt, dev);
+
+ ret = usb_submit_urb(devpriv->urb, GFP_ATOMIC);
+ if (ret) {
+ dev_err(dev->class_dev, "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;
+ long int steps, tmp;
+ int min_sample_period;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src,
+ TRIG_NOW | TRIG_EXT | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src,
+ TRIG_FOLLOW | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_TIMER | TRIG_EXT);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
+ err |= cfc_check_trigger_is_unique(cmd->convert_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ /* can't have external stop and start triggers at once */
+ if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT)
+ err |= -EINVAL;
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (!cmd->chanlist_len)
+ err |= -EINVAL;
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->chanlist_len == 1)
+ min_sample_period = 1;
+ else
+ min_sample_period = MIN_SAMPLING_PERIOD;
+
+ if (cmd->convert_src == TRIG_TIMER) {
+ steps = cmd->convert_arg * 30;
+ if (steps < (min_sample_period * 1000))
+ steps = min_sample_period * 1000;
+
+ if (steps > (MAX_SAMPLING_PERIOD * 1000))
+ steps = MAX_SAMPLING_PERIOD * 1000;
+
+ /* calc arg again */
+ tmp = steps / 30;
+ err |= cfc_check_trigger_arg_is(&cmd->convert_arg, tmp);
+ }
+
+ /* stop source */
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
+ break;
+ case TRIG_NONE:
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ 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 trig_num)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxfast_submit_urb(dev);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "urbSubmit: err=%d\n", ret);
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else {
+ dev_err(dev->class_dev, "ai is already running\n");
+ }
+ up(&devpriv->sem);
+ return 1;
+}
+
+static int usbduxfast_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int chan, gain, rngmask = 0xff;
+ int i, j, ret;
+ int result;
+ long steps, steps_tmp;
+
+ if (!devpriv)
+ return -EFAULT;
+
+ down(&devpriv->sem);
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev, "ai_cmd not possible\n");
+ up(&devpriv->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
+ */
+ devpriv->ignore = PACKETS_TO_IGNORE;
+
+ gain = CR_RANGE(cmd->chanlist[0]);
+ for (i = 0; i < cmd->chanlist_len; ++i) {
+ chan = CR_CHAN(cmd->chanlist[i]);
+ if (chan != i) {
+ dev_err(dev->class_dev,
+ "channels are not consecutive\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if ((gain != CR_RANGE(cmd->chanlist[i]))
+ && (cmd->chanlist_len > 3)) {
+ dev_err(dev->class_dev,
+ "gain must be the same for all channels\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if (i >= NUMCHANNELS) {
+ dev_err(dev->class_dev, "chanlist too long\n");
+ break;
+ }
+ }
+ steps = 0;
+ if (cmd->convert_src == TRIG_TIMER)
+ steps = (cmd->convert_arg * 30) / 1000;
+
+ if ((steps < MIN_SAMPLING_PERIOD) && (cmd->chanlist_len != 1)) {
+ dev_err(dev->class_dev,
+ "steps=%ld, scan_begin_arg=%d. Not properly tested by cmdtest?\n",
+ steps, cmd->scan_begin_arg);
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if (steps > MAX_SAMPLING_PERIOD) {
+ dev_err(dev->class_dev, "sampling rate too low\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ if ((cmd->start_src == TRIG_EXT) && (cmd->chanlist_len != 1)
+ && (cmd->chanlist_len != 16)) {
+ dev_err(dev->class_dev,
+ "TRIG_EXT only with 1 or 16 channels possible\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+
+ 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 */
+ /* deceision state w/o data */
+ /* RDY0 = 0 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x01, rngmask, 0x00);
+ } else { /* we just proceed to state 1 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x00, rngmask, 0x00);
+ }
+
+ 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 */
+ /* deceision state with data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 1,
+ 0x89, 0x03, rngmask, 0xff);
+ } else {
+ /*
+ * we loop through two states: data and delay
+ * max rate is 15MHz
+ */
+ /* data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 1, steps - 1,
+ 0x02, rngmask, 0x00);
+
+ /* branch back to state 1 */
+ /* deceision state w/o data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 2,
+ 0x09, 0x01, rngmask, 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 */
+ usbduxfast_cmd_data(dev, 1,
+ steps / 2, 0x00, rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 2, steps - steps / 2,
+ 0x00, rngmask, 0x00);
+
+ /* get the data and branch back */
+
+ /* branch back to state 1 */
+ /* deceision state w data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 3,
+ 0x09, 0x03, rngmask, 0xff);
+ }
+ break;
+
+ case 2:
+ /*
+ * two channels
+ * commit data to the FIFO
+ */
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* data */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00);
+
+ /* 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 */
+ /* count */
+ usbduxfast_cmd_data(dev, 1, steps_tmp / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 2, steps_tmp - steps_tmp / 2,
+ 0x00, rngmask, 0x00);
+
+ /* data */
+ usbduxfast_cmd_data(dev, 3, 0x01, 0x02, rngmask, 0x00);
+
+ /*
+ * 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 */
+ /* reset */
+ usbduxfast_cmd_data(dev, 4, steps_tmp / 2,
+ 0x00, (0xff - 0x02) & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2,
+ 0x00, rngmask, 0x00);
+
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+ break;
+
+ case 3:
+ /*
+ * three channels
+ */
+ for (j = 0; j < 1; j++) {
+ int index = j * 2;
+
+ 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
+ */
+ /* data */
+ /* no change */
+ usbduxfast_cmd_data(dev, index, steps / 2,
+ 0x02, rngmask, 0x00);
+
+ if (CR_RANGE(cmd->chanlist[j + 1]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the second part of the delay */
+ /* no data */
+ /* count */
+ usbduxfast_cmd_data(dev, index + 1, steps - steps / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+ }
+
+ /* 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 */
+ /* data */
+ usbduxfast_cmd_data(dev, 4, steps_tmp / 2,
+ 0x02, rngmask, 0x00);
+
+ if (CR_RANGE(cmd->chanlist[0]) > 0)
+ rngmask = 0xff - 0x04;
+ else
+ rngmask = 0xff;
+
+ /* do the second part of the delay */
+ /* no data */
+ /* reset */
+ usbduxfast_cmd_data(dev, 5, steps_tmp - steps_tmp / 2,
+ 0x00, (0xff - 0x02) & rngmask, 0x00);
+
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+
+ 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 */
+ /* deceision state w/o data */
+ /* reset */
+ /* RDY0 = 0 */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x01,
+ (0xff - 0x02) & rngmask, 0x00);
+ } else {
+ /*
+ * we just proceed to state 1
+ */
+
+ /* 30us reset pulse */
+ /* reset */
+ usbduxfast_cmd_data(dev, 0, 0xff, 0x00,
+ (0xff - 0x02) & rngmask, 0x00);
+ }
+
+ /* commit data to the FIFO */
+ /* data */
+ usbduxfast_cmd_data(dev, 1, 0x01, 0x02, rngmask, 0x00);
+
+ /* we have 2 states with duration 1 */
+ steps = steps - 2;
+
+ /* do the first part of the delay */
+ usbduxfast_cmd_data(dev, 2, steps / 2,
+ 0x00, 0xfe & rngmask, 0x00);
+
+ /* and the second part */
+ usbduxfast_cmd_data(dev, 3, steps - steps / 2,
+ 0x00, rngmask, 0x00);
+
+ /* branch back to state 1 */
+ /* deceision state w/o data */
+ /* doesn't matter */
+ usbduxfast_cmd_data(dev, 4, 0x09, 0x01, rngmask, 0xff);
+
+ break;
+
+ default:
+ dev_err(dev->class_dev, "unsupported combination of channels\n");
+ up(&devpriv->sem);
+ return -EFAULT;
+ }
+
+ /* 0 means that the AD commands are sent */
+ result = usbduxfast_send_cmd(dev, SENDADCOMMANDS);
+ if (result < 0) {
+ up(&devpriv->sem);
+ return result;
+ }
+
+ if (cmd->stop_src == TRIG_COUNT)
+ devpriv->ai_sample_count = cmd->stop_arg * cmd->scan_end_arg;
+ else /* TRIG_NONE */
+ devpriv->ai_sample_count = 0;
+
+ if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxfast_submit_urb(dev);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ /* fixme: unlink here?? */
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxfast_ai_inttrig;
+ }
+ up(&devpriv->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)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ unsigned int range = CR_RANGE(insn->chanspec);
+ uint8_t rngmask = range ? (0xff - 0x04) : 0xff;
+ int i, j, n, actual_length;
+ int ret;
+
+ down(&devpriv->sem);
+
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev,
+ "ai_insn_read not possible, async cmd is running\n");
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ /* set command for the first channel */
+
+ /* commit data to the FIFO */
+ /* data */
+ usbduxfast_cmd_data(dev, 0, 0x01, 0x02, rngmask, 0x00);
+
+ /* do the first part of the delay */
+ usbduxfast_cmd_data(dev, 1, 0x0c, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 2, 0x01, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 3, 0x01, 0x00, 0xfe & rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 4, 0x01, 0x00, 0xfe & rngmask, 0x00);
+
+ /* second part */
+ usbduxfast_cmd_data(dev, 5, 0x0c, 0x00, rngmask, 0x00);
+ usbduxfast_cmd_data(dev, 6, 0x01, 0x00, rngmask, 0x00);
+
+ ret = usbduxfast_send_cmd(dev, SENDADCOMMANDS);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ for (i = 0; i < PACKETS_TO_IGNORE; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ &actual_length, 10000);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "insn timeout, no data\n");
+ up(&devpriv->sem);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < insn->n;) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, BULKINEP),
+ devpriv->inbuf, SIZEINBUF,
+ &actual_length, 10000);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "insn data error: %d\n", ret);
+ up(&devpriv->sem);
+ return ret;
+ }
+ n = actual_length / sizeof(uint16_t);
+ if ((n % 16) != 0) {
+ dev_err(dev->class_dev, "insn data packet corrupted\n");
+ up(&devpriv->sem);
+ return -EINVAL;
+ }
+ for (j = chan; (j < n) && (i < insn->n); j = j + 16) {
+ data[i] = ((uint16_t *) (devpriv->inbuf))[j];
+ i++;
+ }
+ }
+
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxfast_attach_common(struct comedi_device *dev)
+{
+ struct usbduxfast_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int ret;
+
+ down(&devpriv->sem);
+
+ ret = comedi_alloc_subdevices(dev, 1);
+ if (ret) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ /* 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 = 16;
+ s->len_chanlist = 16;
+ s->insn_read = usbduxfast_ai_insn_read;
+ s->do_cmdtest = usbduxfast_ai_cmdtest;
+ s->do_cmd = usbduxfast_ai_cmd;
+ s->cancel = usbduxfast_ai_cancel;
+ s->maxdata = 0x1000;
+ s->range_table = &range_usbduxfast_ai_range;
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxfast_upload_firmware(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ unsigned char *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev, "firmware binary too large for FX2\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXFASTSUB_CPUCS, 0x0000,
+ tmp, 1,
+ EZTIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ EZTIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXFASTSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXFASTSUB_CPUCS, 0x0000,
+ tmp, 1,
+ EZTIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbduxfast_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxfast_private *devpriv;
+ int ret;
+
+ if (usb->speed != USB_SPEED_HIGH) {
+ dev_err(dev->class_dev,
+ "This driver needs USB 2.0 to operate. Aborting...\n");
+ return -ENODEV;
+ }
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->duxbuf = kmalloc(SIZEOFDUXBUF, GFP_KERNEL);
+ if (!devpriv->duxbuf)
+ return -ENOMEM;
+
+ ret = usb_set_interface(usb,
+ intf->altsetting->desc.bInterfaceNumber, 1);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not switch to alternate setting 1\n");
+ return -ENODEV;
+ }
+
+ devpriv->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!devpriv->urb) {
+ dev_err(dev->class_dev, "Could not alloc. urb\n");
+ return -ENOMEM;
+ }
+
+ devpriv->inbuf = kmalloc(SIZEINBUF, GFP_KERNEL);
+ if (!devpriv->inbuf)
+ return -ENOMEM;
+
+ ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE,
+ usbduxfast_upload_firmware, 0);
+ if (ret)
+ return ret;
+
+ return usbduxfast_attach_common(dev);
+}
+
+static void usbduxfast_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbduxfast_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (devpriv->urb) {
+ /* waits until a running transfer is over */
+ usb_kill_urb(devpriv->urb);
+
+ kfree(devpriv->inbuf);
+ devpriv->inbuf = NULL;
+
+ usb_free_urb(devpriv->urb);
+ devpriv->urb = NULL;
+ }
+
+ kfree(devpriv->duxbuf);
+ devpriv->duxbuf = NULL;
+
+ devpriv->ai_cmd_running = 0;
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbduxfast_driver = {
+ .driver_name = "usbduxfast",
+ .module = THIS_MODULE,
+ .auto_attach = usbduxfast_auto_attach,
+ .detach = usbduxfast_detach,
+};
+
+static int usbduxfast_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbduxfast_driver, 0);
+}
+
+static const struct usb_device_id usbduxfast_usb_table[] = {
+ /* { USB_DEVICE(0x4b4, 0x8613) }, testing */
+ { USB_DEVICE(0x13d8, 0x0010) }, /* real ID */
+ { USB_DEVICE(0x13d8, 0x0011) }, /* real ID */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbduxfast_usb_table);
+
+static struct usb_driver usbduxfast_usb_driver = {
+ .name = "usbduxfast",
+ .probe = usbduxfast_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbduxfast_usb_table,
+};
+module_comedi_usb_driver(usbduxfast_driver, usbduxfast_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("USB-DUXfast, BerndPorr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/usbduxsigma.c b/drivers/staging/comedi/drivers/usbduxsigma.c
new file mode 100644
index 00000000000..ccc3ef7ba55
--- /dev/null
+++ b/drivers/staging/comedi/drivers/usbduxsigma.c
@@ -0,0 +1,1715 @@
+/*
+ * usbduxsigma.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.
+ */
+
+/*
+ * Driver: usbduxsigma
+ * Description: University of Stirling USB DAQ & INCITE Technology Limited
+ * Devices: (ITL) USB-DUX [usbduxsigma]
+ * Author: Bernd Porr <BerndPorr@f2s.com>
+ * 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
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/fcntl.h>
+#include <linux/compiler.h>
+#include <asm/unaligned.h>
+
+#include "comedi_fc.h"
+#include "../comedidev.h"
+
+/* timeout for the USB-transfer in ms*/
+#define BULK_TIMEOUT 1000
+
+/* constants for "firmware" upload and download */
+#define FIRMWARE "usbduxsigma_firmware.bin"
+#define FIRMWARE_MAX_LEN 0x4000
+#define USBDUXSUB_FIRMWARE 0xa0
+#define VENDOR_DIR_IN 0xc0
+#define VENDOR_DIR_OUT 0x40
+
+/* internal addresses of the 8051 processor */
+#define USBDUXSUB_CPUCS 0xE600
+
+/* 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
+
+#define USBDUXSIGMA_NUM_AO_CHAN 4
+
+/* Size of one A/D value */
+#define SIZEADIN ((sizeof(uint32_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(uint16_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
+
+/* number of retries to get the right dux command */
+#define RETRIES 10
+
+/* bulk transfer commands to usbduxsigma */
+#define USBBUXSIGMA_AD_CMD 0
+#define USBDUXSIGMA_DA_CMD 1
+#define USBDUXSIGMA_DIO_CFG_CMD 2
+#define USBDUXSIGMA_DIO_BITS_CMD 3
+#define USBDUXSIGMA_SINGLE_AD_CMD 4
+#define USBDUXSIGMA_PWM_ON_CMD 7
+#define USBDUXSIGMA_PWM_OFF_CMD 8
+
+static const struct comedi_lrange usbduxsigma_ai_range = {
+ 1, {
+ BIP_RANGE(2.65 / 2.0)
+ }
+};
+
+struct usbduxsigma_private {
+ /* actual number of in-buffers */
+ int n_ai_urbs;
+ /* actual number of out-buffers */
+ int n_ao_urbs;
+ /* ISO-transfer handling: buffers */
+ struct urb **ai_urbs;
+ struct urb **ao_urbs;
+ /* pwm-transfer handling */
+ struct urb *pwm_urb;
+ /* PWM period */
+ unsigned int pwm_period;
+ /* PWM internal delay for the GPIF in the FX2 */
+ uint8_t pwm_delay;
+ /* size of the PWM buffer which holds the bit pattern */
+ int pwm_buf_sz;
+ /* input buffer for the ISO-transfer */
+ uint32_t *in_buf;
+ /* input buffer for single insn */
+ uint8_t *insn_buf;
+
+ unsigned int ao_readback[USBDUXSIGMA_NUM_AO_CHAN];
+
+ unsigned high_speed:1;
+ unsigned ai_cmd_running:1;
+ unsigned ao_cmd_running:1;
+ unsigned pwm_cmd_running:1;
+
+ /* 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;
+ /* commands */
+ uint8_t *dux_commands;
+ struct semaphore sem;
+};
+
+static void usbduxsigma_unlink_urbs(struct urb **urbs, int num_urbs)
+{
+ int i;
+
+ for (i = 0; i < num_urbs; i++)
+ usb_kill_urb(urbs[i]);
+}
+
+static void usbduxsigma_ai_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ai_urbs)
+ usbduxsigma_unlink_urbs(devpriv->ai_urbs, devpriv->n_ai_urbs);
+
+ devpriv->ai_cmd_running = 0;
+}
+
+static int usbduxsigma_ai_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbduxsigma_ai_stop(dev, devpriv->ai_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsigma_ai_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->read_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int dio_state;
+ uint32_t val;
+ int ret;
+ int i;
+
+ /* first we test if something unusual has just happened */
+ switch (urb->status) {
+ case 0:
+ /* copy the result in the transfer buffer */
+ memcpy(devpriv->in_buf, 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(dev->class_dev, "CRC error in ISO IN stream\n");
+
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ if (devpriv->ai_cmd_running) {
+ usbduxsigma_ai_stop(dev, 0); /* w/o unlink */
+ /* we are still running a command, tell comedi */
+ s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
+ comedi_event(dev, s);
+ }
+ return;
+
+ default:
+ /*
+ * a real error on the bus
+ * pass error to comedi if we are really running a command
+ */
+ if (devpriv->ai_cmd_running) {
+ dev_err(dev->class_dev,
+ "%s: non-zero urb status (%d)\n",
+ __func__, urb->status);
+ usbduxsigma_ai_stop(dev, 0); /* w/o unlink */
+ s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
+ comedi_event(dev, s);
+ }
+ return;
+ }
+
+ if (unlikely(!devpriv->ai_cmd_running))
+ return;
+
+ urb->dev = comedi_to_usb_dev(dev);
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (unlikely(ret < 0)) {
+ dev_err(dev->class_dev, "%s: urb resubmit failed (%d)\n",
+ __func__, ret);
+ if (ret == -EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ usbduxsigma_ai_stop(dev, 0); /* w/o unlink */
+ s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
+ comedi_event(dev, s);
+ return;
+ }
+
+ /* get the state of the dio pins to allow external trigger */
+ dio_state = be32_to_cpu(devpriv->in_buf[0]);
+
+ devpriv->ai_counter--;
+ if (likely(devpriv->ai_counter > 0))
+ return;
+
+ /* timer zero, transfer measurements to comedi */
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous, fixed number of samples */
+ devpriv->ai_sample_count--;
+ if (devpriv->ai_sample_count < 0) {
+ usbduxsigma_ai_stop(dev, 0); /* w/o unlink */
+ /* acquistion is over, tell comedi */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return;
+ }
+ }
+
+ /* get the data from the USB bus and hand it over to comedi */
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ /* transfer data, note first byte is the DIO state */
+ val = be32_to_cpu(devpriv->in_buf[i+1]);
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ ret = cfc_write_array_to_buffer(s, &val, sizeof(uint32_t));
+ if (unlikely(ret == 0)) {
+ /* buffer overflow */
+ usbduxsigma_ai_stop(dev, 0); /* w/o unlink */
+ return;
+ }
+ }
+ /* tell comedi that data is there */
+ s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS);
+ comedi_event(dev, s);
+}
+
+static void usbduxsigma_ao_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink && devpriv->ao_urbs)
+ usbduxsigma_unlink_urbs(devpriv->ao_urbs, devpriv->n_ao_urbs);
+
+ devpriv->ao_cmd_running = 0;
+}
+
+static int usbduxsigma_ao_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ down(&devpriv->sem);
+ /* unlink only if it is really running */
+ usbduxsigma_ao_stop(dev, devpriv->ao_cmd_running);
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static void usbduxsigma_ao_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_subdevice *s = dev->write_subdev;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ uint8_t *datap;
+ int ret;
+ int i;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ if (devpriv->ao_cmd_running) {
+ usbduxsigma_ao_stop(dev, 0); /* w/o unlink */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ }
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->ao_cmd_running) {
+ dev_err(dev->class_dev,
+ "%s: non-zero urb status (%d)\n",
+ __func__, urb->status);
+ usbduxsigma_ao_stop(dev, 0); /* w/o unlink */
+ s->async->events |= (COMEDI_CB_ERROR | COMEDI_CB_EOA);
+ comedi_event(dev, s);
+ }
+ return;
+ }
+
+ if (!devpriv->ao_cmd_running)
+ return;
+
+ devpriv->ao_counter--;
+ if ((int)devpriv->ao_counter <= 0) {
+ /* timer zero, transfer from comedi */
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous, fixed number of samples */
+ devpriv->ao_sample_count--;
+ if (devpriv->ao_sample_count < 0) {
+ usbduxsigma_ao_stop(dev, 0); /* w/o unlink */
+ /* acquistion is over, tell comedi */
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return;
+ }
+ }
+
+ /* transmit data to the USB bus */
+ datap = urb->transfer_buffer;
+ *datap++ = cmd->chanlist_len;
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+ unsigned short val;
+
+ ret = comedi_buf_get(s, &val);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "buffer underflow\n");
+ s->async->events |= (COMEDI_CB_EOA |
+ COMEDI_CB_OVERFLOW);
+ }
+ *datap++ = val;
+ *datap++ = chan;
+ devpriv->ao_readback[chan] = val;
+
+ s->async->events |= COMEDI_CB_BLOCK;
+ comedi_event(dev, s);
+ }
+ }
+
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ 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(dev->class_dev,
+ "%s: urb resubmit failed (%d)\n",
+ __func__, ret);
+ if (ret == EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ usbduxsigma_ao_stop(dev, 0); /* w/o unlink */
+ s->async->events |= (COMEDI_CB_EOA | COMEDI_CB_ERROR);
+ comedi_event(dev, s);
+ }
+}
+
+static int usbduxsigma_submit_urbs(struct comedi_device *dev,
+ struct urb **urbs, int num_urbs,
+ int input_urb)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int ret;
+ int i;
+
+ /* Submit all URBs and start the transfer on the bus */
+ for (i = 0; i < num_urbs; i++) {
+ urb = urbs[i];
+
+ /* in case of a resubmission after an unlink... */
+ if (input_urb)
+ urb->interval = devpriv->ai_interval;
+ urb->context = dev;
+ urb->dev = usb;
+ urb->status = 0;
+ urb->transfer_flags = URB_ISO_ASAP;
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int usbduxsigma_chans_to_interval(int num_chan)
+{
+ if (num_chan <= 2)
+ return 2; /* 4kHz */
+ if (num_chan <= 8)
+ return 4; /* 2kHz */
+ return 8; /* 1kHz */
+}
+
+static int usbduxsigma_ai_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int high_speed = devpriv->high_speed;
+ int interval = usbduxsigma_chans_to_interval(cmd->chanlist_len);
+ int err = 0;
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err)
+ return 1;
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int tmp;
+
+ if (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.
+ */
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ (1000000 / 8 * interval));
+
+ tmp = (cmd->scan_begin_arg / 125000) * 125000;
+ } else {
+ /* full speed */
+ /* 1kHz scans every USB frame */
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+
+ tmp = (cmd->scan_begin_arg / 1000000) * 1000000;
+ }
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, tmp);
+ }
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ if (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
+ */
+ devpriv->ai_interval = interval;
+ devpriv->ai_timer = cmd->scan_begin_arg / (125000 * interval);
+ } else {
+ /* interval always 1ms */
+ devpriv->ai_interval = 1;
+ devpriv->ai_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ai_timer < 1)
+ err |= -EINVAL;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* data arrives as one packet */
+ devpriv->ai_sample_count = cmd->stop_arg;
+ } else {
+ /* continuous acquisition */
+ devpriv->ai_sample_count = 0;
+ }
+
+ if (err)
+ return 4;
+
+ 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));
+}
+
+static int usbbuxsigma_send_cmd(struct comedi_device *dev, int cmd_type)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ int nsent;
+
+ devpriv->dux_commands[0] = cmd_type;
+
+ return usb_bulk_msg(usb, usb_sndbulkpipe(usb, 1),
+ devpriv->dux_commands, SIZEOFDUXBUFFER,
+ &nsent, BULK_TIMEOUT);
+}
+
+static int usbduxsigma_receive_cmd(struct comedi_device *dev, int command)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ int nrec;
+ int ret;
+ int i;
+
+ for (i = 0; i < RETRIES; i++) {
+ ret = usb_bulk_msg(usb, usb_rcvbulkpipe(usb, 8),
+ devpriv->insn_buf, SIZEINSNBUF,
+ &nrec, BULK_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ if (devpriv->insn_buf[0] == command)
+ return 0;
+ }
+ /*
+ * This is only reached if the data has been requested a
+ * couple of times and the command was not received.
+ */
+ return -EFAULT;
+}
+
+static int usbduxsigma_ai_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+ if (!devpriv->ai_cmd_running) {
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ }
+ up(&devpriv->sem);
+
+ return 1;
+}
+
+static int usbduxsigma_ai_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int len = cmd->chanlist_len;
+ uint8_t muxsg0 = 0;
+ uint8_t muxsg1 = 0;
+ uint8_t sysred = 0;
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+
+ /* set current channel of the running acquisition to zero */
+ s->async->cur_chan = 0;
+ for (i = 0; i < len; i++) {
+ unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+
+ create_adc_command(chan, &muxsg0, &muxsg1);
+ }
+
+ devpriv->dux_commands[1] = len; /* num channels per time step */
+ devpriv->dux_commands[2] = 0x12; /* CONFIG0 */
+ devpriv->dux_commands[3] = 0x03; /* CONFIG1: 23kHz sample, delay 0us */
+ devpriv->dux_commands[4] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[5] = muxsg0;
+ devpriv->dux_commands[6] = muxsg1;
+ devpriv->dux_commands[7] = sysred;
+
+ ret = usbbuxsigma_send_cmd(dev, USBBUXSIGMA_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ devpriv->ai_counter = devpriv->ai_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ai_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ai_urbs,
+ devpriv->n_ai_urbs, 1);
+ if (ret < 0) {
+ devpriv->ai_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxsigma_ai_inttrig;
+ }
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxsigma_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ uint8_t muxsg0 = 0;
+ uint8_t muxsg1 = 0;
+ uint8_t sysred = 0;
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+ if (devpriv->ai_cmd_running) {
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ create_adc_command(chan, &muxsg0, &muxsg1);
+
+ /* Mode 0 is used to get a single conversion on demand */
+ devpriv->dux_commands[1] = 0x16; /* CONFIG0: chopper on */
+ devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
+ devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[4] = muxsg0;
+ devpriv->dux_commands[5] = muxsg1;
+ devpriv->dux_commands[6] = sysred;
+
+ /* adc commands */
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ uint32_t val;
+
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+
+ /* 32 bits big endian from the A/D converter */
+ val = be32_to_cpu(get_unaligned((uint32_t
+ *)(devpriv->insn_buf + 1)));
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ data[i] = val;
+ }
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxsigma_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int i;
+
+ down(&devpriv->sem);
+ for (i = 0; i < insn->n; i++)
+ data[i] = devpriv->ao_readback[chan];
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxsigma_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+ int ret;
+ int i;
+
+ down(&devpriv->sem);
+ if (devpriv->ao_cmd_running) {
+ up(&devpriv->sem);
+ return -EBUSY;
+ }
+
+ for (i = 0; i < insn->n; i++) {
+ devpriv->dux_commands[1] = 1; /* num channels */
+ devpriv->dux_commands[2] = data[i]; /* value */
+ devpriv->dux_commands[3] = chan; /* channel number */
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DA_CMD);
+ if (ret < 0) {
+ up(&devpriv->sem);
+ return ret;
+ }
+ devpriv->ao_readback[chan] = data[i];
+ }
+ up(&devpriv->sem);
+
+ return insn->n;
+}
+
+static int usbduxsigma_ao_inttrig(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int trig_num)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ if (trig_num != cmd->start_arg)
+ return -EINVAL;
+
+ down(&devpriv->sem);
+ if (!devpriv->ao_cmd_running) {
+ devpriv->ao_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ }
+ up(&devpriv->sem);
+
+ return 1;
+}
+
+static int usbduxsigma_ao_cmdtest(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_cmd *cmd)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int err = 0;
+ int high_speed;
+ unsigned int flags;
+
+ /* high speed conversions are not used yet */
+ high_speed = 0; /* (devpriv->high_speed) */
+
+ /* Step 1 : check if triggers are trivially valid */
+
+ err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
+
+ if (high_speed) {
+ /*
+ * start immediately a new scan
+ * the sampling rate is set by the coversion rate
+ */
+ flags = TRIG_FOLLOW;
+ } else {
+ /* start a new scan (output at once) with a timer */
+ flags = TRIG_TIMER;
+ }
+ err |= cfc_check_trigger_src(&cmd->scan_begin_src, flags);
+
+ err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
+ err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+ err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+ if (err) {
+ up(&devpriv->sem);
+ return 1;
+ }
+
+ /* Step 2a : make sure trigger sources are unique */
+
+ err |= cfc_check_trigger_is_unique(cmd->start_src);
+ err |= cfc_check_trigger_is_unique(cmd->stop_src);
+
+ /* Step 2b : and mutually compatible */
+
+ if (err)
+ return 2;
+
+ /* Step 3: check if arguments are trivially valid */
+
+ err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
+ err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+ if (cmd->scan_begin_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
+ 1000000);
+
+ /* not used now, is for later use */
+ if (cmd->convert_src == TRIG_TIMER)
+ err |= cfc_check_trigger_arg_min(&cmd->convert_arg, 125000);
+
+ err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* any count is allowed */
+ } else {
+ /* TRIG_NONE */
+ err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments */
+
+ /* we count in timer steps */
+ if (high_speed) {
+ /* timing of the conversion itself: every 125 us */
+ devpriv->ao_timer = cmd->convert_arg / 125000;
+ } else {
+ /*
+ * timing of the scan: every 1ms
+ * we get all channels at once
+ */
+ devpriv->ao_timer = cmd->scan_begin_arg / 1000000;
+ }
+ if (devpriv->ao_timer < 1)
+ err |= -EINVAL;
+
+ if (cmd->stop_src == TRIG_COUNT) {
+ /* not continuous, use counter */
+ if (high_speed) {
+ /* high speed also scans everything at once */
+ devpriv->ao_sample_count = cmd->stop_arg *
+ cmd->scan_end_arg;
+ } else {
+ /*
+ * There's no scan as the scan has been
+ * handled inside the FX2. Data arrives as
+ * one packet.
+ */
+ devpriv->ao_sample_count = cmd->stop_arg;
+ }
+ } else {
+ /* continuous acquisition */
+ devpriv->ao_sample_count = 0;
+ }
+
+ if (err)
+ return 4;
+
+ return 0;
+}
+
+static int usbduxsigma_ao_cmd(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int ret;
+
+ down(&devpriv->sem);
+
+ /* set current channel of the running acquisition to zero */
+ s->async->cur_chan = 0;
+
+ devpriv->ao_counter = devpriv->ao_timer;
+
+ if (cmd->start_src == TRIG_NOW) {
+ /* enable this acquisition operation */
+ devpriv->ao_cmd_running = 1;
+ ret = usbduxsigma_submit_urbs(dev, devpriv->ao_urbs,
+ devpriv->n_ao_urbs, 0);
+ if (ret < 0) {
+ devpriv->ao_cmd_running = 0;
+ up(&devpriv->sem);
+ return ret;
+ }
+ s->async->inttrig = NULL;
+ } else { /* TRIG_INT */
+ s->async->inttrig = usbduxsigma_ao_inttrig;
+ }
+
+ up(&devpriv->sem);
+
+ return 0;
+}
+
+static int usbduxsigma_dio_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ int ret;
+
+ ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * 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 usbduxsigma_dio_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ down(&devpriv->sem);
+
+ comedi_dio_update_state(s, data);
+
+ /* Always update the hardware. See the (*insn_config). */
+ devpriv->dux_commands[1] = s->io_bits & 0xff;
+ devpriv->dux_commands[4] = s->state & 0xff;
+ devpriv->dux_commands[2] = (s->io_bits >> 8) & 0xff;
+ devpriv->dux_commands[5] = (s->state >> 8) & 0xff;
+ devpriv->dux_commands[3] = (s->io_bits >> 16) & 0xff;
+ devpriv->dux_commands[6] = (s->state >> 16) & 0xff;
+
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
+ if (ret < 0)
+ goto done;
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_DIO_BITS_CMD);
+ if (ret < 0)
+ goto done;
+
+ s->state = devpriv->insn_buf[1] |
+ (devpriv->insn_buf[2] << 8) |
+ (devpriv->insn_buf[3] << 16);
+
+ data[1] = s->state;
+ ret = insn->n;
+
+done:
+ up(&devpriv->sem);
+
+ return ret;
+}
+
+static void usbduxsigma_pwm_stop(struct comedi_device *dev, int do_unlink)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ if (do_unlink) {
+ if (devpriv->pwm_urb)
+ usb_kill_urb(devpriv->pwm_urb);
+ }
+
+ devpriv->pwm_cmd_running = 0;
+}
+
+static int usbduxsigma_pwm_cancel(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ /* unlink only if it is really running */
+ usbduxsigma_pwm_stop(dev, devpriv->pwm_cmd_running);
+
+ return usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_OFF_CMD);
+}
+
+static void usbduxsigma_pwm_urb_complete(struct urb *urb)
+{
+ struct comedi_device *dev = urb->context;
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -ECONNABORTED:
+ /* happens after an unlink command */
+ if (devpriv->pwm_cmd_running)
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ return;
+
+ default:
+ /* a real error */
+ if (devpriv->pwm_cmd_running) {
+ dev_err(dev->class_dev,
+ "%s: non-zero urb status (%d)\n",
+ __func__, urb->status);
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ }
+ return;
+ }
+
+ if (!devpriv->pwm_cmd_running)
+ return;
+
+ urb->transfer_buffer_length = devpriv->pwm_buf_sz;
+ urb->dev = comedi_to_usb_dev(dev);
+ urb->status = 0;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "%s: urb resubmit failed (%d)\n",
+ __func__, ret);
+ if (ret == EL2NSYNC)
+ dev_err(dev->class_dev,
+ "buggy USB host controller or bug in IRQ handler\n");
+ usbduxsigma_pwm_stop(dev, 0); /* w/o unlink */
+ }
+}
+
+static int usbduxsigma_submit_pwm_urb(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb = devpriv->pwm_urb;
+
+ /* in case of a resubmission after an unlink... */
+ usb_fill_bulk_urb(urb, usb, usb_sndbulkpipe(usb, 4),
+ urb->transfer_buffer, devpriv->pwm_buf_sz,
+ usbduxsigma_pwm_urb_complete, dev);
+
+ return usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int usbduxsigma_pwm_period(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int period)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int fx2delay = 255;
+
+ if (period < MIN_PWM_PERIOD) {
+ return -EAGAIN;
+ } else {
+ fx2delay = (period / (6 * 512 * 1000 / 33)) - 6;
+ if (fx2delay > 255)
+ return -EAGAIN;
+ }
+ devpriv->pwm_delay = fx2delay;
+ devpriv->pwm_period = period;
+ return 0;
+}
+
+static int usbduxsigma_pwm_start(struct comedi_device *dev,
+ struct comedi_subdevice *s)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ int ret;
+
+ if (devpriv->pwm_cmd_running)
+ return 0;
+
+ devpriv->dux_commands[1] = devpriv->pwm_delay;
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_PWM_ON_CMD);
+ if (ret < 0)
+ return ret;
+
+ memset(devpriv->pwm_urb->transfer_buffer, 0, devpriv->pwm_buf_sz);
+
+ devpriv->pwm_cmd_running = 1;
+ ret = usbduxsigma_submit_pwm_urb(dev);
+ if (ret < 0) {
+ devpriv->pwm_cmd_running = 0;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usbduxsigma_pwm_pattern(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ unsigned int chan,
+ unsigned int value,
+ unsigned int sign)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ char pwm_mask = (1 << chan); /* DIO bit for the PWM data */
+ char sgn_mask = (16 << chan); /* DIO bit for the sign */
+ char *buf = (char *)(devpriv->pwm_urb->transfer_buffer);
+ int szbuf = devpriv->pwm_buf_sz;
+ int i;
+
+ for (i = 0; i < szbuf; i++) {
+ char c = *buf;
+
+ c &= ~pwm_mask;
+ if (i < value)
+ c |= pwm_mask;
+ if (!sign)
+ c &= ~sgn_mask;
+ else
+ c |= sgn_mask;
+ *buf++ = c;
+ }
+}
+
+static int usbduxsigma_pwm_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ /*
+ * It doesn't make sense to support more than one value here
+ * because it would just overwrite the PWM buffer.
+ */
+ if (insn->n != 1)
+ return -EINVAL;
+
+ /*
+ * The sign is set via a special INSN only, this gives us 8 bits
+ * for normal operation, sign is 0 by default.
+ */
+ usbduxsigma_pwm_pattern(dev, s, chan, data[0], 0);
+
+ return insn->n;
+}
+
+static int usbduxsigma_pwm_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ unsigned int chan = CR_CHAN(insn->chanspec);
+
+ switch (data[0]) {
+ case INSN_CONFIG_ARM:
+ /*
+ * if not zero the PWM is limited to a certain time which is
+ * not supported here
+ */
+ if (data[1] != 0)
+ return -EINVAL;
+ return usbduxsigma_pwm_start(dev, s);
+ case INSN_CONFIG_DISARM:
+ return usbduxsigma_pwm_cancel(dev, s);
+ case INSN_CONFIG_GET_PWM_STATUS:
+ data[1] = devpriv->pwm_cmd_running;
+ return 0;
+ case INSN_CONFIG_PWM_SET_PERIOD:
+ return usbduxsigma_pwm_period(dev, s, data[1]);
+ case INSN_CONFIG_PWM_GET_PERIOD:
+ data[1] = devpriv->pwm_period;
+ return 0;
+ case INSN_CONFIG_PWM_SET_H_BRIDGE:
+ /*
+ * data[1] = value
+ * data[2] = sign (for a relay)
+ */
+ usbduxsigma_pwm_pattern(dev, s, chan, data[1], (data[2] != 0));
+ return 0;
+ case INSN_CONFIG_PWM_GET_H_BRIDGE:
+ /* values are not kept in this driver, nothing to return */
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int usbduxsigma_getstatusinfo(struct comedi_device *dev, int chan)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ uint8_t sysred;
+ uint32_t val;
+ int ret;
+
+ switch (chan) {
+ default:
+ case 0:
+ sysred = 0; /* ADC zero */
+ break;
+ case 1:
+ sysred = 1; /* ADC offset */
+ break;
+ case 2:
+ sysred = 4; /* VCC */
+ break;
+ case 3:
+ sysred = 8; /* temperature */
+ break;
+ case 4:
+ sysred = 16; /* gain */
+ break;
+ case 5:
+ sysred = 32; /* ref */
+ break;
+ }
+
+ devpriv->dux_commands[1] = 0x12; /* CONFIG0 */
+ devpriv->dux_commands[2] = 0x80; /* CONFIG1: 2kHz sampling rate */
+ devpriv->dux_commands[3] = 0x00; /* CONFIG3: diff. channels off */
+ devpriv->dux_commands[4] = 0;
+ devpriv->dux_commands[5] = 0;
+ devpriv->dux_commands[6] = sysred;
+ ret = usbbuxsigma_send_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0)
+ return ret;
+
+ ret = usbduxsigma_receive_cmd(dev, USBDUXSIGMA_SINGLE_AD_CMD);
+ if (ret < 0)
+ return ret;
+
+ /* 32 bits big endian from the A/D converter */
+ val = be32_to_cpu(get_unaligned((uint32_t *)(devpriv->insn_buf + 1)));
+ val &= 0x00ffffff; /* strip status byte */
+ val ^= 0x00800000; /* convert to unsigned */
+
+ return (int)val;
+}
+
+static int usbduxsigma_firmware_upload(struct comedi_device *dev,
+ const u8 *data, size_t size,
+ unsigned long context)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ uint8_t *buf;
+ uint8_t *tmp;
+ int ret;
+
+ if (!data)
+ return 0;
+
+ if (size > FIRMWARE_MAX_LEN) {
+ dev_err(dev->class_dev, "firmware binary too large for FX2\n");
+ return -ENOMEM;
+ }
+
+ /* we generate a local buffer for the firmware */
+ buf = kmemdup(data, size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* we need a malloc'ed buffer for usb_control_msg() */
+ tmp = kmalloc(1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ /* stop the current firmware on the device */
+ *tmp = 1; /* 7f92 to one */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXSUB_CPUCS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "can not stop firmware\n");
+ goto done;
+ }
+
+ /* upload the new firmware to the device */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ 0, 0x0000,
+ buf, size,
+ BULK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev->class_dev, "firmware upload failed\n");
+ goto done;
+ }
+
+ /* start the new firmware on the device */
+ *tmp = 0; /* 7f92 to zero */
+ ret = usb_control_msg(usb, usb_sndctrlpipe(usb, 0),
+ USBDUXSUB_FIRMWARE,
+ VENDOR_DIR_OUT,
+ USBDUXSUB_CPUCS, 0x0000,
+ tmp, 1,
+ BULK_TIMEOUT);
+ if (ret < 0)
+ dev_err(dev->class_dev, "can not start firmware\n");
+
+done:
+ kfree(tmp);
+ kfree(buf);
+ return ret;
+}
+
+static int usbduxsigma_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ devpriv->dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL);
+ devpriv->in_buf = kzalloc(SIZEINBUF, GFP_KERNEL);
+ devpriv->insn_buf = kzalloc(SIZEINSNBUF, GFP_KERNEL);
+ devpriv->ai_urbs = kcalloc(devpriv->n_ai_urbs, sizeof(*urb),
+ GFP_KERNEL);
+ devpriv->ao_urbs = kcalloc(devpriv->n_ao_urbs, sizeof(*urb),
+ GFP_KERNEL);
+ if (!devpriv->dux_commands || !devpriv->in_buf || !devpriv->insn_buf ||
+ !devpriv->ai_urbs || !devpriv->ao_urbs)
+ return -ENOMEM;
+
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ai_urbs[i] = urb;
+ urb->dev = usb;
+ /* will be filled later with a pointer to the comedi-device */
+ /* and ONLY then the urb should be submitted */
+ urb->context = NULL;
+ urb->pipe = usb_rcvisocpipe(usb, 6);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEINBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ urb->complete = usbduxsigma_ai_urb_complete;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEINBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEINBUF;
+ }
+
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ /* one frame: 1ms */
+ urb = usb_alloc_urb(1, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->ao_urbs[i] = urb;
+ urb->dev = usb;
+ /* will be filled later with a pointer to the comedi-device */
+ /* and ONLY then the urb should be submitted */
+ urb->context = NULL;
+ urb->pipe = usb_sndisocpipe(usb, 2);
+ urb->transfer_flags = URB_ISO_ASAP;
+ urb->transfer_buffer = kzalloc(SIZEOUTBUF, GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ urb->complete = usbduxsigma_ao_urb_complete;
+ urb->number_of_packets = 1;
+ urb->transfer_buffer_length = SIZEOUTBUF;
+ urb->iso_frame_desc[0].offset = 0;
+ urb->iso_frame_desc[0].length = SIZEOUTBUF;
+ if (devpriv->high_speed)
+ urb->interval = 8; /* uframes */
+ else
+ urb->interval = 1; /* frames */
+ }
+
+ if (devpriv->pwm_buf_sz) {
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ devpriv->pwm_urb = urb;
+
+ urb->transfer_buffer = kzalloc(devpriv->pwm_buf_sz,
+ GFP_KERNEL);
+ if (!urb->transfer_buffer)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void usbduxsigma_free_usb_buffers(struct comedi_device *dev)
+{
+ struct usbduxsigma_private *devpriv = dev->private;
+ struct urb *urb;
+ int i;
+
+ urb = devpriv->pwm_urb;
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ if (devpriv->ao_urbs) {
+ for (i = 0; i < devpriv->n_ao_urbs; i++) {
+ urb = devpriv->ao_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ao_urbs);
+ }
+ if (devpriv->ai_urbs) {
+ for (i = 0; i < devpriv->n_ai_urbs; i++) {
+ urb = devpriv->ai_urbs[i];
+ if (urb) {
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ }
+ }
+ kfree(devpriv->ai_urbs);
+ }
+ kfree(devpriv->insn_buf);
+ kfree(devpriv->in_buf);
+ kfree(devpriv->dux_commands);
+}
+
+static int usbduxsigma_auto_attach(struct comedi_device *dev,
+ unsigned long context_unused)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usbduxsigma_private *devpriv;
+ struct comedi_subdevice *s;
+ int offset;
+ int ret;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ sema_init(&devpriv->sem, 1);
+
+ usb_set_intfdata(intf, devpriv);
+
+ devpriv->high_speed = (usb->speed == USB_SPEED_HIGH);
+ if (devpriv->high_speed) {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSHIGH;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSHIGH;
+ devpriv->pwm_buf_sz = 512;
+ } else {
+ devpriv->n_ai_urbs = NUMOFINBUFFERSFULL;
+ devpriv->n_ao_urbs = NUMOFOUTBUFFERSFULL;
+ }
+
+ ret = usbduxsigma_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ /* setting to alternate setting 3: enabling iso ep and bulk ep. */
+ ret = usb_set_interface(usb, intf->altsetting->desc.bInterfaceNumber,
+ 3);
+ if (ret < 0) {
+ dev_err(dev->class_dev,
+ "could not set alternate setting 3 in high speed\n");
+ return ret;
+ }
+
+ ret = comedi_load_firmware(dev, &usb->dev, FIRMWARE,
+ usbduxsigma_firmware_upload, 0);
+ if (ret)
+ return ret;
+
+ ret = comedi_alloc_subdevices(dev, (devpriv->high_speed) ? 4 : 3);
+ if (ret)
+ return ret;
+
+ /* 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 | SDF_LSAMPL;
+ s->n_chan = NUMCHANNELS;
+ s->len_chanlist = NUMCHANNELS;
+ s->maxdata = 0x00ffffff;
+ s->range_table = &usbduxsigma_ai_range;
+ s->insn_read = usbduxsigma_ai_insn_read;
+ s->do_cmdtest = usbduxsigma_ai_cmdtest;
+ s->do_cmd = usbduxsigma_ai_cmd;
+ s->cancel = usbduxsigma_ai_cancel;
+
+ /* Analog Output subdevice */
+ s = &dev->subdevices[1];
+ dev->write_subdev = s;
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = USBDUXSIGMA_NUM_AO_CHAN;
+ s->len_chanlist = s->n_chan;
+ s->maxdata = 0x00ff;
+ s->range_table = &range_unipolar2_5;
+ s->insn_write = usbduxsigma_ao_insn_write;
+ s->insn_read = usbduxsigma_ao_insn_read;
+ s->do_cmdtest = usbduxsigma_ao_cmdtest;
+ s->do_cmd = usbduxsigma_ao_cmd;
+ s->cancel = usbduxsigma_ao_cancel;
+
+ /* Digital I/O subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DIO;
+ s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+ s->n_chan = 24;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = usbduxsigma_dio_insn_bits;
+ s->insn_config = usbduxsigma_dio_insn_config;
+
+ if (devpriv->high_speed) {
+ /* Timer / pwm subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE;
+ s->n_chan = 8;
+ s->maxdata = devpriv->pwm_buf_sz;
+ s->insn_write = usbduxsigma_pwm_write;
+ s->insn_config = usbduxsigma_pwm_config;
+
+ usbduxsigma_pwm_period(dev, s, PWM_DEFAULT_PERIOD);
+ }
+
+ offset = usbduxsigma_getstatusinfo(dev, 0);
+ if (offset < 0) {
+ dev_err(dev->class_dev,
+ "Communication to USBDUXSIGMA failed! Check firmware and cabling.\n");
+ return offset;
+ }
+
+ dev_info(dev->class_dev, "ADC_zero = %x\n", offset);
+
+ return 0;
+}
+
+static void usbduxsigma_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usbduxsigma_private *devpriv = dev->private;
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->sem);
+
+ /* force unlink all urbs */
+ usbduxsigma_ai_stop(dev, 1);
+ usbduxsigma_ao_stop(dev, 1);
+ usbduxsigma_pwm_stop(dev, 1);
+
+ usbduxsigma_free_usb_buffers(dev);
+
+ up(&devpriv->sem);
+}
+
+static struct comedi_driver usbduxsigma_driver = {
+ .driver_name = "usbduxsigma",
+ .module = THIS_MODULE,
+ .auto_attach = usbduxsigma_auto_attach,
+ .detach = usbduxsigma_detach,
+};
+
+static int usbduxsigma_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &usbduxsigma_driver, 0);
+}
+
+static const struct usb_device_id usbduxsigma_usb_table[] = {
+ { USB_DEVICE(0x13d8, 0x0020) },
+ { USB_DEVICE(0x13d8, 0x0021) },
+ { USB_DEVICE(0x13d8, 0x0022) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbduxsigma_usb_table);
+
+static struct usb_driver usbduxsigma_usb_driver = {
+ .name = "usbduxsigma",
+ .probe = usbduxsigma_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+ .id_table = usbduxsigma_usb_table,
+};
+module_comedi_usb_driver(usbduxsigma_driver, usbduxsigma_usb_driver);
+
+MODULE_AUTHOR("Bernd Porr, BerndPorr@f2s.com");
+MODULE_DESCRIPTION("Stirling/ITL USB-DUX SIGMA -- Bernd.Porr@f2s.com");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE);
diff --git a/drivers/staging/comedi/drivers/vmk80xx.c b/drivers/staging/comedi/drivers/vmk80xx.c
new file mode 100644
index 00000000000..0adf3cffddb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/vmk80xx.c
@@ -0,0 +1,963 @@
+/*
+ comedi/drivers/vmk80xx.c
+ Velleman USB Board Low-Level Driver
+
+ Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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.
+*/
+/*
+Driver: vmk80xx
+Description: Velleman USB Board Low-Level Driver
+Devices: K8055/K8061 aka VM110/VM140
+Author: Manuel Gebele <forensixs@gmx.de>
+Updated: Sun, 10 May 2009 11:14:59 +0200
+Status: works
+
+Supports:
+ - analog input
+ - analog output
+ - digital input
+ - digital output
+ - counter
+ - pwm
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/usb.h>
+#include <linux/uaccess.h>
+
+#include "../comedidev.h"
+
+enum {
+ DEVICE_VMK8055,
+ DEVICE_VMK8061
+};
+
+#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 IC3_VERSION (1 << 0)
+#define IC6_VERSION (1 << 1)
+
+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 vmk8061_range = {
+ 2, {
+ UNI_RANGE(5),
+ UNI_RANGE(10)
+ }
+};
+
+struct vmk80xx_board {
+ const char *name;
+ enum vmk80xx_model model;
+ const struct comedi_lrange *range;
+ int ai_nchans;
+ unsigned int ai_maxdata;
+ int ao_nchans;
+ int di_nchans;
+ unsigned int cnt_maxdata;
+ int pwm_nchans;
+ unsigned int pwm_maxdata;
+};
+
+static const struct vmk80xx_board vmk80xx_boardinfo[] = {
+ [DEVICE_VMK8055] = {
+ .name = "K8055 (VM110)",
+ .model = VMK8055_MODEL,
+ .range = &range_unipolar5,
+ .ai_nchans = 2,
+ .ai_maxdata = 0x00ff,
+ .ao_nchans = 2,
+ .di_nchans = 6,
+ .cnt_maxdata = 0xffff,
+ },
+ [DEVICE_VMK8061] = {
+ .name = "K8061 (VM140)",
+ .model = VMK8061_MODEL,
+ .range = &vmk8061_range,
+ .ai_nchans = 8,
+ .ai_maxdata = 0x03ff,
+ .ao_nchans = 8,
+ .di_nchans = 8,
+ .cnt_maxdata = 0, /* unknown, device is not writeable */
+ .pwm_nchans = 1,
+ .pwm_maxdata = 0x03ff,
+ },
+};
+
+struct vmk80xx_private {
+ struct usb_endpoint_descriptor *ep_rx;
+ struct usb_endpoint_descriptor *ep_tx;
+ struct firmware_version fw;
+ struct semaphore limit_sem;
+ unsigned char *usb_rx_buf;
+ unsigned char *usb_tx_buf;
+ enum vmk80xx_model model;
+};
+
+static int vmk80xx_check_data_link(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ unsigned char tx[1];
+ unsigned char rx[2];
+
+ tx_pipe = usb_sndbulkpipe(usb, 0x01);
+ rx_pipe = usb_rcvbulkpipe(usb, 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(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval);
+ usb_bulk_msg(usb, rx_pipe, rx, 2, NULL, HZ * 10);
+
+ return (int)rx[1];
+}
+
+static void vmk80xx_read_eeprom(struct comedi_device *dev, int flag)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ unsigned char tx[1];
+ unsigned char rx[64];
+ int cnt;
+
+ tx_pipe = usb_sndbulkpipe(usb, 0x01);
+ rx_pipe = usb_rcvbulkpipe(usb, 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(usb, tx_pipe, tx, 1, NULL, devpriv->ep_tx->bInterval);
+ usb_bulk_msg(usb, rx_pipe, rx, 64, &cnt, HZ * 10);
+
+ rx[cnt] = '\0';
+
+ if (flag & IC3_VERSION)
+ strncpy(devpriv->fw.ic3_vers, rx + 1, 24);
+ else /* IC6_VERSION */
+ strncpy(devpriv->fw.ic6_vers, rx + 25, 24);
+}
+
+static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ __u8 tx_addr;
+ __u8 rx_addr;
+ unsigned int tx_pipe;
+ unsigned int rx_pipe;
+ size_t size;
+
+ tx_addr = devpriv->ep_tx->bEndpointAddress;
+ rx_addr = devpriv->ep_rx->bEndpointAddress;
+ tx_pipe = usb_sndbulkpipe(usb, tx_addr);
+ rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
+
+ /*
+ * The max packet size attributes of the K8061
+ * input/output endpoints are identical
+ */
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+
+ usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf,
+ size, NULL, devpriv->ep_tx->bInterval);
+ usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, size, NULL, HZ * 10);
+}
+
+static int vmk80xx_read_packet(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usb_endpoint_descriptor *ep;
+ unsigned int pipe;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ vmk80xx_do_bulk_msg(dev);
+ return 0;
+ }
+
+ ep = devpriv->ep_rx;
+ pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
+ return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
+ le16_to_cpu(ep->wMaxPacketSize), NULL,
+ HZ * 10);
+}
+
+static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_device *usb = comedi_to_usb_dev(dev);
+ struct usb_endpoint_descriptor *ep;
+ unsigned int pipe;
+
+ devpriv->usb_tx_buf[0] = cmd;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ vmk80xx_do_bulk_msg(dev);
+ return 0;
+ }
+
+ ep = devpriv->ep_tx;
+ pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
+ return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
+ le16_to_cpu(ep->wMaxPacketSize), NULL,
+ HZ * 10);
+}
+
+static int vmk80xx_reset_device(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ size_t size;
+ int retval;
+
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+ memset(devpriv->usb_tx_buf, 0, size);
+ retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST);
+ if (retval)
+ return retval;
+ /* set outputs to known state as we cannot read them */
+ return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD);
+}
+
+static int vmk80xx_ai_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->model) {
+ case VMK8055_MODEL:
+ if (!chan)
+ reg[0] = VMK8055_AI1_REG;
+ else
+ reg[0] = VMK8055_AI2_REG;
+ break;
+ case VMK8061_MODEL:
+ default:
+ reg[0] = VMK8061_AI_REG1;
+ reg[1] = VMK8061_AI_REG2;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
+ devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ if (devpriv->model == VMK8055_MODEL) {
+ data[n] = devpriv->usb_rx_buf[reg[0]];
+ continue;
+ }
+
+ /* VMK8061_MODEL */
+ data[n] = devpriv->usb_rx_buf[reg[0]] + 256 *
+ devpriv->usb_rx_buf[reg[1]];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_ao_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int cmd;
+ int reg;
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->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;
+ devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ devpriv->usb_tx_buf[reg] = data[n];
+
+ if (vmk80xx_write_packet(dev, cmd))
+ break;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_ao_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg;
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ reg = VMK8061_AO_REG - 1;
+
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ data[n] = devpriv->usb_rx_buf[reg + chan];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_di_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *rx_buf;
+ int reg;
+ int retval;
+
+ down(&devpriv->limit_sem);
+
+ rx_buf = devpriv->usb_rx_buf;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ reg = VMK8061_DI_REG;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
+ } else {
+ reg = VMK8055_DI_REG;
+ }
+
+ retval = vmk80xx_read_packet(dev);
+
+ if (!retval) {
+ if (devpriv->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(&devpriv->limit_sem);
+
+ return retval;
+}
+
+static int vmk80xx_do_insn_bits(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *rx_buf = devpriv->usb_rx_buf;
+ unsigned char *tx_buf = devpriv->usb_tx_buf;
+ int reg, cmd;
+ int ret = 0;
+
+ if (devpriv->model == VMK8061_MODEL) {
+ reg = VMK8061_DO_REG;
+ cmd = VMK8061_CMD_DO;
+ } else { /* VMK8055_MODEL */
+ reg = VMK8055_DO_REG;
+ cmd = VMK8055_CMD_WRT_AD;
+ }
+
+ down(&devpriv->limit_sem);
+
+ if (comedi_dio_update_state(s, data)) {
+ tx_buf[reg] = s->state;
+ ret = vmk80xx_write_packet(dev, cmd);
+ if (ret)
+ goto out;
+ }
+
+ if (devpriv->model == VMK8061_MODEL) {
+ tx_buf[0] = VMK8061_CMD_RD_DO;
+ ret = vmk80xx_read_packet(dev);
+ if (ret)
+ goto out;
+ data[1] = rx_buf[reg];
+ } else {
+ data[1] = s->state;
+ }
+
+out:
+ up(&devpriv->limit_sem);
+
+ return ret ? ret : insn->n;
+}
+
+static int vmk80xx_cnt_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ int chan;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+ chan = CR_CHAN(insn->chanspec);
+
+ switch (devpriv->model) {
+ case VMK8055_MODEL:
+ if (!chan)
+ reg[0] = VMK8055_CNT1_REG;
+ else
+ reg[0] = VMK8055_CNT2_REG;
+ break;
+ case VMK8061_MODEL:
+ default:
+ reg[0] = VMK8061_CNT_REG;
+ reg[1] = VMK8061_CNT_REG;
+ devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
+ break;
+ }
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ if (devpriv->model == VMK8055_MODEL)
+ data[n] = devpriv->usb_rx_buf[reg[0]];
+ else /* VMK8061_MODEL */
+ data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1]
+ + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_cnt_insn_config(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned int insn_cmd;
+ int chan;
+ int cmd;
+ int reg;
+ int n;
+
+ insn_cmd = data[0];
+ if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET)
+ return -EINVAL;
+
+ down(&devpriv->limit_sem);
+
+ chan = CR_CHAN(insn->chanspec);
+
+ if (devpriv->model == VMK8055_MODEL) {
+ if (!chan) {
+ cmd = VMK8055_CMD_RST_CNT1;
+ reg = VMK8055_CNT1_REG;
+ } else {
+ cmd = VMK8055_CMD_RST_CNT2;
+ reg = VMK8055_CNT2_REG;
+ }
+
+ devpriv->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(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_cnt_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned long debtime;
+ unsigned long val;
+ int chan;
+ int cmd;
+ int n;
+
+ down(&devpriv->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;
+
+ devpriv->usb_tx_buf[6 + chan] = val;
+
+ if (vmk80xx_write_packet(dev, cmd))
+ break;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_pwm_insn_read(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *tx_buf;
+ unsigned char *rx_buf;
+ int reg[2];
+ int n;
+
+ down(&devpriv->limit_sem);
+
+ tx_buf = devpriv->usb_tx_buf;
+ rx_buf = devpriv->usb_rx_buf;
+
+ reg[0] = VMK8061_PWM_REG1;
+ reg[1] = VMK8061_PWM_REG2;
+
+ tx_buf[0] = VMK8061_CMD_RD_PWM;
+
+ for (n = 0; n < insn->n; n++) {
+ if (vmk80xx_read_packet(dev))
+ break;
+
+ data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]];
+ }
+
+ up(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_pwm_insn_write(struct comedi_device *dev,
+ struct comedi_subdevice *s,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ unsigned char *tx_buf;
+ int reg[2];
+ int cmd;
+ int n;
+
+ down(&devpriv->limit_sem);
+
+ tx_buf = devpriv->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(&devpriv->limit_sem);
+
+ return n;
+}
+
+static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct usb_host_interface *iface_desc = intf->cur_altsetting;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i;
+
+ if (iface_desc->desc.bNumEndpoints != 2)
+ return -ENODEV;
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &iface_desc->endpoint[i].desc;
+
+ if (usb_endpoint_is_int_in(ep_desc) ||
+ usb_endpoint_is_bulk_in(ep_desc)) {
+ if (!devpriv->ep_rx)
+ devpriv->ep_rx = ep_desc;
+ continue;
+ }
+
+ if (usb_endpoint_is_int_out(ep_desc) ||
+ usb_endpoint_is_bulk_out(ep_desc)) {
+ if (!devpriv->ep_tx)
+ devpriv->ep_tx = ep_desc;
+ continue;
+ }
+ }
+
+ if (!devpriv->ep_rx || !devpriv->ep_tx)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev)
+{
+ struct vmk80xx_private *devpriv = dev->private;
+ size_t size;
+
+ size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize);
+ devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_rx_buf)
+ return -ENOMEM;
+
+ size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+ devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
+ if (!devpriv->usb_tx_buf) {
+ kfree(devpriv->usb_rx_buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int vmk80xx_init_subdevices(struct comedi_device *dev)
+{
+ const struct vmk80xx_board *boardinfo = comedi_board(dev);
+ struct vmk80xx_private *devpriv = dev->private;
+ struct comedi_subdevice *s;
+ int n_subd;
+ int ret;
+
+ down(&devpriv->limit_sem);
+
+ if (devpriv->model == VMK8055_MODEL)
+ n_subd = 5;
+ else
+ n_subd = 6;
+ ret = comedi_alloc_subdevices(dev, n_subd);
+ if (ret) {
+ up(&devpriv->limit_sem);
+ return ret;
+ }
+
+ /* Analog input subdevice */
+ s = &dev->subdevices[0];
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE | SDF_GROUND;
+ s->n_chan = boardinfo->ai_nchans;
+ s->maxdata = boardinfo->ai_maxdata;
+ s->range_table = boardinfo->range;
+ s->insn_read = vmk80xx_ai_insn_read;
+
+ /* Analog output subdevice */
+ s = &dev->subdevices[1];
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
+ s->n_chan = boardinfo->ao_nchans;
+ s->maxdata = 0x00ff;
+ s->range_table = boardinfo->range;
+ s->insn_write = vmk80xx_ao_insn_write;
+ if (devpriv->model == VMK8061_MODEL) {
+ s->subdev_flags |= SDF_READABLE;
+ s->insn_read = vmk80xx_ao_insn_read;
+ }
+
+ /* Digital input subdevice */
+ s = &dev->subdevices[2];
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = boardinfo->di_nchans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = vmk80xx_di_insn_bits;
+
+ /* Digital output subdevice */
+ s = &dev->subdevices[3];
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITEABLE;
+ s->n_chan = 8;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = vmk80xx_do_insn_bits;
+
+ /* Counter subdevice */
+ s = &dev->subdevices[4];
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = 2;
+ s->maxdata = boardinfo->cnt_maxdata;
+ s->insn_read = vmk80xx_cnt_insn_read;
+ s->insn_config = vmk80xx_cnt_insn_config;
+ if (devpriv->model == VMK8055_MODEL) {
+ s->subdev_flags |= SDF_WRITEABLE;
+ s->insn_write = vmk80xx_cnt_insn_write;
+ }
+
+ /* PWM subdevice */
+ if (devpriv->model == VMK8061_MODEL) {
+ s = &dev->subdevices[5];
+ s->type = COMEDI_SUBD_PWM;
+ s->subdev_flags = SDF_READABLE | SDF_WRITEABLE;
+ s->n_chan = boardinfo->pwm_nchans;
+ s->maxdata = boardinfo->pwm_maxdata;
+ s->insn_read = vmk80xx_pwm_insn_read;
+ s->insn_write = vmk80xx_pwm_insn_write;
+ }
+
+ up(&devpriv->limit_sem);
+
+ return 0;
+}
+
+static int vmk80xx_auto_attach(struct comedi_device *dev,
+ unsigned long context)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ const struct vmk80xx_board *boardinfo;
+ struct vmk80xx_private *devpriv;
+ int ret;
+
+ boardinfo = &vmk80xx_boardinfo[context];
+ dev->board_ptr = boardinfo;
+ dev->board_name = boardinfo->name;
+
+ devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+ if (!devpriv)
+ return -ENOMEM;
+
+ devpriv->model = boardinfo->model;
+
+ ret = vmk80xx_find_usb_endpoints(dev);
+ if (ret)
+ return ret;
+
+ ret = vmk80xx_alloc_usb_buffers(dev);
+ if (ret)
+ return ret;
+
+ sema_init(&devpriv->limit_sem, 8);
+
+ usb_set_intfdata(intf, devpriv);
+
+ if (devpriv->model == VMK8061_MODEL) {
+ vmk80xx_read_eeprom(dev, IC3_VERSION);
+ dev_info(&intf->dev, "%s\n", devpriv->fw.ic3_vers);
+
+ if (vmk80xx_check_data_link(dev)) {
+ vmk80xx_read_eeprom(dev, IC6_VERSION);
+ dev_info(&intf->dev, "%s\n", devpriv->fw.ic6_vers);
+ }
+ }
+
+ if (devpriv->model == VMK8055_MODEL)
+ vmk80xx_reset_device(dev);
+
+ return vmk80xx_init_subdevices(dev);
+}
+
+static void vmk80xx_detach(struct comedi_device *dev)
+{
+ struct usb_interface *intf = comedi_to_usb_interface(dev);
+ struct vmk80xx_private *devpriv = dev->private;
+
+ if (!devpriv)
+ return;
+
+ down(&devpriv->limit_sem);
+
+ usb_set_intfdata(intf, NULL);
+
+ kfree(devpriv->usb_rx_buf);
+ kfree(devpriv->usb_tx_buf);
+
+ up(&devpriv->limit_sem);
+}
+
+static struct comedi_driver vmk80xx_driver = {
+ .module = THIS_MODULE,
+ .driver_name = "vmk80xx",
+ .auto_attach = vmk80xx_auto_attach,
+ .detach = vmk80xx_detach,
+};
+
+static int vmk80xx_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info);
+}
+
+static const struct usb_device_id vmk80xx_usb_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 },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table);
+
+static struct usb_driver vmk80xx_usb_driver = {
+ .name = "vmk80xx",
+ .id_table = vmk80xx_usb_id_table,
+ .probe = vmk80xx_usb_probe,
+ .disconnect = comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver);
+
+MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
+MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
+MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140");
+MODULE_VERSION("0.8.01");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile
new file mode 100644
index 00000000000..3aff8ed08e2
--- /dev/null
+++ b/drivers/staging/comedi/kcomedilib/Makefile
@@ -0,0 +1,5 @@
+ccflags-$(CONFIG_COMEDI_DEBUG) := -DDEBUG
+
+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 00000000000..8777f958c04
--- /dev/null
+++ b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c
@@ -0,0 +1,252 @@
+/*
+ kcomedilib/kcomedilib.c
+ a comedlib interface for kernel modules
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+
+#include "../comedi.h"
+#include "../comedilib.h"
+#include "../comedidev.h"
+
+MODULE_AUTHOR("David Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Comedi kernel library");
+MODULE_LICENSE("GPL");
+
+struct comedi_device *comedi_open(const char *filename)
+{
+ struct comedi_device *dev, *retval = NULL;
+ unsigned int minor;
+
+ if (strncmp(filename, "/dev/comedi", 11) != 0)
+ return NULL;
+
+ if (kstrtouint(filename + 11, 0, &minor))
+ return NULL;
+
+ if (minor >= COMEDI_NUM_BOARD_MINORS)
+ return NULL;
+
+ dev = comedi_dev_get_from_minor(minor);
+ if (!dev)
+ return NULL;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached)
+ retval = dev;
+ else
+ retval = NULL;
+ up_read(&dev->attach_lock);
+
+ if (retval == NULL)
+ comedi_dev_put(dev);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(comedi_open);
+
+int comedi_close(struct comedi_device *dev)
+{
+ comedi_dev_put(dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_close);
+
+static int comedi_do_insn(struct comedi_device *dev,
+ struct comedi_insn *insn,
+ unsigned int *data)
+{
+ struct comedi_subdevice *s;
+ int ret;
+
+ mutex_lock(&dev->mutex);
+
+ if (!dev->attached) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* a subdevice instruction */
+ if (insn->subdev >= dev->n_subdevices) {
+ ret = -EINVAL;
+ goto error;
+ }
+ s = &dev->subdevices[insn->subdev];
+
+ if (s->type == COMEDI_SUBD_UNUSED) {
+ dev_err(dev->class_dev,
+ "%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) {
+ dev_err(dev->class_dev, "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, data);
+ break;
+ case INSN_CONFIG:
+ /* XXX should check instruction length */
+ ret = s->insn_config(dev, s, insn, data);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ s->busy = NULL;
+error:
+
+ mutex_unlock(&dev->mutex);
+ return ret;
+}
+
+int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
+ unsigned int chan, unsigned int *io)
+{
+ struct comedi_insn insn;
+ unsigned int data[2];
+ int ret;
+
+ memset(&insn, 0, sizeof(insn));
+ insn.insn = INSN_CONFIG;
+ insn.n = 2;
+ insn.subdev = subdev;
+ insn.chanspec = CR_PACK(chan, 0, 0);
+ data[0] = INSN_CONFIG_DIO_QUERY;
+ data[1] = 0;
+ ret = comedi_do_insn(dev, &insn, data);
+ if (ret >= 0)
+ *io = data[1];
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_get_config);
+
+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.subdev = subdev;
+ insn.chanspec = CR_PACK(chan, 0, 0);
+
+ return comedi_do_insn(dev, &insn, &io);
+}
+EXPORT_SYMBOL_GPL(comedi_dio_config);
+
+int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
+ unsigned int mask, unsigned int *bits,
+ unsigned int base_channel)
+{
+ struct comedi_insn insn;
+ unsigned int data[2];
+ unsigned int n_chan;
+ unsigned int shift;
+ int ret;
+
+ base_channel = CR_CHAN(base_channel);
+ n_chan = comedi_get_n_channels(dev, subdev);
+ if (base_channel >= n_chan)
+ return -EINVAL;
+
+ memset(&insn, 0, sizeof(insn));
+ insn.insn = INSN_BITS;
+ insn.chanspec = base_channel;
+ insn.n = 2;
+ insn.subdev = subdev;
+
+ data[0] = mask;
+ data[1] = *bits;
+
+ /*
+ * Most drivers ignore the base channel in insn->chanspec.
+ * Fix this here if the subdevice has <= 32 channels.
+ */
+ if (n_chan <= 32) {
+ shift = base_channel;
+ if (shift) {
+ insn.chanspec = 0;
+ data[0] <<= shift;
+ data[1] <<= shift;
+ }
+ } else {
+ shift = 0;
+ }
+
+ ret = comedi_do_insn(dev, &insn, data);
+ *bits = data[1] >> shift;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
+
+int comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
+ unsigned int subd)
+{
+ struct comedi_subdevice *s;
+ int ret = -ENODEV;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached)
+ for (; subd < dev->n_subdevices; subd++) {
+ s = &dev->subdevices[subd];
+ if (s->type == type) {
+ ret = subd;
+ break;
+ }
+ }
+ up_read(&dev->attach_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
+
+int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)
+{
+ int n;
+
+ down_read(&dev->attach_lock);
+ if (!dev->attached || subdevice >= dev->n_subdevices)
+ n = 0;
+ else
+ n = dev->subdevices[subdevice].n_chan;
+ up_read(&dev->attach_lock);
+
+ return n;
+}
+EXPORT_SYMBOL_GPL(comedi_get_n_channels);
diff --git a/drivers/staging/comedi/proc.c b/drivers/staging/comedi/proc.c
new file mode 100644
index 00000000000..91dea25b572
--- /dev/null
+++ b/drivers/staging/comedi/proc.c
@@ -0,0 +1,97 @@
+/*
+ * /proc interface for comedi
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
+ * This program is free software; you can redistribute 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.
+ */
+
+/*
+ * This is some serious bloatware.
+ *
+ * Taken from Dave A.'s PCL-711 driver, 'cuz I thought it
+ * was cool.
+ */
+
+#include "comedidev.h"
+#include "comedi_internal.h"
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+static int comedi_read(struct seq_file *m, void *v)
+{
+ int i;
+ int devices_q = 0;
+ struct comedi_driver *driv;
+
+ seq_printf(m, "comedi version " COMEDI_RELEASE "\nformat 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 *dev = comedi_dev_get_from_minor(i);
+
+ if (!dev)
+ continue;
+
+ down_read(&dev->attach_lock);
+ if (dev->attached) {
+ devices_q = 1;
+ seq_printf(m, "%2d: %-20s %-20s %4d\n",
+ i, dev->driver->driver_name,
+ dev->board_name, dev->n_subdevices);
+ }
+ up_read(&dev->attach_lock);
+ comedi_dev_put(dev);
+ }
+ if (!devices_q)
+ seq_puts(m, "no devices\n");
+
+ mutex_lock(&comedi_drivers_list_lock);
+ for (driv = comedi_drivers; driv; driv = driv->next) {
+ seq_printf(m, "%s:\n", driv->driver_name);
+ for (i = 0; i < driv->num_names; i++)
+ seq_printf(m, " %s\n",
+ *(char **)((char *)driv->board_name +
+ i * driv->offset));
+
+ if (!driv->num_names)
+ seq_printf(m, " %s\n", driv->driver_name);
+ }
+ mutex_unlock(&comedi_drivers_list_lock);
+
+ return 0;
+}
+
+/*
+ * seq_file wrappers for procfile show routines.
+ */
+static int comedi_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, comedi_read, NULL);
+}
+
+static const struct file_operations comedi_proc_fops = {
+ .open = comedi_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void comedi_proc_init(void)
+{
+ proc_create("comedi", 0644, NULL, &comedi_proc_fops);
+}
+
+void comedi_proc_cleanup(void)
+{
+ remove_proc_entry("comedi", NULL);
+}
diff --git a/drivers/staging/comedi/range.c b/drivers/staging/comedi/range.c
new file mode 100644
index 00000000000..b6849545b81
--- /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 <ds@schleef.org>
+
+ This program is free software; you can redistribute 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 <linux/uaccess.h>
+#include "comedidev.h"
+#include "comedi_internal.h"
+
+const struct comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} };
+EXPORT_SYMBOL_GPL(range_bipolar10);
+const struct comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} };
+EXPORT_SYMBOL_GPL(range_bipolar5);
+const struct comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} };
+EXPORT_SYMBOL_GPL(range_bipolar2_5);
+const struct comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} };
+EXPORT_SYMBOL_GPL(range_unipolar10);
+const struct comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} };
+EXPORT_SYMBOL_GPL(range_unipolar5);
+const struct comedi_lrange range_unipolar2_5 = { 1, {UNI_RANGE(2.5)} };
+EXPORT_SYMBOL_GPL(range_unipolar2_5);
+const struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} };
+EXPORT_SYMBOL_GPL(range_0_20mA);
+const struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} };
+EXPORT_SYMBOL_GPL(range_4_20mA);
+const struct comedi_lrange range_0_32mA = { 1, {RANGE_mA(0, 32)} };
+EXPORT_SYMBOL_GPL(range_0_32mA);
+const struct comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none} } };
+EXPORT_SYMBOL_GPL(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) {
+ dev_dbg(dev->class_dev,
+ "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;
+ }
+ dev_dbg(s->device->class_dev, "subdevice does not support aref %i",
+ aref);
+ return 1;
+}
+
+/**
+ * comedi_check_chanlist() - Validate each element in a chanlist.
+ * @s: comedi_subdevice struct
+ * @n: number of elements in the chanlist
+ * @chanlist: the chanlist to validate
+*/
+int comedi_check_chanlist(struct comedi_subdevice *s, int n,
+ unsigned int *chanlist)
+{
+ struct comedi_device *dev = s->device;
+ unsigned int chanspec;
+ int chan, range_len, i;
+
+ for (i = 0; i < n; i++) {
+ chanspec = chanlist[i];
+ chan = CR_CHAN(chanspec);
+ if (s->range_table)
+ range_len = s->range_table->length;
+ else if (s->range_table_list && chan < s->n_chan)
+ range_len = s->range_table_list[chan]->length;
+ else
+ range_len = 0;
+ if (chan >= s->n_chan ||
+ CR_RANGE(chanspec) >= range_len ||
+ aref_invalid(s, chanspec)) {
+ dev_warn(dev->class_dev,
+ "bad chanlist[%d]=0x%08x chan=%d range length=%d\n",
+ i, chanspec, chan, range_len);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(comedi_check_chanlist);
diff --git a/drivers/staging/cptm1217/Kconfig b/drivers/staging/cptm1217/Kconfig
new file mode 100644
index 00000000000..43b1cc0a50a
--- /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 00000000000..8961fafa80e
--- /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 00000000000..303922465e4
--- /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 00000000000..ca4c2c67dd8
--- /dev/null
+++ b/drivers/staging/cptm1217/clearpad_tm1217.c
@@ -0,0 +1,666 @@
+/*
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/timer.h>
+#include <linux/gpio.h>
+#include <linux/hrtimer.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#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 failed, 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)
+ 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) {
+ 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);
+ }
+ kfree(ts);
+ return retval;
+
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+/*
+ * cp_tm1217 suspend
+ *
+ */
+static int cp_tm1217_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ 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 device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ 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;
+}
+
+#endif
+
+static SIMPLE_DEV_PM_OPS(cp_tm1217_pm_ops, cp_tm1217_suspend,
+ cp_tm1217_resume);
+
+/*
+ * 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,
+ .pm = &cp_tm1217_pm_ops,
+ },
+ .id_table = cp_tm1217_idtable,
+ .probe = cp_tm1217_probe,
+ .remove = cp_tm1217_remove,
+};
+
+module_i2c_driver(cp_tm1217_driver);
+
+MODULE_AUTHOR("Ramesh Agarwal <ramesh.agarwal@intel.com>");
+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 00000000000..30bad357a05
--- /dev/null
+++ b/drivers/staging/cptm1217/cp_tm1217.h
@@ -0,0 +1,8 @@
+#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 00000000000..56b414bca1a
--- /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 00000000000..c31657a9335
--- /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 00000000000..daca2d4d2a2
--- /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 <greg@kroah.com>
+Naren Sankar <nsankar@broadcom.com>
+Jarod Wilson <jarod@wilsonet.com>
+Scott Davilla <davilla@4pi.com>
+Manu Abraham <abraham.manu@gmail.com>
+
diff --git a/drivers/staging/crystalhd/bc_dts_defs.h b/drivers/staging/crystalhd/bc_dts_defs.h
new file mode 100644
index 00000000000..647e116e10d
--- /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 <http://www.gnu.org/licenses/>.
+ *******************************************************************/
+
+#ifndef _BC_DTS_DEFS_H_
+#define _BC_DTS_DEFS_H_
+
+#include <linux/types.h>
+
+/* 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 00000000000..92b0cff248c
--- /dev/null
+++ b/drivers/staging/crystalhd/bc_dts_glob_lnx.h
@@ -0,0 +1,300 @@
+/********************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ *******************************************************************/
+
+#ifndef _BC_DTS_GLOB_LNX_H_
+#define _BC_DTS_GLOB_LNX_H_
+
+#ifdef __LINUX_USER__
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/time.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <linux/param.h>
+#include <linux/ioctl.h>
+#include <sys/select.h>
+
+#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, /* Write FPGA Register */
+ 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 00000000000..da199ad8e27
--- /dev/null
+++ b/drivers/staging/crystalhd/bcm_70012_regs.h
@@ -0,0 +1,758 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ ***************************************************************************/
+
+#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 00000000000..b3a550bd5b0
--- /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 00000000000..642f438793c
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_cmds.c
@@ -0,0 +1,1066 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#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 management 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 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 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 false;
+ }
+
+ 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 00000000000..b5bf59dbcde
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_cmds.h
@@ -0,0 +1,92 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#ifndef _CRYSTALHD_CMDS_H_
+#define _CRYSTALHD_CMDS_H_
+
+/*
+ * NOTE:: This is the main interface file between the Linux layer
+ * and the hardware 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 00000000000..05615e2a231
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_fw_if.h
@@ -0,0 +1,370 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#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 description 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 00000000000..4765d528279
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_hw.c
@@ -0,0 +1,2458 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#include "crystalhd.h"
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+/* 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
+ * during 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) \
+do { \
+ if (q) { \
+ crystalhd_delete_dioq(adp, q); \
+ q = NULL; \
+ } \
+} while (0)
+
+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 = false;
+
+ 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 = false;
+
+ 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 = true;
+ 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 = true;
+ }
+
+ /* 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 00000000000..d5cb68dfe69
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_hw.h
@@ -0,0 +1,407 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#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 /* Return 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; /* Reserved. 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; /* Reserved. 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 00000000000..e6fb331c673
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_lnx.c
@@ -0,0 +1,782 @@
+/***************************************************************************
+ 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 <http://www.gnu.org/licenses/>.
+***************************************************************************/
+
+#include "crystalhd.h"
+
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+
+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 = 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;
+}
+
+static 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;
+}
+
+static 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(void __user *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(ud, dr, size);
+ else
+ rc = copy_from_user(dr, 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((void __user *)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);
+ vfree(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((void __user *)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((void __user *)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);
+ }
+
+ chd_dec_free_iodata(adp, temp, 0);
+
+ 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 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)) {
+ rc = PTR_ERR(crystalhd_class);
+ BCMLOG_ERR("failed to create class\n");
+ goto class_create_fail;
+ }
+
+ dev = device_create(crystalhd_class, NULL,
+ MKDEV(adp->chd_dec_major, 0), NULL, "crystalhd");
+ if (IS_ERR(dev)) {
+ rc = PTR_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(*temp), 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);
+class_create_fail:
+ unregister_chrdev(adp->chd_dec_major, CRYSTALHD_API_NAME);
+fail:
+ return rc;
+}
+
+static void 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 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 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 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 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(*pinfo), 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
+static 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;
+}
+
+static 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 const struct pci_device_id 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 = 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 <nsankar@broadcom.com>");
+MODULE_AUTHOR("Prasad Bolisetty <prasadb@broadcom.com>");
+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 00000000000..49e1ef3a19a
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_lnx.h
@@ -0,0 +1,93 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#ifndef _CRYSTALHD_LNX_H_
+#define _CRYSTALHD_LNX_H_
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <asm/pgtable.h>
+#include <linux/uaccess.h>
+
+#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 board/PCI specifics */
+ char name[32];
+ struct pci_dev *pdev;
+
+ unsigned long pci_mem_start;
+ uint32_t pci_mem_len;
+ void __iomem *addr;
+
+ unsigned long pci_i2o_start;
+ uint32_t pci_i2o_len;
+ void __iomem *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 00000000000..3aabf75b7d9
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_misc.c
@@ -0,0 +1,1044 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#include "crystalhd.h"
+
+#include <linux/slab.h>
+
+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("Invalid 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("Invalid 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 __user *)(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(&current->mm->mmap_sem);
+ res = get_user_pages(current, current->mm, uaddr, nr_pages, rw == READ,
+ 0, dio->pages, NULL);
+ up_read(&current->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 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 00000000000..0f63827acfb
--- /dev/null
+++ b/drivers/staging/crystalhd/crystalhd_misc.h
@@ -0,0 +1,232 @@
+/***************************************************************************
+ * 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 <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+#ifndef _CRYSTALHD_MISC_H_
+#define _CRYSTALHD_MISC_H_
+
+#include "crystalhd.h"
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/ioctl.h>
+#include <linux/dma-mapping.h>
+#include <linux/sched.h>
+#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) (sg_dma_address(&_dio->sg[_ix]))
+#define crystalhd_get_sgle_len(_dio, _ix) (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, /* Special 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) \
+ pr_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 00000000000..947f42a65c5
--- /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 00000000000..b879e7b553c
--- /dev/null
+++ b/drivers/staging/cxt1e1/Makefile
@@ -0,0 +1,18 @@
+obj-$(CONFIG_CXT1E1) += cxt1e1.o
+
+ccflags-y := -DSBE_PMCC4_ENABLE
+ccflags-y += -DSBE_ISR_TASKLET
+
+cxt1e1-y := \
+ musycc.o \
+ pmcc4_drv.o \
+ comet.o \
+ linux.o \
+ functions.o \
+ hwprobe.o \
+ pmc93x6_eeprom.o \
+ sbecrc.o \
+ comet_tables.o \
+ sbeid.o
+
+cxt1e1-$(CONFIG_PROC_FS) += sbeproc.o
diff --git a/drivers/staging/cxt1e1/comet.c b/drivers/staging/cxt1e1/comet.c
new file mode 100644
index 00000000000..7005ad02233
--- /dev/null
+++ b/drivers/staging/cxt1e1/comet.c
@@ -0,0 +1,596 @@
+/* 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 <linux/io.h>
+#include <linux/hdlc.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "libsbew.h"
+#include "pmcc4.h"
+#include "comet.h"
+#include "comet_tables.h"
+
+
+#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(struct s_comet_reg *comet);
+static void WrtRcvEqualizerTbl(ci_t *ci, struct s_comet_reg *comet,
+ u_int32_t *table);
+static void WrtXmtWaveformTbl(ci_t *ci, struct s_comet_reg *comet,
+ u_int8_t table[COMET_NUM_SAMPLES]
+ [COMET_NUM_UNITS]);
+
+
+static void *TWV_table[12] = {
+ TWVLongHaul0DB, TWVLongHaul7_5DB, TWVLongHaul15DB, TWVLongHaul22_5DB,
+ TWVShortHaul0, TWVShortHaul1, TWVShortHaul2, TWVShortHaul3,
+ TWVShortHaul4, TWVShortHaul5,
+ /** PORT POINT - 75 Ohm not supported **/
+ TWV_E1_75Ohm,
+ TWV_E1_120Ohm
+};
+
+
+static int
+lbo_tbl_lkup(int t1, int lbo) {
+ /* error switches to default */
+ if ((lbo < CFG_LBO_LH0) || (lbo > CFG_LBO_E120)) {
+ if (t1)
+ /* default T1 waveform table */
+ lbo = CFG_LBO_LH0;
+
+ else
+ /* default E1 waveform table */
+ lbo = CFG_LBO_E120;
+ }
+ /* make index ZERO relative */
+ return lbo - 1;
+}
+
+void init_comet(void *ci, struct s_comet_reg *comet, u_int32_t port_mode,
+ int clockmaster, u_int8_t moreParams)
+{
+ u_int8_t isT1mode;
+ /* T1 default */
+ u_int8_t tix = CFG_LBO_LH0;
+ isT1mode = IS_FRAME_ANY_T1(port_mode);
+ /* T1 or E1 */
+ if (isT1mode) {
+ /* Select T1 Mode & PIO output enabled */
+ pci_write_32((u_int32_t *) &comet->gbl_cfg, 0xa0);
+ /* default T1 waveform table */
+ tix = lbo_tbl_lkup(isT1mode, CFG_LBO_LH0);
+ } else {
+ /* Select E1 Mode & PIO output enabled */
+ pci_write_32((u_int32_t *) &comet->gbl_cfg, 0x81);
+ /* default E1 waveform table */
+ tix = lbo_tbl_lkup(isT1mode, CFG_LBO_E120);
+ }
+
+ if (moreParams & CFG_LBO_MASK)
+ /* dial-in requested waveform table */
+ tix = lbo_tbl_lkup(isT1mode, moreParams & CFG_LBO_MASK);
+ /* Tx line Intfc cfg Set for analog & no special patterns */
+ /* Transmit Line Interface Config. */
+ pci_write_32((u_int32_t *) &comet->tx_line_cfg, 0x00);
+ /* master test Ignore Test settings for now */
+ /* making sure it's Default value */
+ pci_write_32((u_int32_t *) &comet->mtest, 0x00);
+ /* Turn on Center (CENT) and everything else off */
+ /* RJAT cfg */
+ pci_write_32((u_int32_t *) &comet->rjat_cfg, 0x10);
+ /* Set Jitter Attenuation to recommend T1 values */
+ if (isT1mode) {
+ /* RJAT Divider N1 Control */
+ pci_write_32((u_int32_t *) &comet->rjat_n1clk, 0x2F);
+ /* RJAT Divider N2 Control */
+ pci_write_32((u_int32_t *) &comet->rjat_n2clk, 0x2F);
+ } else {
+ /* RJAT Divider N1 Control */
+ pci_write_32((u_int32_t *) &comet->rjat_n1clk, 0xFF);
+ /* RJAT Divider N2 Control */
+ pci_write_32((u_int32_t *) &comet->rjat_n2clk, 0xFF);
+ }
+
+ /* Turn on Center (CENT) and everything else off */
+ /* TJAT Config. */
+ pci_write_32((u_int32_t *) &comet->tjat_cfg, 0x10);
+
+ /* Do not bypass jitter attenuation and bypass elastic store */
+ /* rx opts */
+ pci_write_32((u_int32_t *) &comet->rx_opt, 0x00);
+
+ /* TJAT ctrl & TJAT divider ctrl */
+ /* Set Jitter Attenuation to recommended T1 values */
+ if (isT1mode) {
+ /* TJAT Divider N1 Control */
+ pci_write_32((u_int32_t *) &comet->tjat_n1clk, 0x2F);
+ /* TJAT Divider N2 Control */
+ pci_write_32((u_int32_t *) &comet->tjat_n2clk, 0x2F);
+ } else {
+ /* TJAT Divider N1 Control */
+ pci_write_32((u_int32_t *) &comet->tjat_n1clk, 0xFF);
+ /* TJAT Divider N2 Control */
+ pci_write_32((u_int32_t *) &comet->tjat_n2clk, 0xFF);
+ }
+
+ /* 1c: rx ELST cfg 20: tx ELST cfg 28&38: rx&tx data link ctrl */
+
+ /* Select 193-bit frame format */
+ if (isT1mode) {
+ 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);
+ /* disable T1 data link receive */
+ pci_write_32((u_int32_t *) &comet->rxce1_ctl, 0x00);
+ /* disable T1 data link transmit */
+ pci_write_32((u_int32_t *) &comet->txci1_ctl, 0x00);
+ }
+
+ /* the following is a default value */
+ /* Enable 8 out of 10 validation */
+ /* t1RBOC enable(BOC:BitOriented Code) */
+ pci_write_32((u_int32_t *) &comet->t1_rboc_ena, 0x00);
+ if (isT1mode) {
+ /* IBCD cfg: aka Inband Code Detection ** loopback code length
+ * set to
+ */
+ /* 6 bit down, 5 bit up (assert) */
+ pci_write_32((u_int32_t *) &comet->ibcd_cfg, 0x04);
+ /* line loopback activate pattern */
+ pci_write_32((u_int32_t *) &comet->ibcd_act, 0x08);
+ /* deactivate code pattern (i.e.001) */
+ pci_write_32((u_int32_t *) &comet->ibcd_deact, 0x24);
+ }
+ /* 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) {
+ /* 1 - T1 B8ZS */
+ case CFG_FRAME_SF:
+ 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);
+ /* 5:B8ZS */
+ pci_write_32((u_int32_t *) &comet->t1_xbas_cfg, 0x20);
+ pci_write_32((u_int32_t *) &comet->t1_almi_cfg, 0);
+ break;
+ /* 2 - T1 B8ZS */
+ case CFG_FRAME_ESF:
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0);
+ /* Bit 5: T1 DataLink Enable */
+ pci_write_32((u_int32_t *) &comet->rxce1_ctl, 0x20);
+ /* 5: T1 DataLink Enable */
+ pci_write_32((u_int32_t *) &comet->txci1_ctl, 0x20);
+ /* 4:ESF 5:ESFFA */
+ pci_write_32((u_int32_t *) &comet->t1_frmr_cfg, 0x30);
+ /* 2:ESF */
+ pci_write_32((u_int32_t *) &comet->sigx_cfg, 0x04);
+ /* 4:ESF 5:B8ZS */
+ pci_write_32((u_int32_t *) &comet->t1_xbas_cfg, 0x30);
+ /* 4:ESF */
+ pci_write_32((u_int32_t *) &comet->t1_almi_cfg, 0x10);
+ break;
+ /* 3 - HDB3 */
+ case CFG_FRAME_E1PLAIN:
+ 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;
+ /* 4 - HDB3 */
+ case CFG_FRAME_E1CAS:
+ 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;
+ /* 5 - HDB3 */
+ case CFG_FRAME_E1CRC:
+ 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;
+ /* 6 - HDB3 */
+ case CFG_FRAME_E1CRC_CAS:
+ 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;
+ /* 7 - T1 AMI */
+ case CFG_FRAME_SF_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ 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;
+ /* 8 - T1 AMI */
+ case CFG_FRAME_ESF_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ /* 5: T1 DataLink Enable */
+ pci_write_32((u_int32_t *) &comet->rxce1_ctl, 0x20);
+ /* 5: T1 DataLink Enable */
+ pci_write_32((u_int32_t *) &comet->txci1_ctl, 0x20);
+ /* Bit 4:ESF 5:ESFFA */
+ pci_write_32((u_int32_t *) &comet->t1_frmr_cfg, 0x30);
+ /* 2:ESF */
+ pci_write_32((u_int32_t *) &comet->sigx_cfg, 0x04);
+ /* 4: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);
+ break;
+ /* 9 - AMI */
+ case CFG_FRAME_E1PLAIN_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ 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;
+ /* 10 - AMI */
+ case CFG_FRAME_E1CAS_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ 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;
+ /* 11 - AMI */
+ case CFG_FRAME_E1CRC_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ 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;
+ /* 12 - AMI */
+ case CFG_FRAME_E1CRC_CAS_AMI:
+ /* Enable AMI Line Decoding */
+ pci_write_32((u_int32_t *) &comet->cdrc_cfg, 0x80);
+ 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)
+ */
+ /* rate = 1.544 Mb/s */
+ if (isT1mode)
+ /* Comet 0 Master Mode(CMODE=0) */
+ pci_write_32((u_int32_t *) &comet->brif_cfg, 0x00);
+ /* rate = 2.048 Mb/s */
+ else
+ /* Comet 0 Master Mode(CMODE=0) */
+ pci_write_32((u_int32_t *) &comet->brif_cfg, 0x01);
+
+ /* 31: BRIF frame pulse cfg 06: tx timing options */
+
+ /* Master Mode i.e.FPMODE=0 (@0x20) */
+ pci_write_32((u_int32_t *) &comet->brif_fpcfg, 0x00);
+ if ((moreParams & CFG_CLK_PORT_MASK) == CFG_CLK_PORT_INTERNAL) {
+ if (cxt1e1_log_level >= LOG_SBEBUG12)
+ pr_info(">> %s: clockmaster internal clock\n",
+ __func__);
+ /* internal oscillator */
+ pci_write_32((u_int32_t *) &comet->tx_time, 0x0d);
+ } else {
+ /* external clock source */
+ if (cxt1e1_log_level >= LOG_SBEBUG12)
+ pr_info(">> %s: clockmaster external clock\n",
+ __func__);
+ /* loop timing(external) */
+ pci_write_32((u_int32_t *) &comet->tx_time, 0x09);
+ }
+
+ } else {
+ /* slave */
+ if (isT1mode)
+ /* Slave Mode(CMODE=1, see above) */
+ pci_write_32((u_int32_t *) &comet->brif_cfg, 0x20);
+ else
+ /* Slave Mode(CMODE=1)*/
+ pci_write_32((u_int32_t *) &comet->brif_cfg, 0x21);
+ /* Slave Mode i.e. FPMODE=1 (@0x20) */
+ pci_write_32((u_int32_t *) &comet->brif_fpcfg, 0x20);
+ if (cxt1e1_log_level >= LOG_SBEBUG12)
+ pr_info(">> %s: clockslave internal clock\n", __func__);
+ /* oscillator timing */
+ pci_write_32((u_int32_t *) &comet->tx_time, 0x0d);
+ }
+
+ /* 32: BRIF parity F-bit cfg */
+ /* Totem-pole operation */
+ /* Receive Backplane Parity/F-bit */
+ pci_write_32((u_int32_t *) &comet->brif_pfcfg, 0x01);
+
+ /* dc: RLPS equalizer V ref */
+ /* Configuration */
+ if (isT1mode)
+ /* RLPS Equalizer Voltage */
+ pci_write_32((u_int32_t *) &comet->rlps_eqvr, 0x2c);
+ else
+ /* RLPS Equalizer Voltage */
+ pci_write_32((u_int32_t *) &comet->rlps_eqvr, 0x34);
+
+ /* Reserved bit set and SQUELCH enabled */
+ /* f8: RLPS cfg & status f9: RLPS ALOS detect/clear threshold */
+ /* RLPS Configuration Status */
+ pci_write_32((u_int32_t *) &comet->rlps_cfgsts, 0x11);
+ 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: loop timing(external) */
+ /*BTIF frame pulse cfg */
+ if (isT1mode)
+ /* BTIF Configuration Reg. */
+ pci_write_32((u_int32_t *) &comet->btif_cfg, 0x38);
+ else
+ /* BTIF Configuration Reg. */
+ pci_write_32((u_int32_t *) &comet->btif_cfg, 0x39);
+ /* BTIF Frame Pulse Config. */
+ pci_write_32((u_int32_t *) &comet->btif_fpcfg, 0x01);
+
+ /* 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, struct s_comet_reg *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);
+ /* for write order preservation when Optimizing driver */
+ pci_flush_write(ci);
+ 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, struct s_comet_reg *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, struct s_comet_reg *comet, u_int32_t *table)
+{
+ u_int32_t ramaddr;
+ u_int32_t value;
+
+ for (ramaddr = 0; ramaddr < 256; ramaddr++) {
+ /*** the following lines are per Errata 7, 2.5 ***/
+ {
+ /* Set up for a read operation */
+ pci_write_32((u_int32_t *) &comet->rlps_eq_rwsel, 0x80);
+ /* for write order preservation when Optimizing driver */
+ pci_flush_write(ci);
+ /* write the addr, initiate a read */
+ pci_write_32((u_int32_t *) &comet->rlps_eq_iaddr,
+ (u_int8_t) ramaddr);
+ /* for write order preservation when Optimizing driver */
+ pci_flush_write(ci);
+ /*
+ * wait 3 line rate clock cycles to ensure address bits are
+ * captured by T1/E1 clock
+ */
+
+ /* 683ns * 3 = 1366 ns, approx 2us (but use 4us) */
+ OS_uwait(4, "wret");
+ }
+
+ 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);
+ /* for write order preservation when Optimizing driver */
+ pci_flush_write(ci);
+
+ /* Storing RAM address, causes RAM to be updated */
+
+ /* Set up for a write operation */
+ pci_write_32((u_int32_t *) &comet->rlps_eq_rwsel, 0);
+ /* for write order preservation when optimizing driver */
+ pci_flush_write(ci);
+ /* write the addr, initiate a read */
+ pci_write_32((u_int32_t *) &comet->rlps_eq_iaddr,
+ (u_int8_t) ramaddr);
+ /* for write order preservation when optimizing driver */
+ pci_flush_write(ci);
+
+ /*
+ * wait 3 line rate clock cycles to ensure address bits are captured
+ * by T1/E1 clock
+ */
+ /* 683ns * 3 = 1366 ns, approx 2us (but use 4us) */
+ OS_uwait(4, "wret");
+ }
+
+ /* Enable Equalizer & set it to use 256 periods */
+ pci_write_32((u_int32_t *) &comet->rlps_eq_cfg, 0xCB);
+}
+
+
+/*
+** Name: SetPwrLevel
+** Description: Implement power level setting algorithm described below
+** Returns: Nothing
+*/
+
+static void
+SetPwrLevel(struct s_comet_reg *comet)
+{
+ 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
+*/
+ /* XLPG Fuse Data Select */
+ pci_write_32((u_int32_t *) &comet->xlpg_fdata_sel, 0x00);
+ /* XLPG Analog Test Positive control */
+ pci_write_32((u_int32_t *) &comet->xlpg_atest_pctl, 0x01);
+ 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);
+ pci_write_32((u_int32_t *) &comet->xlpg_atest_nctl, 0x01);
+ /* XLPG Analog Test Negative control */
+ temp = pci_read_32((u_int32_t *) &comet->xlpg_atest_nctl) & 0xfe;
+ pci_write_32((u_int32_t *) &comet->xlpg_atest_nctl, temp);
+ /* XLPG */
+ pci_write_32((u_int32_t *) &comet->xlpg_fdata_sel, 0x01);
+}
+
+
+/*
+** 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(struct s_comet_reg *comet)
+{
+ u_int8_t rd_value;
+
+ if (comet == mConfig.C4Func1Base + (COMET0_OFFSET >> 2)) {
+ /* read the BRIF Configuration */
+ rd_value = (u_int8_t) pci_read_32((u_int32_t *)
+ &comet->brif_cfg);
+ rd_value &= ~0x20;
+ pci_write_32((u_int32_t *) &comet->brif_cfg,
+ (u_int32_t) rd_value);
+ /* read the BRIF Frame Pulse Configuration */
+ rd_value = (u_int8_t) pci_read_32((u_int32_t *)
+ &comet->brif_fpcfg);
+ rd_value &= ~0x20;
+ pci_write_32((u_int32_t *) &comet->brif_fpcfg,
+ (u_int8_t) rd_value);
+ } else {
+ /* read the BRIF Configuration */
+ rd_value = (u_int8_t) pci_read_32((u_int32_t *) &comet->brif_cfg);
+ rd_value |= 0x20;
+ pci_write_32((u_int32_t *) &comet->brif_cfg, (u_int32_t) rd_value);
+ /* read the BRIF Frame Pulse Configuration */
+ rd_value = (u_int8_t) pci_read_32((u_int32_t *) &comet->brif_fpcfg);
+ 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 00000000000..d5d286e47a4
--- /dev/null
+++ b/drivers/staging/cxt1e1/comet.h
@@ -0,0 +1,353 @@
+#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 <linux/types.h>
+
+#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 */
+};
+
+/* 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 *, struct s_comet_reg *, 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 00000000000..e96665ea366
--- /dev/null
+++ b/drivers/staging/cxt1e1/comet_tables.c
@@ -0,0 +1,538 @@
+/*-----------------------------------------------------------------------------
+ * 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 <linux/types.h>
+#include "comet_tables.h"
+
+/*****************************************************************************
+*
+* Array names:
+*
+* TWVLongHaul0DB
+* TWVLongHaul7_5DB
+* TWVLongHaul15DB
+* TWVLongHaul22_5DB
+* TWVShortHaul0
+* TWVShortHaul1
+* TWVShortHaul2
+* TWVShortHaul3
+* TWVShortHaul4
+* TWVShortHaul5
+* TWV_E1_120Ohm
+* TWV_E1_75Ohm <not supported>
+* 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 00000000000..3e2e5badf78
--- /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 <not supported>
+* 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 00000000000..65b6fc36edf
--- /dev/null
+++ b/drivers/staging/cxt1e1/functions.c
@@ -0,0 +1,318 @@
+/* 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 <linux/slab.h>
+#include <linux/io.h>
+#include <asm/byteorder.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+#include <linux/hdlc.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "libsbew.h"
+#include "pmcc4.h"
+
+
+#ifndef USE_MAX_INT_DELAY
+static int dummy = 0;
+
+#endif
+
+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_TRIGGER(ci_t *ci, int x)
+{
+ struct s_comet_reg *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 00000000000..4fa27c8931b
--- /dev/null
+++ b/drivers/staging/cxt1e1/hwprobe.c
@@ -0,0 +1,382 @@
+/* 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 <linux/netdevice.h>
+#include <linux/hdlc.h>
+#include <linux/if_arp.h>
+#include <asm/uaccess.h>
+#include <linux/rtnetlink.h>
+#include <linux/pci.h>
+#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
+
+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 banner[80];
+ char sn[6] = {0,};
+
+ ci = (ci_t *)(netdev_priv(hi->ndev));
+ bid = sbeid_get_bdname(ci);
+ switch (hi->promfmt) {
+ case PROM_FORMAT_TYPE1:
+ memcpy(sn, hi->mfg_info.pft1.Serial, 6);
+ break;
+ case PROM_FORMAT_TYPE2:
+ memcpy(sn, hi->mfg_info.pft2.Serial, 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
+
+ hi->promfmt = pmc_verify_cksum(&hi->mfg_info.data);
+ if (hi->promfmt == 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 = NULL;
+ hi->addr[0] = 0L;
+ hi->addr[1] = 0L;
+ hi->addr_mapped[0] = NULL;
+ hi->addr_mapped[1] = NULL;
+ }
+}
+
+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(hi->addr_mapped[0]);
+ release_mem_region((long) hi->addr[0], hi->len[0]);
+ hi->addr_mapped[0] = NULL;
+ }
+ if (hi->addr_mapped[1]) {
+ iounmap(hi->addr_mapped[1]);
+ release_mem_region((long) hi->addr[1], hi->len[1]);
+ hi->addr_mapped[1] = NULL;
+ }
+ }
+}
+
+
+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
+ 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 */
+ fun = PCI_FUNC(pdev->devfn);
+ if (fun > 1) {
+ pr_warning("unexpected devfun: 0x%x\n", pdev->devfn);
+ return 0;
+ }
+
+ /* obtain bus number */
+ if (pdev->bus)
+ 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
+ * fisrt found
+ */
+ if ((hi->pci_slot == 0xff) || /* new board */
+ ((hi->pci_slot == slot) && (hi->bus == pdev->bus)))
+ break; /* found for-loop exit */
+ }
+
+ /* no match in above loop means MAX exceeded */
+ if (i == MAX_BOARDS) {
+ 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)) {
+ 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] = 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]);
+ 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);
+ if (!hi->ndev) {
+ 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 00000000000..bd2bfba604b
--- /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 /* <not defined> */
+#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 <intr_mask> */
+#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 <status> */
+#define TX_ENABLED 0x01
+#define RX_ENABLED 0x02
+
+/* Protocol modes <mode> */
+#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 <idlecode> */
+#define CFG_CH_FLAG_7E 0
+#define CFG_CH_FLAG_FF 1
+#define CFG_CH_FLAG_00 2
+
+/* data inversion selection <data_inv> */
+#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 <framing_type> */
+#define FRAMING_M13 0
+#define FRAMING_CBP 1
+
+/* card level loopback options <loopback> */
+#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 <loopback> */
+#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 <clock> */
+#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 <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 <ds1_unchan> */
+#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 <frame_type> */
+#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 <loopback_type> */
+#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 00000000000..09f3d5ca75a
--- /dev/null
+++ b/drivers/staging/cxt1e1/linux.c
@@ -0,0 +1,1142 @@
+/* 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 <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/hdlc.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/rtnetlink.h>
+#include <linux/skbuff.h>
+#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
+
+/*******************************************************************************/
+
+#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 *);
+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 ci_t *CI;
+extern struct s_hdw_info hdw_info[];
+
+int error_flag; /* module load error reporting */
+int cxt1e1_log_level = LOG_ERROR;
+static int log_level_default = LOG_ERROR;
+module_param(cxt1e1_log_level, int, 0444);
+
+int cxt1e1_max_mru = MUSYCC_MRU;
+static 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 : NULL;
+}
+
+
+char *
+get_hdlc_name(hdlc_device *hdlc)
+{
+ struct c4_priv *priv = hdlc->priv;
+ struct net_device *dev = getuserbychan(priv->channum);
+
+ return dev->name;
+}
+
+/***************************************************************************/
+#include <linux/workqueue.h>
+
+/***
+ * 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]; /* NOTE: name of the queue limited by system
+ * to 10 characters */
+ if (pi->wq_port)
+ return 0; /* already initialized */
+
+ /* IE pmcc4-01 */
+ snprintf(name, sizeof(name), "%s%d", pi->up->devname, pi->portnum);
+
+#ifdef RLD_RESTART_DEBUG
+ pr_info(">> %s: creating workqueue <%s> for Port %d.\n",
+ __func__, name, pi->portnum); /* RLD DEBUG */
+#endif
+ pi->wq_port = create_singlethread_workqueue(name);
+ if (!pi->wq_port)
+ 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 = NULL;
+ }
+}
+
+/***************************************************************************/
+
+static 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
+static 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;
+
+ ret = hdlc_open(ndev);
+ if (ret) {
+ pr_info("hdlc_open failure, err %d.\n", ret);
+ return ret;
+ }
+
+ ret = c4_chan_up(priv->ci, priv->channum);
+ if (ret < 0)
+ 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)
+{
+ /* our driver has nothing to do here, show's
+ * over, go home
+ */
+ return 0;
+}
+
+
+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 NULL; /* channel already exists */
+
+ {
+ struct c4_priv *priv;
+
+ /* allocate then fill in private data structure */
+ priv = kzalloc(sizeof(struct c4_priv), GFP_KERNEL);
+ if (!priv) {
+ pr_warning("%s: no memory for net_device !\n",
+ ci->devname);
+ return NULL;
+ }
+ dev = alloc_hdlcdev(priv);
+ if (!dev) {
+ pr_warning("%s: no memory for hdlc_device !\n",
+ ci->devname);
+ kfree(priv);
+ return NULL;
+ }
+ 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;
+
+ /* needed due to Ioctl calling sequence */
+ rtnl_unlock();
+ ret = register_hdlc_device(dev);
+ /* NOTE: <stats> setting must occur AFTER registration in order to "take" */
+ dev->tx_queue_len = MAX_DEFAULT_IFQLEN;
+
+ /* needed due to Ioctl calling sequence */
+ rtnl_lock();
+ if (ret) {
+ if (cxt1e1_log_level >= LOG_WARN)
+ pr_info("%s: create_chan[%d] registration error = %d.\n",
+ ci->devname, cp->channum, ret);
+ /* cleanup */
+ free_netdev(dev);
+ /* failed to register */
+ return NULL;
+ }
+ 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 = c4_get_port(ci, pp.portnum);
+ if (ret < 0)
+ 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 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 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 = c4_frame_rw(ci, &pp);
+ if (ret < 0)
+ 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 = 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 = c4_musycc_rw(ci, &mp);
+ if (ret < 0)
+ 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;
+
+ ret = c4_get_chan(cp.channum, &cp);
+ if (ret < 0)
+ 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;
+ 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;
+ return c4_set_chan(cp.channum, &cp);
+}
+
+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 = c4_new_chan(ci, cp.port, cp.channum, dev);
+ if (ret < 0) {
+ /* needed due to Ioctl calling sequence */
+ rtnl_unlock();
+ unregister_hdlc_device(dev);
+ /* needed due to Ioctl calling sequence */
+ rtnl_lock();
+ 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;
+
+ ret = c4_get_chan_stats(ccs.channum, &ccs.stats);
+ if (ret < 0)
+ 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 = NULL; /* will be freed, below */
+ }
+
+ /* needed if Ioctl calling sequence */
+ if (lockit)
+ rtnl_unlock();
+ unregister_hdlc_device(ndev);
+ /* needed if Ioctl calling sequence */
+ if (lockit)
+ rtnl_lock();
+ 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;
+ if (cp.channum > 999)
+ return -EINVAL;
+ snprintf(buf, sizeof(buf), CHANNAME "%d", cp.channum);
+ dev = __dev_get_by_name(&init_net, buf);
+ if (!dev)
+ return -ENODEV;
+ 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);
+ ndev = __dev_get_by_name(&init_net, buf);
+ if (!ndev)
+ continue;
+ priv = dev_to_hdlc(ndev)->priv;
+
+ if ((unsigned long) (priv->ci) ==
+ (unsigned long) (netdev_priv(musycc_dev))) {
+ ndev->flags &= ~IFF_UP;
+ netif_stop_queue(ndev);
+ do_deluser(ndev, 1);
+ }
+ }
+ 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 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;
+ ci = get_ci_by_dev(ndev);
+ if (!ci)
+ 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);
+ if (iolen > sizeof(arg))
+ return -EFAULT;
+ data = ifr->ifr_data + sizeof(iocmd);
+ if (copy_from_user(&arg, data, iolen))
+ return -EFAULT;
+
+ ret = 0;
+ switch (iocmd) {
+ case SBE_IOC_PORT_GET:
+ ret = do_get_port(ndev, data);
+ break;
+ case SBE_IOC_PORT_SET:
+ ret = do_set_port(ndev, data);
+ break;
+ case SBE_IOC_CHAN_GET:
+ ret = do_get_chan(ndev, data);
+ break;
+ case SBE_IOC_CHAN_SET:
+ ret = do_set_chan(ndev, data);
+ break;
+ case C4_DEL_CHAN:
+ 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:
+ ret = -EINVAL;
+ break;
+ }
+ return 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 NULL;
+ }
+ 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)
+ CI = ci; /* DEBUG, only board 0 usage */
+
+ strcpy(ci->devname, hi->devname);
+
+ /* 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)) {
+ kfree(netdev_priv(ndev));
+ kfree(ndev);
+ error_flag = -ENODEV;
+ return NULL;
+ }
+ /*************************************************************
+ * 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);
+ kfree(netdev_priv(ndev));
+ kfree(ndev);
+ error_flag = -EIO;
+ return NULL;
+ }
+#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);
+ kfree(netdev_priv(ndev));
+ kfree(ndev);
+ error_flag = -EIO;
+ return NULL;
+ }
+#endif
+
+ /* setup board identification information */
+
+ {
+ u_int32_t tmp;
+
+ /* also sets PROM format type (promfmt) for later usage */
+ hdw_sn_get(hi, brdno);
+
+ switch (hi->promfmt) {
+ case PROM_FORMAT_TYPE1:
+ memcpy(ndev->dev_addr,
+ (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6);
+ /* unaligned data acquisition */
+ memcpy(&tmp, (FLD_TYPE1 *) (hi->mfg_info.pft1.Id), 4);
+ ci->brd_id = cpu_to_be32(tmp);
+ break;
+ case PROM_FORMAT_TYPE2:
+ memcpy(ndev->dev_addr,
+ (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6);
+ /* unaligned data acquisition */
+ memcpy(&tmp, (FLD_TYPE2 *) (hi->mfg_info.pft2.Id), 4);
+ ci->brd_id = cpu_to_be32(tmp);
+ break;
+ default:
+ ci->brd_id = 0;
+ memset(ndev->dev_addr, 0, 6);
+ break;
+ }
+
+#if 1
+ /* requires bid to be preset */
+ sbeid_set_hdwbid(ci);
+#else
+ /* requires hdw_bid to be preset */
+ sbeid_set_bdtype(ci);
+#endif
+ }
+
+#ifdef CONFIG_PROC_FS
+ sbecom_proc_brd_init(ci);
+#endif
+#if defined(SBE_ISR_TASKLET)
+ tasklet_enable(&ci->ci_musycc_isr_tasklet);
+#endif
+
+ error_flag = c4_init2(ci);
+ if (error_flag != SBE_DRVR_SUCCESS) {
+#ifdef CONFIG_PROC_FS
+ sbecom_proc_brd_cleanup(ci);
+#endif
+ unregister_netdev(ndev);
+ free_irq(irq1, ndev);
+ free_irq(irq0, ndev);
+ kfree(netdev_priv(ndev));
+ kfree(ndev);
+ /* failure, error_flag is set */
+ return NULL;
+ }
+ return ndev;
+}
+
+static int __init
+c4_mod_init(void)
+{
+ int rtn;
+
+ rtn = c4hw_attach_all();
+ if (rtn)
+ return -rtn; /* installation failure - see system log */
+
+ /* housekeeping notifications */
+ if (cxt1e1_log_level != log_level_default)
+ pr_info("NOTE: driver parameter <cxt1e1_log_level> 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 <cxt1e1_max_mru> 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 <cxt1e1_max_mtu> 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 <max_rxdesc_used> 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 <max_txdesc_used> 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++) {
+ ndev = ci->port[j].chan[k]->user;
+ if (ndev)
+ 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 <support@sbei.com>");
+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 00000000000..0bcbd8a3fc8
--- /dev/null
+++ b/drivers/staging/cxt1e1/musycc.c
@@ -0,0 +1,1720 @@
+static unsigned int max_intcnt;
+static unsigned int max_bh;
+
+/*-----------------------------------------------------------------------------
+ * 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 <linux/types.h>
+#include "pmcc4_sysdep.h"
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include "sbecom_inline_linux.h"
+#include "libsbew.h"
+#include "pmcc4_private.h"
+#include "pmcc4.h"
+#include "musycc.h"
+
+#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_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 *);
+void musycc_intr_bh_tasklet(ci_t *);
+void musycc_serv_req(mpi_t *, u_int32_t);
+void musycc_update_timeslots(mpi_t *);
+
+/*******************************************************************/
+
+static int
+musycc_dump_rxbuffer_ring(mch_t *ch, int lockit)
+{
+ struct mdesc *m;
+ unsigned long flags = 0;
+
+ u_int32_t status;
+ int n;
+
+#ifdef RLD_DUMP_BUFDATA
+ u_int32_t *dp;
+ int len = 0;
+#endif
+ 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
+ len = status & LENGTH_MASK;
+
+#if 1
+ if (m->data && (status & HOST_RX_OWNED))
+#else
+ /* always dump regardless of valid RX data */
+ if (m->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;
+ }
+ }
+ pr_info("\n");
+
+ if (lockit)
+ spin_unlock_irqrestore(&ch->ch_rxlock, flags);
+ return 0;
+}
+
+static int
+musycc_dump_txbuffer_ring(mch_t *ch, int lockit)
+{
+ struct mdesc *m;
+ unsigned long flags = 0;
+ u_int32_t status;
+ int n;
+#ifdef RLD_DUMP_BUFDATA
+ u_int32_t *dp;
+ int len = 0;
+#endif
+
+ 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
+ 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;
+}
+
+/*
+ * 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;
+ int bh;
+
+ if (chan >= MAX_CHANS_USED)
+ return SBE_DRVR_FAIL; /* E2BIG */
+
+ 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 */
+
+ ch = sd_find_chan(dummy, chan);
+ if (!ch) {
+ 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("--------------------------------\n");
+ pr_info("TX 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 defined(RLD_TRANS_DEBUG) || defined(RLD_RXACT_DEBUG)
+ static int hereb4 = 7;
+#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)
+ 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);
+
+ 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
+ } else if (md->data && ((le32_to_cpu(md->status)) &
+ MUSYCC_TX_OWNED)) {
+ ch->ch_start_tx = 0;
+
+#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);
+ }
+#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;
+
+ if (ledval == 0)
+ led = 0;
+ else
+ led |= ledval;
+
+ /* RLD DEBUG TRANHANG */
+ pci_write_32((u_int32_t *) &pi->up->cpldbase->leds, led);
+}
+
+
+#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
+ /* allow this next request */
+ SD_SEM_GIVE(&pi->sr_sem_busy);
+ musycc_serv_req(pi, SR_NOOP);
+ /* relock & continue w/ original req */
+ SD_SEM_TAKE(&pi->sr_sem_busy, "serv");
+ } 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
+ /* allow this next request */
+ SD_SEM_GIVE(&pi->sr_sem_busy);
+ 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."
+ */
+
+ /* adhere to write timing imposition */
+ r = pci_read_32((u_int32_t *) &pi->reg->srd);
+
+
+ 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));
+ /* this delay helps reduce reissue counts
+ * (reason not yet researched)
+ */
+ OS_uwait_dummy();
+ 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();
+ /* sleep until SACK interrupt occurs */
+ SD_SEM_TAKE(&pi->sr_sem_wait, "sakack");
+ }
+ 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 */
+ /* make tslot unavailable for this mode */
+ pi->tsm[i] = 0xff;
+ else
+ /* make tslot available for assignment */
+ pi->tsm[i] = 0x00;
+ 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 = kzalloc((INT_QUEUE_SIZE + 1) * sizeof(u_int32_t),
+ GFP_KERNEL | GFP_DMA);
+ if (!regaddr)
+ return -ENOMEM;
+ ci->iqd_p_saved = regaddr; /* save orig value for free's usage */
+ /* this calculates closest boundary */
+ ci->iqd_p = (u_int32_t *) ((unsigned long)(regaddr + INT_QUEUE_BOUNDARY - 1) &
+ (~(INT_QUEUE_BOUNDARY - 1)));
+
+ 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 = kzalloc(sizeof(struct musycc_groupr) + GROUP_BOUNDARY,
+ GFP_KERNEL | GFP_DMA);
+ if (!regaddr) {
+ for (gchan = 0; gchan < i; gchan++) {
+ pi = &ci->port[gchan];
+ kfree(pi->reg);
+ pi->reg = NULL;
+ }
+ return -ENOMEM;
+ }
+ pi->regram_saved = regaddr; /* save orig value for free's usage */
+ /* this calculates closest boundary */
+ pi->regram = (struct musycc_groupr *) ((unsigned long)(regaddr + GROUP_BOUNDARY - 1) &
+ (~(GROUP_BOUNDARY - 1)));
+ }
+
+ /* 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;
+
+ volatile u_int32_t status;
+
+ ch = pi->chan[gchan];
+ if (!ch || 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 || !ch->mdt)
+ return; /* note: mdt==0 implies a malloc()
+ * failure w/in chan_up() routine */
+
+ 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++)
+ /* use call to avoid optimization
+ * removal of dummy delay */
+ OS_uwait_dummy();
+ 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) {
+ /* 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
+ OS_mem_token_free_irq(md->mem_token);
+ md->mem_token = NULL;
+ }
+ 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;
+ /* re-enable to catch flow controlled channel */
+ sd_enable_xmit(ch->user);
+ }
+ }
+#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();
+}
+
+
+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 || 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)
+ 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) {
+ {
+ m2 = OS_mem_token_alloc(cxt1e1_max_mru);
+ if (m2) {
+ /* 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.
+ */
+ 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++)
+ /* use call to avoid optimization
+ * removal of dummy delay
+ */
+ OS_uwait_dummy();
+ FLUSH_MEM_READ();
+ currInt = le32_to_cpu(ci->iqd_p[headx]);
+ if (readCount++ > 20)
+ break;
+ }
+
+ /* catch failure of Bug Fix checking */
+ if ((currInt == badInt) || (currInt == badInt2)) {
+ 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);
+ /* insure wrapness */
+ ci->iqp_headx = (headx + 1) & (INT_QUEUE_SIZE - 1);
+ 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');
+ }
+ /* notice that here we assume 1-1 group - port mapping */
+ pi = &ci->port[group];
+ 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);
+ }
+ /* wake up waiting process */
+ SD_SEM_GIVE(&pi->sr_sem_wait);
+ 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);
+ /*
+ * 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...
+ */
+ 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
+ /* problem = ONR on HDLC mode */
+ if (ch->p.chan_mode == 2) {
+ 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);
+#ifdef RLD_DEBUG
+ musycc_dump_rxbuffer_ring(ch, 0);
+#endif
+ }
+ }
+ 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);
+ /* insure wrapness */
+ ci->iqp_headx = (headx + 1) & (INT_QUEUE_SIZE - 1);
+ 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 */
+}
+
+#ifdef SBE_PMCC4_ENABLE
+ status_t
+musycc_chan_down(ci_t *dummy, int channum)
+{
+ mpi_t *pi;
+ mch_t *ch;
+ int i, gchan;
+
+ ch = sd_find_chan(dummy, channum);
+ if (!ch)
+ 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)
+ OS_mem_token_free(ch->mdt[i].mem_token);
+
+ for (i = 0; i < ch->rxd_num; i++)
+ if (ch->mdr[i].mem_token)
+ OS_mem_token_free(ch->mdr[i].mem_token);
+
+ kfree(ch->mdr);
+ ch->mdr = NULL;
+ ch->rxd_num = 0;
+ kfree(ch->mdt);
+ ch->mdt = NULL;
+ ch->txd_num = 0;
+
+ musycc_update_timeslots(pi);
+ c4_fifo_free(pi, ch->gchan);
+
+ pi->openchans--;
+ return 0;
+}
+#endif
+
+int
+musycc_start_xmit(ci_t *ci, int channum, void *mem_token)
+{
+ mch_t *ch;
+ struct mdesc *md;
+ void *m2;
+ int txd_need_cnt;
+ u_int32_t len;
+
+ ch = sd_find_chan(ci, channum);
+ if (!ch)
+ return -ENOENT;
+
+ /* full interrupt processing available */
+ if (ci->state != C_RUNNING)
+ return -EINVAL;
+ if (ch->state != UP)
+ return -EINVAL;
+
+ /* how else to flag unwritable state ? */
+ if (!(ch->status & TX_ENABLED))
+ return -EROFS;
+
+#ifdef RLD_TRANS_DEBUG
+ 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;
+ }
+
+ /************************************************************/
+ /** 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);
+ 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 */
+ /* transfer ownership from HOST to MUSYCC */
+ u |= MUSYCC_TX_OWNED;
+
+ 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) {
+ u |= (PADFILL_ENABLE | (ch->p.pad_fill_count << EXTRA_FLAGS));
+ }
+ /* Fill in mds on last segment, others set ZERO
+ * so that entire token is removed ONLY when ALL
+ * segments have been transmitted.
+ */
+ md->mem_token = len ? NULL : mem_token;
+
+ 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 00000000000..56fb42f0f64
--- /dev/null
+++ b/drivers/staging/cxt1e1/musycc.h
@@ -0,0 +1,427 @@
+#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 <linux/types.h>
+
+#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/pmc93x6_eeprom.c b/drivers/staging/cxt1e1/pmc93x6_eeprom.c
new file mode 100644
index 00000000000..ba588f1b211
--- /dev/null
+++ b/drivers/staging/cxt1e1/pmc93x6_eeprom.c
@@ -0,0 +1,532 @@
+/* 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 <linux/types.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "pmcc4.h"
+#include "sbe_promformat.h"
+#include "pmc93x6_eeprom.h"
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#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
+ *------------------------------------------------------------------------
+ */
+
+static u8 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)
+{
+ /* Used to build by powers to 2 */
+ long half;
+ 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.
+ */
+
+static void eeprom_put_byte(long addr, long data, int count)
+{
+ u_int32_t output;
+
+ while (--count >= 0) {
+ /* Get next data bit */
+ output = (data & EPROM_ACTIVE_OUT_BIT) ? 1 : 0;
+ /* Add Chip Select */
+ output |= EPROM_ENCS;
+ data >>= 1;
+
+ eeprom_delay();
+ /* Output it */
+ pci_write_32((u_int32_t *) addr, output);
+ }
+}
+
+/*------------------------------------------------------------------------
+ * eeprom_get_byte - Receive a byte from the EEPROM serially
+ *------------------------------------------------------------------------
+ *
+ * Given the PCI address, this routine serially fetches the data
+ * from the EEPROM.
+ */
+
+static 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);
+
+ /* Shift data over */
+ data <<= 1;
+ 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);
+
+ /* this removes Chip Select from EEPROM */
+ pci_write_32((u_int32_t *) addr, 0);
+}
+
+/*------------------------------------------------------------------------
+ * 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);
+
+ /* this removes Chip Select from EEPROM */
+ pci_write_32((u_int32_t *) addr, 0);
+}
+
+/*------------------------------------------------------------------------
+ * 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.
+ */
+
+static u_int32_t pmc_eeprom_read(long addr, long mem_offset)
+{
+ /* Data from chip */
+ u_int32_t data;
+
+ if (!ByteReverseBuilt)
+ BuildByteReverse();
+
+ /* Reverse address */
+ mem_offset = ByteReverse[0x7F & mem_offset];
+
+ /*
+ * 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.
+ */
+
+ /* Shift for command */
+ mem_offset <<= 2;
+ /* Add command */
+ mem_offset |= EPROM_READ;
+
+ /* Output chip address */
+ eeprom_put_byte(addr, mem_offset, SIZE_ADDR_OP);
+
+ /* Read chip data */
+ data = eeprom_get_byte(addr);
+
+ /* Remove Chip Select from EEPROM */
+ pci_write_32((u_int32_t *) addr, 0);
+
+ 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.
+ */
+
+static int pmc_eeprom_write(long addr, long mem_offset, u_int32_t data)
+{
+ u_int32_t temp;
+ int count;
+
+ if (!ByteReverseBuilt)
+ BuildByteReverse();
+
+ /* Reverse address */
+ mem_offset = ByteReverse[0x7F & mem_offset];
+
+ /*
+ * 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.
+ */
+
+ /* Shift for command */
+ mem_offset <<= 2;
+ /* Add command */
+ mem_offset |= EPROM_WRITE;
+
+ /* Output chip address */
+ eeprom_put_byte(addr, mem_offset, SIZE_ADDR_OP);
+
+ /* Reverse data */
+ data = ByteReverse[0xFF & data];
+ /* Output chip data */
+ eeprom_put_byte(addr, data, NUM_OF_BITS);
+
+ /* Remove Chip Select from EEPROM */
+ pci_write_32((u_int32_t *) addr, 0);
+
+/*
+ * 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.
+ */
+ /* Re-enable Chip Select */
+ pci_write_32((u_int32_t *) addr, EPROM_ENCS);
+ /* discard first read */
+ temp = pci_read_32((u_int32_t *) addr);
+ 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) {
+ /* Remove Chip Select from EEPROM */
+ pci_write_32((u_int32_t *) addr, 0);
+ 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
+ *------------------------------------------------------------------------
+ */
+
+static 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
+ *------------------------------------------------------------------------
+ */
+
+static 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
+ *------------------------------------------------------------------------
+ */
+
+static u_int32_t pmcCalcCrc_T01(void *bufp)
+{
+ FLD_TYPE2 *buf = bufp;
+ /* CRC of the structure */
+ u_int32_t crc;
+
+ /* 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
+ /* RLD DEBUG */
+ pr_info("sbeCrc: crc 1 calculated as %08x\n", crc);
+#endif
+ return ~crc;
+}
+
+static u_int32_t pmcCalcCrc_T02(void *bufp)
+{
+ FLD_TYPE2 *buf = bufp;
+ /* CRC of the structure */
+ u_int32_t crc;
+
+ /* 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
+ /* RLD DEBUG */
+ pr_info("sbeCrc: crc 2 calculated as %08x\n", crc);
+#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)
+{
+ /* Memory image of structure */
+ PROMFORMAT buffer;
+ /* CRC of structure */
+ u_int32_t crc;
+ time_t createTime;
+
+ createTime = get_seconds();
+
+ /* use template data */
+ memcpy(&buffer.fldType2, mfg_template, sizeof(buffer.fldType2));
+
+ /* 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;
+ /* CRC read from EEPROM */
+ u_int32_t crc1, crc2;
+
+ /* Retrieve contents of CRC field */
+ crc1 = pmcGetBuffValue(&buf1->Crc32[0], sizeof(buf1->Crc32));
+#ifdef EEPROM_TYPE_DEBUG
+ /* RLD DEBUG */
+ pr_info("EEPROM: chksum 1 reads as %08x\n", crc1);
+#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
+ /* RLD DEBUG */
+ pr_info("EEPROM: chksum 2 reads as %08x\n", crc2);
+#endif
+ if ((buf2->type == PROM_FORMAT_TYPE2) &&
+ (pmcCalcCrc_T02((void *)buf2) == crc2))
+ return PROM_FORMAT_TYPE2; /* checksum type 2 verified */
+
+ /* failed to validate */
+ return PROM_FORMAT_Unk;
+}
diff --git a/drivers/staging/cxt1e1/pmc93x6_eeprom.h b/drivers/staging/cxt1e1/pmc93x6_eeprom.h
new file mode 100644
index 00000000000..96c48cb8326
--- /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 <linux/types.h>
+
+#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 00000000000..b4b5e5ad791
--- /dev/null
+++ b/drivers/staging/cxt1e1/pmcc4.h
@@ -0,0 +1,108 @@
+#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 <linux/types.h>
+
+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_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 00000000000..a51209bc527
--- /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 <linux/types.h>
+
+/********************************/
+/* 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 00000000000..83ceae4324b
--- /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 00000000000..76bebdd18b3
--- /dev/null
+++ b/drivers/staging/cxt1e1/pmcc4_drv.c
@@ -0,0 +1,1613 @@
+/*-----------------------------------------------------------------------------
+ * 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 <linux/types.h>
+#include "pmcc4_sysdep.h"
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h> /* include for timer */
+#include <linux/timer.h> /* include for timer */
+#include <linux/hdlc.h>
+#include <linux/io.h>
+
+#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"
+
+#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 = NULL;
+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++)
+ {
+ ch = ci->port[portnum].chan[gchan];
+ if (ch) {
+ if ((ch->state != UNASSIGNED) &&
+ (ch->channum == channum))
+ return ch;
+ }
+ }
+ return NULL;
+}
+
+/***
+ * 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.
+ */
+
+ struct s_comet_reg *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 <next> from upcoming <free> */
+ 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])
+ kfree(pi->chan[j]); /* free mch_t struct */
+ }
+ kfree(pi->regram_saved);
+ }
+ kfree(ci->iqd_p_saved);
+ 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)
+{
+ struct s_comet_reg *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 = (struct s_comet_reg *) ((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 = kzalloc(sizeof(mch_t), GFP_KERNEL | GFP_DMA);
+ 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 */
+ ret = musycc_init(ci);
+ if (ret != 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)
+{
+ struct s_comet_reg *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)
+{
+ struct s_comet_reg *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 <offset> ioctl
+ * settings are aligned with the <reg> 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 = NULL; /* 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;
+
+ ret = c4_wq_port_init(pi);
+ if (ret) /* 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;
+
+ ret = c4_wk_chan_init(pi, ch);
+ if (ret)
+ return ret;
+ }
+
+ /* save off interface assignments which bound a board */
+ if (!ci->first_if) /* 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;
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ 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;
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ 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;
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ 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;
+
+ ret = musycc_chan_down((ci_t *)0, channum);
+ if (ret)
+ return ret;
+ ret = c4_chan_up(ch->up->up, channum);
+ if (ret)
+ 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;
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ return -ENOENT;
+
+ *p = ch->p;
+ return 0;
+}
+
+status_t
+c4_get_chan_stats (int channum, struct sbecom_chan_stats *p)
+{
+ mch_t *ch;
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ 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 */
+
+ ch = c4_find_chan(channum);
+ if (!ch)
+ 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 = kzalloc(sizeof(struct mdesc) * rxnum, GFP_KERNEL | GFP_DMA);
+ ch->mdt = kzalloc(sizeof(struct mdesc) * txnum, GFP_KERNEL | GFP_DMA);
+ 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));
+
+ m = OS_mem_token_alloc(cxt1e1_max_mru);
+ if (!m) {
+ 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 = NULL;
+ 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 <delayed>\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);
+ }
+ kfree(ch->mdt);
+ ch->mdt = NULL;
+ ch->txd_num = 0;
+ kfree(ch->mdr);
+ ch->mdr = NULL;
+ 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, "<NULL>");
+ 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, "<NULL>");
+
+ 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;
+
+ dev = getuserbychan(iip->channum);
+ if (!dev)
+ return -ENOENT;
+
+ np = dev->name;
+ strncpy (iip->iname, np, CHNM_STRLEN - 1);
+ iip->iname[CHNM_STRLEN - 1] = '\0';
+ 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 00000000000..56a1ee39be1
--- /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 00000000000..451f12f5b04
--- /dev/null
+++ b/drivers/staging/cxt1e1/pmcc4_private.h
@@ -0,0 +1,295 @@
+#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 <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h> /* support for tasklets */
+#include <linux/timer.h> /* support for timer */
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#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. */
+ struct s_comet_reg *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 */
+ 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];
+ void __iomem *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 00000000000..2916c2cb13f
--- /dev/null
+++ b/drivers/staging/cxt1e1/pmcc4_sysdep.h
@@ -0,0 +1,63 @@
+#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, <user> 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_ ***/
+extern int cxt1e1_log_level;
diff --git a/drivers/staging/cxt1e1/sbe_bid.h b/drivers/staging/cxt1e1/sbe_bid.h
new file mode 100644
index 00000000000..abc2e55f62f
--- /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 00000000000..aad411d185f
--- /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 <SERIAL NUM> <Create TIME> <Heatrun TIME>
+ * 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 <SERIAL NUM>
+ * 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
+ * --------------------------------------------------------------------------
+ * <Create TIME> <Heatrun TIME> 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 00000000000..f5835c29ef3
--- /dev/null
+++ b/drivers/staging/cxt1e1/sbecom_inline_linux.h
@@ -0,0 +1,193 @@
+#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 <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h> /* resolves kmalloc references */
+#include <linux/skbuff.h> /* resolves skb references */
+#include <linux/netdevice.h> /* resolves dev_kree_skb_any */
+#include <asm/byteorder.h> /* 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
+ */
+
+/****************/
+/* 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 NULL;
+ }
+ 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 NULL;
+}
+
+
+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);
+ 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 00000000000..a51780f6048
--- /dev/null
+++ b/drivers/staging/cxt1e1/sbecrc.c
@@ -0,0 +1,133 @@
+/* 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 <linux/types.h>
+#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 = NULL;
+ 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 = kzalloc(CRC_TABLE_ENTRIES * sizeof(u_int32_t),
+ GFP_KERNEL);
+ if (!tbl) {
+ *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;
+ 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 00000000000..97c5c6e7e29
--- /dev/null
+++ b/drivers/staging/cxt1e1/sbeid.c
@@ -0,0 +1,205 @@
+/* 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 <linux/types.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "libsbew.h"
+#include "pmcc4_private.h"
+#include "pmcc4.h"
+#include "sbe_bid.h"
+
+char *
+sbeid_get_bdname(ci_t *ci)
+{
+ char *np = NULL;
+
+ switch (ci->brd_id) {
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1):
+ np = "wanPTMC-256T3 <E1>";
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1):
+ np = "wanPTMC-256T3 <T1>";
+ 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 = "<unknown>"; ***/
+ 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
+ * <bid> from the board's EEPROM. Assume a PCI board and set
+ * <hdsbid> 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 = "<unknown>"; ***/
+ 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 = "<unknown>"; ***/
+ 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 00000000000..1c2e52e8b5f
--- /dev/null
+++ b/drivers/staging/cxt1e1/sbeproc.c
@@ -0,0 +1,219 @@
+/* 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 <linux/types.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/sched.h>
+#include <asm/uaccess.h>
+#include "pmcc4_sysdep.h"
+#include "sbecom_inline_linux.h"
+#include "pmcc4_private.h"
+#include "sbeproc.h"
+
+extern void sbecom_get_brdinfo(ci_t *, struct sbe_brd_info *, u_int8_t *);
+extern struct s_hdw_info hdw_info[MAX_BOARDS];
+
+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 void sbecom_proc_get_brdinfo(ci_t *ci, struct sbe_brd_info *bip)
+{
+ hdw_info_t *hi = &hdw_info[ci->brdno];
+ u_int8_t *bsn = NULL;
+
+ 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);
+
+ pr_devel(">> sbecom_get_brdinfo: returned, first_if %p <%s> last_if %p <%s>\n",
+ bip->first_iname, bip->first_iname,
+ bip->last_iname, bip->last_iname);
+}
+
+/*
+ * Describe the driver state through /proc
+ */
+static int sbecom_proc_get_sbe_info(struct seq_file *m, void *v)
+{
+ ci_t *ci = m->private;
+ char *spd;
+ struct sbe_brd_info *bip;
+
+ bip = kzalloc(sizeof(struct sbe_brd_info), GFP_KERNEL | GFP_DMA);
+ if (!bip)
+ return -ENOMEM;
+
+ pr_devel(">> sbecom_proc_get_sbe_info: entered\n");
+
+ sbecom_proc_get_brdinfo(ci, bip);
+
+ seq_puts(m, "Board Type: ");
+ switch (bip->brd_id) {
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T3):
+ seq_puts(m, "wanPMC-C1T3");
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1):
+ seq_puts(m, "wanPTMC-256T3 <E1>");
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1):
+ seq_puts(m, "wanPTMC-256T3 <T1>");
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_C24TE1):
+ seq_puts(m, "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):
+ seq_puts(m, "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):
+ seq_puts(m, "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):
+ seq_puts(m, "wanPMC-C1T1E1");
+ break;
+
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1):
+ seq_puts(m, "wanPCI-C4T1E1");
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1):
+ seq_puts(m, "wanPCI-C2T1E1");
+ break;
+ case SBE_BOARD_ID(PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1):
+ seq_puts(m, "wanPCI-C1T1E1");
+ break;
+
+ default:
+ seq_puts(m, "unknown");
+ break;
+ }
+
+ seq_printf(m, " [%08X]\n", bip->brd_id);
+
+ seq_printf(m, "Board Number: %d\n", bip->brdno);
+ seq_printf(m, "Hardware ID: 0x%02X\n", ci->hdw_bid);
+ seq_printf(m, "Board SN: %06X\n", bip->brd_sn);
+ seq_printf(m, "Board MAC: %pMF\n", bip->brd_mac_addr);
+ seq_printf(m, "Ports: %d\n", ci->max_port);
+ seq_printf(m, "Channels: %d\n", bip->brd_chan_cnt);
+#if 1
+ seq_printf(m, "Interface: %s -> %s\n",
+ bip->first_iname, bip->last_iname);
+#else
+ seq_printf(m, "Interface: <not available> 1st %p lst %p\n",
+ bip->first_iname, 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 = "<not available>";
+ break;
+ }
+ seq_printf(m, "PCI Bus Speed: %s\n", spd);
+
+#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;
+
+ seq_printf(m, "\ncxt1e1_max_mru: %d\n", cxt1e1_max_mru);
+#if 0
+ seq_printf(m, "\nmax_chans_used: %d\n", max_chans_used);
+ seq_printf(m, "cxt1e1_max_mtu: %d\n", cxt1e1_max_mtu);
+#endif
+ seq_printf(m, "max_rxdesc_used: %d\n", max_rxdesc_used);
+ seq_printf(m, "max_txdesc_used: %d\n", max_txdesc_used);
+ }
+#endif
+
+ kfree(bip);
+
+ pr_devel(">> proc_fs: finished\n");
+ return 0;
+}
+
+/*
+ * seq_file wrappers for procfile show routines.
+ */
+static int sbecom_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sbecom_proc_get_sbe_info, PDE_DATA(inode));
+}
+
+static const struct file_operations sbecom_proc_fops = {
+ .open = sbecom_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * Initialize the /proc subsystem for the specific SBE driver
+ */
+int __init sbecom_proc_brd_init(ci_t *ci)
+{
+ char dir[7 + SBE_IFACETMPL_SIZE + 1];
+
+ 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;
+ }
+
+ if (!proc_create_data("info", S_IFREG | S_IRUGO, ci->dir_dev,
+ &sbecom_proc_fops, ci)) {
+ 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;
+}
diff --git a/drivers/staging/cxt1e1/sbeproc.h b/drivers/staging/cxt1e1/sbeproc.h
new file mode 100644
index 00000000000..37285df359c
--- /dev/null
+++ b/drivers/staging/cxt1e1/sbeproc.h
@@ -0,0 +1,42 @@
+#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
+void sbecom_proc_brd_cleanup (ci_t *);
+int __init sbecom_proc_brd_init (ci_t *);
+
+#else
+
+static inline void sbecom_proc_brd_cleanup(ci_t *ci)
+{
+}
+
+static inline int __init sbecom_proc_brd_init(ci_t *ci)
+{
+ return 0;
+}
+
+#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 00000000000..e1e5bfc9ad3
--- /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 <linux/ioctl.h>
+
+#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/dgap/Kconfig b/drivers/staging/dgap/Kconfig
new file mode 100644
index 00000000000..3bbe9e12236
--- /dev/null
+++ b/drivers/staging/dgap/Kconfig
@@ -0,0 +1,6 @@
+config DGAP
+ tristate "Digi EPCA PCI products"
+ default n
+ depends on TTY && HAS_IOMEM
+ ---help---
+ Driver for the Digi International EPCA PCI based product line
diff --git a/drivers/staging/dgap/Makefile b/drivers/staging/dgap/Makefile
new file mode 100644
index 00000000000..0063d044ca7
--- /dev/null
+++ b/drivers/staging/dgap/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DGAP) += dgap.o
diff --git a/drivers/staging/dgap/dgap.c b/drivers/staging/dgap/dgap.c
new file mode 100644
index 00000000000..170d6f3e422
--- /dev/null
+++ b/drivers/staging/dgap/dgap.c
@@ -0,0 +1,7499 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ * In the original out of kernel Digi dgap driver, firmware
+ * loading was done via user land to driver handshaking.
+ *
+ * For cards that support a concentrator (port expander),
+ * I believe the concentrator its self told the card which
+ * concentrator is actually attached and then that info
+ * was used to tell user land which concentrator firmware
+ * image was to be downloaded. I think even the BIOS or
+ * FEP images required could change with the connection
+ * of a particular concentrator.
+ *
+ * Since I have no access to any of these cards or
+ * concentrators, I cannot put the correct concentrator
+ * firmware file names into the firmware_info structure
+ * as is now done for the BIOS and FEP images.
+ *
+ * I think, but am not certain, that the cards supporting
+ * concentrators will function without them. So support
+ * of these cards has been left in this driver.
+ *
+ * In order to fully support those cards, they would
+ * either have to be acquired for dissection or maybe
+ * Digi International could provide some assistance.
+ */
+#undef DIGI_CONCENTRATORS_SUPPORTED
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h> /* For udelay */
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_reg.h>
+#include <linux/io.h> /* For read[bwl]/write[bwl] */
+
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include <linux/firmware.h>
+
+#include "dgap.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Digi International, http://www.digi.com");
+MODULE_DESCRIPTION("Driver for the Digi International EPCA PCI based product line");
+MODULE_SUPPORTED_DEVICE("dgap");
+
+static int dgap_start(void);
+static void dgap_init_globals(void);
+static int dgap_found_board(struct pci_dev *pdev, int id);
+static void dgap_cleanup_board(struct board_t *brd);
+static void dgap_poll_handler(ulong dummy);
+static int dgap_init_pci(void);
+static int dgap_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
+static void dgap_remove_one(struct pci_dev *dev);
+static int dgap_probe1(struct pci_dev *pdev, int card_type);
+static int dgap_do_remap(struct board_t *brd);
+static irqreturn_t dgap_intr(int irq, void *voidbrd);
+
+static int dgap_tty_open(struct tty_struct *tty, struct file *file);
+static void dgap_tty_close(struct tty_struct *tty, struct file *file);
+static int dgap_block_til_ready(struct tty_struct *tty, struct file *file,
+ struct channel_t *ch);
+static int dgap_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg);
+static int dgap_tty_digigeta(struct tty_struct *tty,
+ struct digi_t __user *retinfo);
+static int dgap_tty_digiseta(struct tty_struct *tty,
+ struct digi_t __user *new_info);
+static int dgap_tty_digigetedelay(struct tty_struct *tty, int __user *retinfo);
+static int dgap_tty_digisetedelay(struct tty_struct *tty, int __user *new_info);
+static int dgap_tty_write_room(struct tty_struct *tty);
+static int dgap_tty_chars_in_buffer(struct tty_struct *tty);
+static void dgap_tty_start(struct tty_struct *tty);
+static void dgap_tty_stop(struct tty_struct *tty);
+static void dgap_tty_throttle(struct tty_struct *tty);
+static void dgap_tty_unthrottle(struct tty_struct *tty);
+static void dgap_tty_flush_chars(struct tty_struct *tty);
+static void dgap_tty_flush_buffer(struct tty_struct *tty);
+static void dgap_tty_hangup(struct tty_struct *tty);
+static int dgap_wait_for_drain(struct tty_struct *tty);
+static int dgap_set_modem_info(struct tty_struct *tty, unsigned int command,
+ unsigned int __user *value);
+static int dgap_get_modem_info(struct channel_t *ch,
+ unsigned int __user *value);
+static int dgap_tty_digisetcustombaud(struct tty_struct *tty,
+ int __user *new_info);
+static int dgap_tty_digigetcustombaud(struct tty_struct *tty,
+ int __user *retinfo);
+static int dgap_tty_tiocmget(struct tty_struct *tty);
+static int dgap_tty_tiocmset(struct tty_struct *tty, unsigned int set,
+ unsigned int clear);
+static int dgap_tty_send_break(struct tty_struct *tty, int msec);
+static void dgap_tty_wait_until_sent(struct tty_struct *tty, int timeout);
+static int dgap_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int count);
+static void dgap_tty_set_termios(struct tty_struct *tty,
+ struct ktermios *old_termios);
+static int dgap_tty_put_char(struct tty_struct *tty, unsigned char c);
+static void dgap_tty_send_xchar(struct tty_struct *tty, char ch);
+
+static int dgap_tty_register(struct board_t *brd);
+static int dgap_tty_init(struct board_t *);
+static void dgap_tty_uninit(struct board_t *);
+static void dgap_carrier(struct channel_t *ch);
+static void dgap_input(struct channel_t *ch);
+
+/*
+ * Our function prototypes from dgap_fep5
+ */
+static void dgap_cmdw_ext(struct channel_t *ch, u16 cmd, u16 word, uint ncmds);
+static int dgap_event(struct board_t *bd);
+
+static void dgap_poll_tasklet(unsigned long data);
+static void dgap_cmdb(struct channel_t *ch, u8 cmd, u8 byte1,
+ u8 byte2, uint ncmds);
+static void dgap_cmdw(struct channel_t *ch, u8 cmd, u16 word, uint ncmds);
+static void dgap_wmove(struct channel_t *ch, char *buf, uint cnt);
+static int dgap_param(struct tty_struct *tty);
+static void dgap_parity_scan(struct channel_t *ch, unsigned char *cbuf,
+ unsigned char *fbuf, int *len);
+static uint dgap_get_custom_baud(struct channel_t *ch);
+static void dgap_firmware_reset_port(struct channel_t *ch);
+
+/*
+ * Function prototypes from dgap_parse.c.
+ */
+static int dgap_gettok(char **in, struct cnode *p);
+static char *dgap_getword(char **in);
+static struct cnode *dgap_newnode(int t);
+static int dgap_checknode(struct cnode *p);
+static void dgap_err(char *s);
+
+/*
+ * Function prototypes from dgap_sysfs.h
+ */
+struct board_t;
+struct channel_t;
+struct un_t;
+struct pci_driver;
+struct class_device;
+
+static void dgap_create_ports_sysfiles(struct board_t *bd);
+static void dgap_remove_ports_sysfiles(struct board_t *bd);
+
+static int dgap_create_driver_sysfiles(struct pci_driver *);
+static void dgap_remove_driver_sysfiles(struct pci_driver *);
+
+static void dgap_create_tty_sysfs(struct un_t *un, struct device *c);
+static void dgap_remove_tty_sysfs(struct device *c);
+
+/*
+ * Function prototypes from dgap_parse.h
+ */
+static int dgap_parsefile(char **in, int remove);
+static struct cnode *dgap_find_config(int type, int bus, int slot);
+static uint dgap_config_get_num_prts(struct board_t *bd);
+static char *dgap_create_config_string(struct board_t *bd, char *string);
+static uint dgap_config_get_useintr(struct board_t *bd);
+static uint dgap_config_get_altpin(struct board_t *bd);
+
+static int dgap_ms_sleep(ulong ms);
+static void dgap_do_bios_load(struct board_t *brd, const u8 *ubios, int len);
+static void dgap_do_fep_load(struct board_t *brd, const u8 *ufep, int len);
+#ifdef DIGI_CONCENTRATORS_SUPPORTED
+static void dgap_do_conc_load(struct board_t *brd, u8 *uaddr, int len);
+#endif
+static int dgap_after_config_loaded(int board);
+static int dgap_finalize_board_init(struct board_t *brd);
+
+static void dgap_get_vpd(struct board_t *brd);
+static void dgap_do_reset_board(struct board_t *brd);
+static int dgap_test_bios(struct board_t *brd);
+static int dgap_test_fep(struct board_t *brd);
+static int dgap_tty_register_ports(struct board_t *brd);
+static int dgap_firmware_load(struct pci_dev *pdev, int card_type);
+
+static void dgap_cleanup_module(void);
+
+module_exit(dgap_cleanup_module);
+
+/*
+ * File operations permitted on Control/Management major.
+ */
+static const struct file_operations dgap_board_fops = {
+ .owner = THIS_MODULE,
+};
+
+static uint dgap_numboards;
+static struct board_t *dgap_board[MAXBOARDS];
+static ulong dgap_poll_counter;
+static char *dgap_config_buf;
+static int dgap_driver_state = DRIVER_INITIALIZED;
+static wait_queue_head_t dgap_dl_wait;
+static int dgap_poll_tick = 20; /* Poll interval - 20 ms */
+
+static struct class *dgap_class;
+
+static struct board_t *dgap_boards_by_major[256];
+static uint dgap_count = 500;
+
+/*
+ * Poller stuff
+ */
+static DEFINE_SPINLOCK(dgap_poll_lock); /* Poll scheduling lock */
+static ulong dgap_poll_time; /* Time of next poll */
+static uint dgap_poll_stop; /* Used to tell poller to stop */
+static struct timer_list dgap_poll_timer;
+
+/*
+ SUPPORTED PRODUCTS
+
+ Card Model Number of Ports Interface
+ ----------------------------------------------------------------
+ Acceleport Xem 4 - 64 (EIA232 & EIA422)
+ Acceleport Xr 4 & 8 (EIA232)
+ Acceleport Xr 920 4 & 8 (EIA232)
+ Acceleport C/X 8 - 128 (EIA232)
+ Acceleport EPC/X 8 - 224 (EIA232)
+ Acceleport Xr/422 4 & 8 (EIA422)
+ Acceleport 2r/920 2 (EIA232)
+ Acceleport 4r/920 4 (EIA232)
+ Acceleport 8r/920 8 (EIA232)
+
+ IBM 8-Port Asynchronous PCI Adapter (EIA232)
+ IBM 128-Port Asynchronous PCI Adapter (EIA232 & EIA422)
+*/
+
+static struct pci_device_id dgap_pci_tbl[] = {
+ { DIGI_VID, PCI_DEV_XEM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { DIGI_VID, PCI_DEV_CX_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 },
+ { DIGI_VID, PCI_DEV_CX_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2 },
+ { DIGI_VID, PCI_DEV_EPCJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3 },
+ { DIGI_VID, PCI_DEV_920_2_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 },
+ { DIGI_VID, PCI_DEV_920_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 },
+ { DIGI_VID, PCI_DEV_920_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6 },
+ { DIGI_VID, PCI_DEV_XR_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 7 },
+ { DIGI_VID, PCI_DEV_XRJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 8 },
+ { DIGI_VID, PCI_DEV_XR_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 9 },
+ { DIGI_VID, PCI_DEV_XR_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 10 },
+ { DIGI_VID, PCI_DEV_XR_SAIP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 11 },
+ { DIGI_VID, PCI_DEV_XR_BULL_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 12 },
+ { DIGI_VID, PCI_DEV_920_8_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 13 },
+ { DIGI_VID, PCI_DEV_XEM_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 14 },
+ {0,} /* 0 terminated list. */
+};
+MODULE_DEVICE_TABLE(pci, dgap_pci_tbl);
+
+/*
+ * A generic list of Product names, PCI Vendor ID, and PCI Device ID.
+ */
+struct board_id {
+ uint config_type;
+ u8 *name;
+ uint maxports;
+ uint dpatype;
+};
+
+static struct board_id dgap_ids[] = {
+ { PPCM, PCI_DEV_XEM_NAME, 64, (T_PCXM|T_PCLITE|T_PCIBUS) },
+ { PCX, PCI_DEV_CX_NAME, 128, (T_CX|T_PCIBUS) },
+ { PCX, PCI_DEV_CX_IBM_NAME, 128, (T_CX|T_PCIBUS) },
+ { PEPC, PCI_DEV_EPCJ_NAME, 224, (T_EPC|T_PCIBUS) },
+ { APORT2_920P, PCI_DEV_920_2_NAME, 2, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { APORT4_920P, PCI_DEV_920_4_NAME, 4, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { APORT8_920P, PCI_DEV_920_8_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XR_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XRJ_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XR_422_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XR_IBM_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XR_SAIP_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PAPORT8, PCI_DEV_XR_BULL_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { APORT8_920P, PCI_DEV_920_8_HP_NAME, 8, (T_PCXR|T_PCLITE|T_PCIBUS) },
+ { PPCM, PCI_DEV_XEM_HP_NAME, 64, (T_PCXM|T_PCLITE|T_PCIBUS) },
+ {0,} /* 0 terminated list. */
+};
+
+static struct pci_driver dgap_driver = {
+ .name = "dgap",
+ .probe = dgap_init_one,
+ .id_table = dgap_pci_tbl,
+ .remove = dgap_remove_one,
+};
+
+struct firmware_info {
+ u8 *conf_name; /* dgap.conf */
+ u8 *bios_name; /* BIOS filename */
+ u8 *fep_name; /* FEP filename */
+ u8 *con_name; /* Concentrator filename FIXME*/
+ int num; /* sequence number */
+};
+
+/*
+ * Firmware - BIOS, FEP, and CONC filenames
+ */
+static struct firmware_info fw_info[] = {
+ { "dgap/dgap.conf", "dgap/sxbios.bin", "dgap/sxfep.bin", NULL, 0 },
+ { "dgap/dgap.conf", "dgap/cxpbios.bin", "dgap/cxpfep.bin", NULL, 1 },
+ { "dgap/dgap.conf", "dgap/cxpbios.bin", "dgap/cxpfep.bin", NULL, 2 },
+ { "dgap/dgap.conf", "dgap/pcibios.bin", "dgap/pcifep.bin", NULL, 3 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 4 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 5 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 6 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 7 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 8 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 9 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 10 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 11 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 12 },
+ { "dgap/dgap.conf", "dgap/xrbios.bin", "dgap/xrfep.bin", NULL, 13 },
+ { "dgap/dgap.conf", "dgap/sxbios.bin", "dgap/sxfep.bin", NULL, 14 },
+ {NULL,}
+};
+
+/*
+ * Default transparent print information.
+ */
+static struct digi_t dgap_digi_init = {
+ .digi_flags = DIGI_COOK, /* Flags */
+ .digi_maxcps = 100, /* Max CPS */
+ .digi_maxchar = 50, /* Max chars in print queue */
+ .digi_bufsize = 100, /* Printer buffer size */
+ .digi_onlen = 4, /* size of printer on string */
+ .digi_offlen = 4, /* size of printer off string */
+ .digi_onstr = "\033[5i", /* ANSI printer on string ] */
+ .digi_offstr = "\033[4i", /* ANSI printer off string ] */
+ .digi_term = "ansi" /* default terminal type */
+};
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.
+ *
+ * This defines a raw port at 9600 baud, 8 data bits, no parity,
+ * 1 stop bit.
+ */
+
+static struct ktermios dgap_default_termios = {
+ .c_iflag = (DEFAULT_IFLAGS), /* iflags */
+ .c_oflag = (DEFAULT_OFLAGS), /* oflags */
+ .c_cflag = (DEFAULT_CFLAGS), /* cflags */
+ .c_lflag = (DEFAULT_LFLAGS), /* lflags */
+ .c_cc = INIT_C_CC,
+ .c_line = 0,
+};
+
+static const struct tty_operations dgap_tty_ops = {
+ .open = dgap_tty_open,
+ .close = dgap_tty_close,
+ .write = dgap_tty_write,
+ .write_room = dgap_tty_write_room,
+ .flush_buffer = dgap_tty_flush_buffer,
+ .chars_in_buffer = dgap_tty_chars_in_buffer,
+ .flush_chars = dgap_tty_flush_chars,
+ .ioctl = dgap_tty_ioctl,
+ .set_termios = dgap_tty_set_termios,
+ .stop = dgap_tty_stop,
+ .start = dgap_tty_start,
+ .throttle = dgap_tty_throttle,
+ .unthrottle = dgap_tty_unthrottle,
+ .hangup = dgap_tty_hangup,
+ .put_char = dgap_tty_put_char,
+ .tiocmget = dgap_tty_tiocmget,
+ .tiocmset = dgap_tty_tiocmset,
+ .break_ctl = dgap_tty_send_break,
+ .wait_until_sent = dgap_tty_wait_until_sent,
+ .send_xchar = dgap_tty_send_xchar
+};
+
+/*
+ * Our needed internal static variables from dgap_parse.c
+ */
+static struct cnode dgap_head;
+#define MAXCWORD 200
+static char dgap_cword[MAXCWORD];
+
+struct toklist {
+ int token;
+ char *string;
+};
+
+static struct toklist dgap_tlist[] = {
+ { BEGIN, "config_begin" },
+ { END, "config_end" },
+ { BOARD, "board" },
+ { PCX, "Digi_AccelePort_C/X_PCI" },
+ { PEPC, "Digi_AccelePort_EPC/X_PCI" },
+ { PPCM, "Digi_AccelePort_Xem_PCI" },
+ { APORT2_920P, "Digi_AccelePort_2r_920_PCI" },
+ { APORT4_920P, "Digi_AccelePort_4r_920_PCI" },
+ { APORT8_920P, "Digi_AccelePort_8r_920_PCI" },
+ { PAPORT4, "Digi_AccelePort_4r_PCI(EIA-232/RS-422)" },
+ { PAPORT8, "Digi_AccelePort_8r_PCI(EIA-232/RS-422)" },
+ { IO, "io" },
+ { PCIINFO, "pciinfo" },
+ { LINE, "line" },
+ { CONC, "conc" },
+ { CONC, "concentrator" },
+ { CX, "cx" },
+ { CX, "ccon" },
+ { EPC, "epccon" },
+ { EPC, "epc" },
+ { MOD, "module" },
+ { ID, "id" },
+ { STARTO, "start" },
+ { SPEED, "speed" },
+ { CABLE, "cable" },
+ { CONNECT, "connect" },
+ { METHOD, "method" },
+ { STATUS, "status" },
+ { CUSTOM, "Custom" },
+ { BASIC, "Basic" },
+ { MEM, "mem" },
+ { MEM, "memory" },
+ { PORTS, "ports" },
+ { MODEM, "modem" },
+ { NPORTS, "nports" },
+ { TTYN, "ttyname" },
+ { CU, "cuname" },
+ { PRINT, "prname" },
+ { CMAJOR, "major" },
+ { ALTPIN, "altpin" },
+ { USEINTR, "useintr" },
+ { TTSIZ, "ttysize" },
+ { CHSIZ, "chsize" },
+ { BSSIZ, "boardsize" },
+ { UNTSIZ, "schedsize" },
+ { F2SIZ, "f2200size" },
+ { VPSIZ, "vpixsize" },
+ { 0, NULL }
+};
+
+/************************************************************************
+ *
+ * Driver load/unload functions
+ *
+ ************************************************************************/
+
+/*
+ * init_module()
+ *
+ * Module load. This is where it all starts.
+ */
+static int dgap_init_module(void)
+{
+ int rc;
+
+ pr_info("%s, Digi International Part Number %s\n", DG_NAME, DG_PART);
+
+ rc = dgap_start();
+ if (rc)
+ return rc;
+
+ rc = dgap_init_pci();
+ if (rc)
+ goto err_cleanup;
+
+ rc = dgap_create_driver_sysfiles(&dgap_driver);
+ if (rc)
+ goto err_cleanup;
+
+ dgap_driver_state = DRIVER_READY;
+
+ return 0;
+
+err_cleanup:
+
+ dgap_cleanup_module();
+
+ return rc;
+}
+module_init(dgap_init_module);
+
+/*
+ * Start of driver.
+ */
+static int dgap_start(void)
+{
+ int rc;
+ unsigned long flags;
+ struct device *device;
+
+ /*
+ * make sure that the globals are
+ * init'd before we do anything else
+ */
+ dgap_init_globals();
+
+ dgap_numboards = 0;
+
+ pr_info("For the tools package please visit http://www.digi.com\n");
+
+ /*
+ * Register our base character device into the kernel.
+ */
+
+ /*
+ * Register management/dpa devices
+ */
+ rc = register_chrdev(DIGI_DGAP_MAJOR, "dgap", &dgap_board_fops);
+ if (rc < 0)
+ return rc;
+
+ dgap_class = class_create(THIS_MODULE, "dgap_mgmt");
+ if (IS_ERR(dgap_class)) {
+ rc = PTR_ERR(dgap_class);
+ goto failed_class;
+ }
+
+ device = device_create(dgap_class, NULL,
+ MKDEV(DIGI_DGAP_MAJOR, 0),
+ NULL, "dgap_mgmt");
+ if (IS_ERR(device)) {
+ rc = PTR_ERR(device);
+ goto failed_device;
+ }
+
+ /* Start the poller */
+ spin_lock_irqsave(&dgap_poll_lock, flags);
+ init_timer(&dgap_poll_timer);
+ dgap_poll_timer.function = dgap_poll_handler;
+ dgap_poll_timer.data = 0;
+ dgap_poll_time = jiffies + dgap_jiffies_from_ms(dgap_poll_tick);
+ dgap_poll_timer.expires = dgap_poll_time;
+ spin_unlock_irqrestore(&dgap_poll_lock, flags);
+
+ add_timer(&dgap_poll_timer);
+
+ return rc;
+
+failed_device:
+ class_destroy(dgap_class);
+failed_class:
+ unregister_chrdev(DIGI_DGAP_MAJOR, "dgap");
+ return rc;
+}
+
+/*
+ * Register pci driver, and return how many boards we have.
+ */
+static int dgap_init_pci(void)
+{
+ return pci_register_driver(&dgap_driver);
+}
+
+static int dgap_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int rc;
+
+ if (dgap_numboards >= MAXBOARDS)
+ return -EPERM;
+
+ rc = pci_enable_device(pdev);
+ if (rc)
+ return -EIO;
+
+ rc = dgap_probe1(pdev, ent->driver_data);
+ if (rc)
+ return rc;
+
+ dgap_numboards++;
+ return dgap_firmware_load(pdev, ent->driver_data);
+}
+
+static int dgap_probe1(struct pci_dev *pdev, int card_type)
+{
+ return dgap_found_board(pdev, card_type);
+}
+
+static void dgap_remove_one(struct pci_dev *dev)
+{
+ /* Do Nothing */
+}
+
+/*
+ * dgap_cleanup_module()
+ *
+ * Module unload. This is where it all ends.
+ */
+static void dgap_cleanup_module(void)
+{
+ int i;
+ ulong lock_flags;
+
+ spin_lock_irqsave(&dgap_poll_lock, lock_flags);
+ dgap_poll_stop = 1;
+ spin_unlock_irqrestore(&dgap_poll_lock, lock_flags);
+
+ /* Turn off poller right away. */
+ del_timer_sync(&dgap_poll_timer);
+
+ dgap_remove_driver_sysfiles(&dgap_driver);
+
+ device_destroy(dgap_class, MKDEV(DIGI_DGAP_MAJOR, 0));
+ class_destroy(dgap_class);
+ unregister_chrdev(DIGI_DGAP_MAJOR, "dgap");
+
+ for (i = 0; i < dgap_numboards; ++i) {
+ dgap_remove_ports_sysfiles(dgap_board[i]);
+ dgap_tty_uninit(dgap_board[i]);
+ dgap_cleanup_board(dgap_board[i]);
+ }
+
+ if (dgap_numboards)
+ pci_unregister_driver(&dgap_driver);
+}
+
+/*
+ * dgap_cleanup_board()
+ *
+ * Free all the memory associated with a board
+ */
+static void dgap_cleanup_board(struct board_t *brd)
+{
+ int i;
+
+ if (!brd || brd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ if (brd->intr_used && brd->irq)
+ free_irq(brd->irq, brd);
+
+ tasklet_kill(&brd->helper_tasklet);
+
+ if (brd->re_map_port) {
+ release_mem_region(brd->membase + 0x200000, 0x200000);
+ iounmap(brd->re_map_port);
+ brd->re_map_port = NULL;
+ }
+
+ if (brd->re_map_membase) {
+ release_mem_region(brd->membase, 0x200000);
+ iounmap(brd->re_map_membase);
+ brd->re_map_membase = NULL;
+ }
+
+ /* Free all allocated channels structs */
+ for (i = 0; i < MAXPORTS ; i++)
+ kfree(brd->channels[i]);
+
+ kfree(brd->flipbuf);
+ kfree(brd->flipflagbuf);
+
+ dgap_board[brd->boardnum] = NULL;
+
+ kfree(brd);
+}
+
+/*
+ * dgap_found_board()
+ *
+ * A board has been found, init it.
+ */
+static int dgap_found_board(struct pci_dev *pdev, int id)
+{
+ struct board_t *brd;
+ unsigned int pci_irq;
+ int i;
+
+ /* get the board structure and prep it */
+ brd = kzalloc(sizeof(struct board_t), GFP_KERNEL);
+ if (!brd)
+ return -ENOMEM;
+
+ dgap_board[dgap_numboards] = brd;
+
+ /* store the info for the board we've found */
+ brd->magic = DGAP_BOARD_MAGIC;
+ brd->boardnum = dgap_numboards;
+ brd->firstminor = 0;
+ brd->vendor = dgap_pci_tbl[id].vendor;
+ brd->device = dgap_pci_tbl[id].device;
+ brd->pdev = pdev;
+ brd->pci_bus = pdev->bus->number;
+ brd->pci_slot = PCI_SLOT(pdev->devfn);
+ brd->name = dgap_ids[id].name;
+ brd->maxports = dgap_ids[id].maxports;
+ brd->type = dgap_ids[id].config_type;
+ brd->dpatype = dgap_ids[id].dpatype;
+ brd->dpastatus = BD_NOFEP;
+ init_waitqueue_head(&brd->state_wait);
+
+ spin_lock_init(&brd->bd_lock);
+
+ brd->runwait = 0;
+ brd->inhibit_poller = FALSE;
+ brd->wait_for_bios = 0;
+ brd->wait_for_fep = 0;
+
+ for (i = 0; i < MAXPORTS; i++)
+ brd->channels[i] = NULL;
+
+ /* store which card & revision we have */
+ pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor);
+ pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice);
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev);
+
+ pci_irq = pdev->irq;
+ brd->irq = pci_irq;
+
+ /* get the PCI Base Address Registers */
+
+ /* Xr Jupiter and EPC use BAR 2 */
+ if (brd->device == PCI_DEV_XRJ_DID || brd->device == PCI_DEV_EPCJ_DID) {
+ brd->membase = pci_resource_start(pdev, 2);
+ brd->membase_end = pci_resource_end(pdev, 2);
+ }
+ /* Everyone else uses BAR 0 */
+ else {
+ brd->membase = pci_resource_start(pdev, 0);
+ brd->membase_end = pci_resource_end(pdev, 0);
+ }
+
+ if (!brd->membase)
+ return -ENODEV;
+
+ if (brd->membase & 1)
+ brd->membase &= ~3;
+ else
+ brd->membase &= ~15;
+
+ /*
+ * On the PCI boards, there is no IO space allocated
+ * The I/O registers will be in the first 3 bytes of the
+ * upper 2MB of the 4MB memory space. The board memory
+ * will be mapped into the low 2MB of the 4MB memory space
+ */
+ brd->port = brd->membase + PCI_IO_OFFSET;
+ brd->port_end = brd->port + PCI_IO_SIZE;
+
+ /*
+ * Special initialization for non-PLX boards
+ */
+ if (brd->device != PCI_DEV_XRJ_DID && brd->device != PCI_DEV_EPCJ_DID) {
+ unsigned short cmd;
+
+ pci_write_config_byte(pdev, 0x40, 0);
+ pci_write_config_byte(pdev, 0x46, 0);
+
+ /* Limit burst length to 2 doubleword transactions */
+ pci_write_config_byte(pdev, 0x42, 1);
+
+ /*
+ * Enable IO and mem if not already done.
+ * This was needed for support on Itanium.
+ */
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ cmd |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+ }
+
+ /* init our poll helper tasklet */
+ tasklet_init(&brd->helper_tasklet, dgap_poll_tasklet,
+ (unsigned long) brd);
+
+ i = dgap_do_remap(brd);
+ if (i)
+ brd->state = BOARD_FAILED;
+
+ pr_info("dgap: board %d: %s (rev %d), irq %ld\n",
+ dgap_numboards, brd->name, brd->rev, brd->irq);
+
+ return 0;
+}
+
+
+static int dgap_finalize_board_init(struct board_t *brd)
+{
+ int rc;
+
+ if (!brd || brd->magic != DGAP_BOARD_MAGIC)
+ return -ENODEV;
+
+ brd->use_interrupts = dgap_config_get_useintr(brd);
+
+ /*
+ * Set up our interrupt handler if we are set to do interrupts.
+ */
+ if (brd->use_interrupts && brd->irq) {
+
+ rc = request_irq(brd->irq, dgap_intr, IRQF_SHARED, "DGAP", brd);
+
+ if (rc)
+ brd->intr_used = 0;
+ else
+ brd->intr_used = 1;
+ } else {
+ brd->intr_used = 0;
+ }
+
+ return 0;
+}
+
+static int dgap_firmware_load(struct pci_dev *pdev, int card_type)
+{
+ struct board_t *brd = dgap_board[dgap_numboards - 1];
+ const struct firmware *fw;
+ char *tmp_ptr;
+ int ret;
+
+ dgap_get_vpd(brd);
+ dgap_do_reset_board(brd);
+
+ if ((fw_info[card_type].conf_name) && !dgap_config_buf) {
+ ret = request_firmware(&fw, fw_info[card_type].conf_name,
+ &pdev->dev);
+ if (ret) {
+ pr_err("dgap: config file %s not found\n",
+ fw_info[card_type].conf_name);
+ return ret;
+ }
+
+ dgap_config_buf = kzalloc(fw->size + 1, GFP_KERNEL);
+ if (!dgap_config_buf) {
+ release_firmware(fw);
+ return -ENOMEM;
+ }
+
+ memcpy(dgap_config_buf, fw->data, fw->size);
+ release_firmware(fw);
+
+ /*
+ * preserve dgap_config_buf
+ * as dgap_parsefile would
+ * otherwise alter it.
+ */
+ tmp_ptr = dgap_config_buf;
+
+ if (dgap_parsefile(&tmp_ptr, TRUE) != 0) {
+ kfree(dgap_config_buf);
+ return -EINVAL;
+ }
+ kfree(dgap_config_buf);
+ }
+
+ ret = dgap_after_config_loaded(brd->boardnum);
+ if (ret)
+ return ret;
+ /*
+ * Match this board to a config the user created for us.
+ */
+ brd->bd_config =
+ dgap_find_config(brd->type, brd->pci_bus, brd->pci_slot);
+
+ /*
+ * Because the 4 port Xr products share the same PCI ID
+ * as the 8 port Xr products, if we receive a NULL config
+ * back, and this is a PAPORT8 board, retry with a
+ * PAPORT4 attempt as well.
+ */
+ if (brd->type == PAPORT8 && !brd->bd_config)
+ brd->bd_config =
+ dgap_find_config(PAPORT4, brd->pci_bus, brd->pci_slot);
+
+ if (!brd->bd_config) {
+ pr_err("dgap: No valid configuration found\n");
+ return -EINVAL;
+ }
+
+ ret = dgap_tty_register(brd);
+ if (ret)
+ return ret;
+
+ ret = dgap_finalize_board_init(brd);
+ if (ret)
+ return ret;
+
+ if (fw_info[card_type].bios_name) {
+ ret = request_firmware(&fw, fw_info[card_type].bios_name,
+ &pdev->dev);
+ if (ret) {
+ pr_err("dgap: bios file %s not found\n",
+ fw_info[card_type].bios_name);
+ return ret;
+ }
+ dgap_do_bios_load(brd, fw->data, fw->size);
+ release_firmware(fw);
+
+ /* Wait for BIOS to test board... */
+ ret = dgap_test_bios(brd);
+ if (ret)
+ return ret;
+ }
+
+ if (fw_info[card_type].fep_name) {
+ ret = request_firmware(&fw, fw_info[card_type].fep_name,
+ &pdev->dev);
+ if (ret) {
+ pr_err("dgap: fep file %s not found\n",
+ fw_info[card_type].fep_name);
+ return ret;
+ }
+ dgap_do_fep_load(brd, fw->data, fw->size);
+ release_firmware(fw);
+
+ /* Wait for FEP to load on board... */
+ ret = dgap_test_fep(brd);
+ if (ret)
+ return ret;
+ }
+
+#ifdef DIGI_CONCENTRATORS_SUPPORTED
+ /*
+ * If this is a CX or EPCX, we need to see if the firmware
+ * is requesting a concentrator image from us.
+ */
+ if ((bd->type == PCX) || (bd->type == PEPC)) {
+ chk_addr = (u16 *) (vaddr + DOWNREQ);
+ /* Nonzero if FEP is requesting concentrator image. */
+ check = readw(chk_addr);
+ vaddr = brd->re_map_membase;
+ }
+
+ if (fw_info[card_type].con_name && check && vaddr) {
+ ret = request_firmware(&fw, fw_info[card_type].con_name,
+ &pdev->dev);
+ if (ret) {
+ pr_err("dgap: conc file %s not found\n",
+ fw_info[card_type].con_name);
+ return ret;
+ }
+ /* Put concentrator firmware loading code here */
+ offset = readw((u16 *) (vaddr + DOWNREQ));
+ memcpy_toio(offset, fw->data, fw->size);
+
+ dgap_do_conc_load(brd, (char *)fw->data, fw->size)
+ release_firmware(fw);
+ }
+#endif
+ /*
+ * Do tty device initialization.
+ */
+ ret = dgap_tty_init(brd);
+ if (ret < 0) {
+ dgap_tty_uninit(brd);
+ return ret;
+ }
+
+ ret = dgap_tty_register_ports(brd);
+ if (ret)
+ return ret;
+
+ brd->state = BOARD_READY;
+ brd->dpastatus = BD_RUNNING;
+
+ return 0;
+}
+
+/*
+ * Remap PCI memory.
+ */
+static int dgap_do_remap(struct board_t *brd)
+{
+ if (!brd || brd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ if (!request_mem_region(brd->membase, 0x200000, "dgap"))
+ return -ENOMEM;
+
+ if (!request_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000,
+ "dgap")) {
+ release_mem_region(brd->membase, 0x200000);
+ return -ENOMEM;
+ }
+
+ brd->re_map_membase = ioremap(brd->membase, 0x200000);
+ if (!brd->re_map_membase) {
+ release_mem_region(brd->membase, 0x200000);
+ release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000);
+ return -ENOMEM;
+ }
+
+ brd->re_map_port = ioremap((brd->membase + PCI_IO_OFFSET), 0x200000);
+ if (!brd->re_map_port) {
+ release_mem_region(brd->membase, 0x200000);
+ release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000);
+ iounmap(brd->re_map_membase);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+*
+* Function:
+*
+* dgap_poll_handler
+*
+* Author:
+*
+* Scott H Kilau
+*
+* Parameters:
+*
+* dummy -- ignored
+*
+* Return Values:
+*
+* none
+*
+* Description:
+*
+* As each timer expires, it determines (a) whether the "transmit"
+* waiter needs to be woken up, and (b) whether the poller needs to
+* be rescheduled.
+*
+******************************************************************************/
+
+static void dgap_poll_handler(ulong dummy)
+{
+ int i;
+ struct board_t *brd;
+ unsigned long lock_flags;
+ ulong new_time;
+
+ dgap_poll_counter++;
+
+ /*
+ * Do not start the board state machine until
+ * driver tells us its up and running, and has
+ * everything it needs.
+ */
+ if (dgap_driver_state != DRIVER_READY)
+ goto schedule_poller;
+
+ /*
+ * If we have just 1 board, or the system is not SMP,
+ * then use the typical old style poller.
+ * Otherwise, use our new tasklet based poller, which should
+ * speed things up for multiple boards.
+ */
+ if ((dgap_numboards == 1) || (num_online_cpus() <= 1)) {
+ for (i = 0; i < dgap_numboards; i++) {
+
+ brd = dgap_board[i];
+
+ if (brd->state == BOARD_FAILED)
+ continue;
+ if (!brd->intr_running)
+ /* Call the real board poller directly */
+ dgap_poll_tasklet((unsigned long) brd);
+ }
+ } else {
+ /*
+ * Go thru each board, kicking off a
+ * tasklet for each if needed
+ */
+ for (i = 0; i < dgap_numboards; i++) {
+ brd = dgap_board[i];
+
+ /*
+ * Attempt to grab the board lock.
+ *
+ * If we can't get it, no big deal, the next poll
+ * will get it. Basically, I just really don't want
+ * to spin in here, because I want to kick off my
+ * tasklets as fast as I can, and then get out the
+ * poller.
+ */
+ if (!spin_trylock(&brd->bd_lock))
+ continue;
+
+ /*
+ * If board is in a failed state, don't bother
+ * scheduling a tasklet
+ */
+ if (brd->state == BOARD_FAILED) {
+ spin_unlock(&brd->bd_lock);
+ continue;
+ }
+
+ /* Schedule a poll helper task */
+ if (!brd->intr_running)
+ tasklet_schedule(&brd->helper_tasklet);
+
+ /*
+ * Can't do DGAP_UNLOCK here, as we don't have
+ * lock_flags because we did a trylock above.
+ */
+ spin_unlock(&brd->bd_lock);
+ }
+ }
+
+schedule_poller:
+
+ /*
+ * Schedule ourself back at the nominal wakeup interval.
+ */
+ spin_lock_irqsave(&dgap_poll_lock, lock_flags);
+ dgap_poll_time += dgap_jiffies_from_ms(dgap_poll_tick);
+
+ new_time = dgap_poll_time - jiffies;
+
+ if ((ulong) new_time >= 2 * dgap_poll_tick) {
+ dgap_poll_time =
+ jiffies + dgap_jiffies_from_ms(dgap_poll_tick);
+ }
+
+ dgap_poll_timer.function = dgap_poll_handler;
+ dgap_poll_timer.data = 0;
+ dgap_poll_timer.expires = dgap_poll_time;
+ spin_unlock_irqrestore(&dgap_poll_lock, lock_flags);
+
+ if (!dgap_poll_stop)
+ add_timer(&dgap_poll_timer);
+}
+
+/*
+ * dgap_intr()
+ *
+ * Driver interrupt handler.
+ */
+static irqreturn_t dgap_intr(int irq, void *voidbrd)
+{
+ struct board_t *brd = (struct board_t *) voidbrd;
+
+ if (!brd)
+ return IRQ_NONE;
+
+ /*
+ * Check to make sure its for us.
+ */
+ if (brd->magic != DGAP_BOARD_MAGIC)
+ return IRQ_NONE;
+
+ brd->intr_count++;
+
+ /*
+ * Schedule tasklet to run at a better time.
+ */
+ tasklet_schedule(&brd->helper_tasklet);
+ return IRQ_HANDLED;
+}
+
+/*
+ * dgap_init_globals()
+ *
+ * This is where we initialize the globals from the static insmod
+ * configuration variables. These are declared near the head of
+ * this file.
+ */
+static void dgap_init_globals(void)
+{
+ int i;
+
+ for (i = 0; i < MAXBOARDS; i++)
+ dgap_board[i] = NULL;
+
+ init_timer(&dgap_poll_timer);
+
+ init_waitqueue_head(&dgap_dl_wait);
+}
+
+/************************************************************************
+ *
+ * Utility functions
+ *
+ ************************************************************************/
+
+/*
+ * dgap_ms_sleep()
+ *
+ * Put the driver to sleep for x ms's
+ *
+ * Returns 0 if timed out, !0 (showing signal) if interrupted by a signal.
+ */
+static int dgap_ms_sleep(ulong ms)
+{
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout((ms * HZ) / 1000);
+ return signal_pending(current);
+}
+
+/************************************************************************
+ *
+ * TTY Initialization/Cleanup Functions
+ *
+ ************************************************************************/
+
+/*
+ * dgap_tty_register()
+ *
+ * Init the tty subsystem for this board.
+ */
+static int dgap_tty_register(struct board_t *brd)
+{
+ int rc;
+
+ brd->serial_driver = tty_alloc_driver(MAXPORTS, 0);
+ if (IS_ERR(brd->serial_driver))
+ return PTR_ERR(brd->serial_driver);
+
+ snprintf(brd->serial_name, MAXTTYNAMELEN, "tty_dgap_%d_",
+ brd->boardnum);
+ brd->serial_driver->name = brd->serial_name;
+ brd->serial_driver->name_base = 0;
+ brd->serial_driver->major = 0;
+ brd->serial_driver->minor_start = 0;
+ brd->serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ brd->serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ brd->serial_driver->init_termios = dgap_default_termios;
+ brd->serial_driver->driver_name = DRVSTR;
+ brd->serial_driver->flags = (TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_HARDWARE_BREAK);
+
+ /* The kernel wants space to store pointers to tty_structs */
+ brd->serial_driver->ttys =
+ kzalloc(MAXPORTS * sizeof(struct tty_struct *), GFP_KERNEL);
+ if (!brd->serial_driver->ttys) {
+ rc = -ENOMEM;
+ goto free_serial_drv;
+ }
+
+ /*
+ * Entry points for driver. Called by the kernel from
+ * tty_io.c and n_tty.c.
+ */
+ tty_set_operations(brd->serial_driver, &dgap_tty_ops);
+
+ /*
+ * If we're doing transparent print, we have to do all of the above
+ * again, separately so we don't get the LD confused about what major
+ * we are when we get into the dgap_tty_open() routine.
+ */
+ brd->print_driver = tty_alloc_driver(MAXPORTS, 0);
+ if (IS_ERR(brd->print_driver)) {
+ rc = PTR_ERR(brd->print_driver);
+ goto free_serial_drv;
+ }
+
+ snprintf(brd->print_name, MAXTTYNAMELEN, "pr_dgap_%d_",
+ brd->boardnum);
+ brd->print_driver->name = brd->print_name;
+ brd->print_driver->name_base = 0;
+ brd->print_driver->major = 0;
+ brd->print_driver->minor_start = 0;
+ brd->print_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ brd->print_driver->subtype = SERIAL_TYPE_NORMAL;
+ brd->print_driver->init_termios = dgap_default_termios;
+ brd->print_driver->driver_name = DRVSTR;
+ brd->print_driver->flags = (TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_HARDWARE_BREAK);
+
+ /* The kernel wants space to store pointers to tty_structs */
+ brd->print_driver->ttys =
+ kzalloc(MAXPORTS * sizeof(struct tty_struct *), GFP_KERNEL);
+ if (!brd->print_driver->ttys) {
+ rc = -ENOMEM;
+ goto free_print_drv;
+ }
+
+ /*
+ * Entry points for driver. Called by the kernel from
+ * tty_io.c and n_tty.c.
+ */
+ tty_set_operations(brd->print_driver, &dgap_tty_ops);
+
+ /* Register tty devices */
+ rc = tty_register_driver(brd->serial_driver);
+ if (rc < 0)
+ goto free_print_drv;
+
+ /* Register Transparent Print devices */
+ rc = tty_register_driver(brd->print_driver);
+ if (rc < 0)
+ goto unregister_serial_drv;
+
+ brd->dgap_major_serial_registered = TRUE;
+ dgap_boards_by_major[brd->serial_driver->major] = brd;
+ brd->dgap_serial_major = brd->serial_driver->major;
+
+ brd->dgap_major_transparent_print_registered = TRUE;
+ dgap_boards_by_major[brd->print_driver->major] = brd;
+ brd->dgap_transparent_print_major = brd->print_driver->major;
+
+ return 0;
+
+unregister_serial_drv:
+ tty_unregister_driver(brd->serial_driver);
+free_print_drv:
+ put_tty_driver(brd->print_driver);
+free_serial_drv:
+ put_tty_driver(brd->serial_driver);
+
+ return rc;
+}
+
+/*
+ * dgap_tty_init()
+ *
+ * Init the tty subsystem. Called once per board after board has been
+ * downloaded and init'ed.
+ */
+static int dgap_tty_init(struct board_t *brd)
+{
+ int i;
+ int tlw;
+ uint true_count;
+ u8 __iomem *vaddr;
+ u8 modem;
+ struct channel_t *ch;
+ struct bs_t __iomem *bs;
+ struct cm_t __iomem *cm;
+
+ if (!brd)
+ return -EIO;
+
+ /*
+ * Initialize board structure elements.
+ */
+
+ vaddr = brd->re_map_membase;
+ true_count = readw((vaddr + NCHAN));
+
+ brd->nasync = dgap_config_get_num_prts(brd);
+
+ if (!brd->nasync)
+ brd->nasync = brd->maxports;
+
+ if (brd->nasync > brd->maxports)
+ brd->nasync = brd->maxports;
+
+ if (true_count != brd->nasync) {
+ if ((brd->type == PPCM) && (true_count == 64)) {
+ pr_warn("dgap: %s configured for %d ports, has %d ports.\n",
+ brd->name, brd->nasync, true_count);
+ pr_warn("dgap: Please make SURE the EBI cable running from the card\n");
+ pr_warn("dgap: to each EM module is plugged into EBI IN!\n");
+ } else if ((brd->type == PPCM) && (true_count == 0)) {
+ pr_warn("dgap: %s configured for %d ports, has %d ports.\n",
+ brd->name, brd->nasync, true_count);
+ pr_warn("dgap: Please make SURE the EBI cable running from the card\n");
+ pr_warn("dgap: to each EM module is plugged into EBI IN!\n");
+ } else
+ pr_warn("dgap: %s configured for %d ports, has %d ports.\n",
+ brd->name, brd->nasync, true_count);
+
+ brd->nasync = true_count;
+
+ /* If no ports, don't bother going any further */
+ if (!brd->nasync) {
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+ return -EIO;
+ }
+ }
+
+ /*
+ * Allocate channel memory that might not have been allocated
+ * when the driver was first loaded.
+ */
+ for (i = 0; i < brd->nasync; i++) {
+ if (!brd->channels[i]) {
+ brd->channels[i] =
+ kzalloc(sizeof(struct channel_t), GFP_KERNEL);
+ if (!brd->channels[i])
+ return -ENOMEM;
+ }
+ }
+
+ ch = brd->channels[0];
+ vaddr = brd->re_map_membase;
+
+ bs = (struct bs_t __iomem *) ((ulong) vaddr + CHANBUF);
+ cm = (struct cm_t __iomem *) ((ulong) vaddr + CMDBUF);
+
+ brd->bd_bs = bs;
+
+ /* Set up channel variables */
+ for (i = 0; i < brd->nasync; i++, ch = brd->channels[i], bs++) {
+
+ if (!brd->channels[i])
+ continue;
+
+ spin_lock_init(&ch->ch_lock);
+
+ /* Store all our magic numbers */
+ ch->magic = DGAP_CHANNEL_MAGIC;
+ ch->ch_tun.magic = DGAP_UNIT_MAGIC;
+ ch->ch_tun.un_type = DGAP_SERIAL;
+ ch->ch_tun.un_ch = ch;
+ ch->ch_tun.un_dev = i;
+
+ ch->ch_pun.magic = DGAP_UNIT_MAGIC;
+ ch->ch_pun.un_type = DGAP_PRINT;
+ ch->ch_pun.un_ch = ch;
+ ch->ch_pun.un_dev = i;
+
+ ch->ch_vaddr = vaddr;
+ ch->ch_bs = bs;
+ ch->ch_cm = cm;
+ ch->ch_bd = brd;
+ ch->ch_portnum = i;
+ ch->ch_digi = dgap_digi_init;
+
+ /*
+ * Set up digi dsr and dcd bits based on altpin flag.
+ */
+ if (dgap_config_get_altpin(brd)) {
+ ch->ch_dsr = DM_CD;
+ ch->ch_cd = DM_DSR;
+ ch->ch_digi.digi_flags |= DIGI_ALTPIN;
+ } else {
+ ch->ch_cd = DM_CD;
+ ch->ch_dsr = DM_DSR;
+ }
+
+ ch->ch_taddr = vaddr + (ioread16(&(ch->ch_bs->tx_seg)) << 4);
+ ch->ch_raddr = vaddr + (ioread16(&(ch->ch_bs->rx_seg)) << 4);
+ ch->ch_tx_win = 0;
+ ch->ch_rx_win = 0;
+ ch->ch_tsize = readw(&(ch->ch_bs->tx_max)) + 1;
+ ch->ch_rsize = readw(&(ch->ch_bs->rx_max)) + 1;
+ ch->ch_tstart = 0;
+ ch->ch_rstart = 0;
+
+ /* .25 second delay */
+ ch->ch_close_delay = 250;
+
+ /*
+ * Set queue water marks, interrupt mask,
+ * and general tty parameters.
+ */
+ tlw = ch->ch_tsize >= 2000 ? ((ch->ch_tsize * 5) / 8) :
+ ch->ch_tsize / 2;
+ ch->ch_tlw = tlw;
+
+ dgap_cmdw(ch, STLOW, tlw, 0);
+
+ dgap_cmdw(ch, SRLOW, ch->ch_rsize / 2, 0);
+
+ dgap_cmdw(ch, SRHIGH, 7 * ch->ch_rsize / 8, 0);
+
+ ch->ch_mistat = readb(&(ch->ch_bs->m_stat));
+
+ init_waitqueue_head(&ch->ch_flags_wait);
+ init_waitqueue_head(&ch->ch_tun.un_flags_wait);
+ init_waitqueue_head(&ch->ch_pun.un_flags_wait);
+
+ /* Turn on all modem interrupts for now */
+ modem = (DM_CD | DM_DSR | DM_CTS | DM_RI);
+ writeb(modem, &(ch->ch_bs->m_int));
+
+ /*
+ * Set edelay to 0 if interrupts are turned on,
+ * otherwise set edelay to the usual 100.
+ */
+ if (brd->intr_used)
+ writew(0, &(ch->ch_bs->edelay));
+ else
+ writew(100, &(ch->ch_bs->edelay));
+
+ writeb(1, &(ch->ch_bs->idata));
+ }
+
+ return 0;
+}
+
+/*
+ * dgap_tty_uninit()
+ *
+ * Uninitialize the TTY portion of this driver. Free all memory and
+ * resources.
+ */
+static void dgap_tty_uninit(struct board_t *brd)
+{
+ struct device *dev;
+ int i;
+
+ if (brd->dgap_major_serial_registered) {
+ dgap_boards_by_major[brd->serial_driver->major] = NULL;
+ brd->dgap_serial_major = 0;
+ for (i = 0; i < brd->nasync; i++) {
+ tty_port_destroy(&brd->serial_ports[i]);
+ dev = brd->channels[i]->ch_tun.un_sysfs;
+ dgap_remove_tty_sysfs(dev);
+ tty_unregister_device(brd->serial_driver, i);
+ }
+ tty_unregister_driver(brd->serial_driver);
+ put_tty_driver(brd->serial_driver);
+ kfree(brd->serial_ports);
+ brd->dgap_major_serial_registered = FALSE;
+ }
+
+ if (brd->dgap_major_transparent_print_registered) {
+ dgap_boards_by_major[brd->print_driver->major] = NULL;
+ brd->dgap_transparent_print_major = 0;
+ for (i = 0; i < brd->nasync; i++) {
+ tty_port_destroy(&brd->printer_ports[i]);
+ dev = brd->channels[i]->ch_pun.un_sysfs;
+ dgap_remove_tty_sysfs(dev);
+ tty_unregister_device(brd->print_driver, i);
+ }
+ tty_unregister_driver(brd->print_driver);
+ put_tty_driver(brd->print_driver);
+ kfree(brd->printer_ports);
+ brd->dgap_major_transparent_print_registered = FALSE;
+ }
+}
+
+/*=======================================================================
+ *
+ * dgap_input - Process received data.
+ *
+ * ch - Pointer to channel structure.
+ *
+ *=======================================================================*/
+
+static void dgap_input(struct channel_t *ch)
+{
+ struct board_t *bd;
+ struct bs_t __iomem *bs;
+ struct tty_struct *tp;
+ struct tty_ldisc *ld;
+ uint rmask;
+ uint head;
+ uint tail;
+ int data_len;
+ ulong lock_flags;
+ ulong lock_flags2;
+ int flip_len;
+ int len;
+ int n;
+ u8 *buf;
+ u8 tmpchar;
+ int s;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ tp = ch->ch_tun.un_tty;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ /*
+ * Figure the number of characters in the buffer.
+ * Exit immediately if none.
+ */
+
+ rmask = ch->ch_rsize - 1;
+
+ head = readw(&(bs->rx_head));
+ head &= rmask;
+ tail = readw(&(bs->rx_tail));
+ tail &= rmask;
+
+ data_len = (head - tail) & rmask;
+
+ if (data_len == 0) {
+ writeb(1, &(bs->idata));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * If the device is not open, or CREAD is off, flush
+ * input data and return immediately.
+ */
+ if ((bd->state != BOARD_READY) || !tp ||
+ (tp->magic != TTY_MAGIC) ||
+ !(ch->ch_tun.un_flags & UN_ISOPEN) ||
+ !(tp->termios.c_cflag & CREAD) ||
+ (ch->ch_tun.un_flags & UN_CLOSING)) {
+
+ writew(head, &(bs->rx_tail));
+ writeb(1, &(bs->idata));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * If we are throttled, simply don't read any data.
+ */
+ if (ch->ch_flags & CH_RXBLOCK) {
+ writeb(1, &(bs->idata));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * Ignore oruns.
+ */
+ tmpchar = readb(&(bs->orun));
+ if (tmpchar) {
+ ch->ch_err_overrun++;
+ writeb(0, &(bs->orun));
+ }
+
+ /* Decide how much data we can send into the tty layer */
+ flip_len = TTY_FLIPBUF_SIZE;
+
+ /* Chop down the length, if needed */
+ len = min(data_len, flip_len);
+ len = min(len, (N_TTY_BUF_SIZE - 1));
+
+ ld = tty_ldisc_ref(tp);
+
+#ifdef TTY_DONT_FLIP
+ /*
+ * If the DONT_FLIP flag is on, don't flush our buffer, and act
+ * like the ld doesn't have any space to put the data right now.
+ */
+ if (test_bit(TTY_DONT_FLIP, &tp->flags))
+ len = 0;
+#endif
+
+ /*
+ * If we were unable to get a reference to the ld,
+ * don't flush our buffer, and act like the ld doesn't
+ * have any space to put the data right now.
+ */
+ if (!ld) {
+ len = 0;
+ } else {
+ /*
+ * If ld doesn't have a pointer to a receive_buf function,
+ * flush the data, then act like the ld doesn't have any
+ * space to put the data right now.
+ */
+ if (!ld->ops->receive_buf) {
+ writew(head, &(bs->rx_tail));
+ len = 0;
+ }
+ }
+
+ if (len <= 0) {
+ writeb(1, &(bs->idata));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ if (ld)
+ tty_ldisc_deref(ld);
+ return;
+ }
+
+ buf = ch->ch_bd->flipbuf;
+ n = len;
+
+ /*
+ * n now contains the most amount of data we can copy,
+ * bounded either by our buffer size or the amount
+ * of data the card actually has pending...
+ */
+ while (n) {
+
+ s = ((head >= tail) ? head : ch->ch_rsize) - tail;
+ s = min(s, n);
+
+ if (s <= 0)
+ break;
+
+ memcpy_fromio(buf, ch->ch_raddr + tail, s);
+
+ tail += s;
+ buf += s;
+
+ n -= s;
+ /* Flip queue if needed */
+ tail &= rmask;
+ }
+
+ writew(tail, &(bs->rx_tail));
+ writeb(1, &(bs->idata));
+ ch->ch_rxcount += len;
+
+ /*
+ * If we are completely raw, we don't need to go through a lot
+ * of the tty layers that exist.
+ * In this case, we take the shortest and fastest route we
+ * can to relay the data to the user.
+ *
+ * On the other hand, if we are not raw, we need to go through
+ * the tty layer, which has its API more well defined.
+ */
+ if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
+ dgap_parity_scan(ch, ch->ch_bd->flipbuf,
+ ch->ch_bd->flipflagbuf, &len);
+
+ len = tty_buffer_request_room(tp->port, len);
+ tty_insert_flip_string_flags(tp->port, ch->ch_bd->flipbuf,
+ ch->ch_bd->flipflagbuf, len);
+ } else {
+ len = tty_buffer_request_room(tp->port, len);
+ tty_insert_flip_string(tp->port, ch->ch_bd->flipbuf, len);
+ }
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ /* Tell the tty layer its okay to "eat" the data now */
+ tty_flip_buffer_push(tp->port);
+
+ if (ld)
+ tty_ldisc_deref(ld);
+
+}
+
+/************************************************************************
+ * Determines when CARRIER changes state and takes appropriate
+ * action.
+ ************************************************************************/
+static void dgap_carrier(struct channel_t *ch)
+{
+ struct board_t *bd;
+
+ int virt_carrier = 0;
+ int phys_carrier = 0;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ /* Make sure altpin is always set correctly */
+ if (ch->ch_digi.digi_flags & DIGI_ALTPIN) {
+ ch->ch_dsr = DM_CD;
+ ch->ch_cd = DM_DSR;
+ } else {
+ ch->ch_dsr = DM_DSR;
+ ch->ch_cd = DM_CD;
+ }
+
+ if (ch->ch_mistat & D_CD(ch))
+ phys_carrier = 1;
+
+ if (ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+ virt_carrier = 1;
+
+ if (ch->ch_c_cflag & CLOCAL)
+ virt_carrier = 1;
+
+ /*
+ * Test for a VIRTUAL carrier transition to HIGH.
+ */
+ if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {
+
+ /*
+ * When carrier rises, wake any threads waiting
+ * for carrier in the open routine.
+ */
+
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+
+ /*
+ * Test for a PHYSICAL carrier transition to HIGH.
+ */
+ if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {
+
+ /*
+ * When carrier rises, wake any threads waiting
+ * for carrier in the open routine.
+ */
+
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+
+ /*
+ * Test for a PHYSICAL transition to low, so long as we aren't
+ * currently ignoring physical transitions (which is what "virtual
+ * carrier" indicates).
+ *
+ * The transition of the virtual carrier to low really doesn't
+ * matter... it really only means "ignore carrier state", not
+ * "make pretend that carrier is there".
+ */
+ if ((virt_carrier == 0) &&
+ ((ch->ch_flags & CH_CD) != 0) &&
+ (phys_carrier == 0)) {
+
+ /*
+ * When carrier drops:
+ *
+ * Drop carrier on all open units.
+ *
+ * Flush queues, waking up any task waiting in the
+ * line discipline.
+ *
+ * Send a hangup to the control terminal.
+ *
+ * Enable all select calls.
+ */
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+
+ if (ch->ch_tun.un_open_count > 0)
+ tty_hangup(ch->ch_tun.un_tty);
+
+ if (ch->ch_pun.un_open_count > 0)
+ tty_hangup(ch->ch_pun.un_tty);
+ }
+
+ /*
+ * Make sure that our cached values reflect the current reality.
+ */
+ if (virt_carrier == 1)
+ ch->ch_flags |= CH_FCAR;
+ else
+ ch->ch_flags &= ~CH_FCAR;
+
+ if (phys_carrier == 1)
+ ch->ch_flags |= CH_CD;
+ else
+ ch->ch_flags &= ~CH_CD;
+}
+
+/************************************************************************
+ *
+ * TTY Entry points and helper functions
+ *
+ ************************************************************************/
+
+/*
+ * dgap_tty_open()
+ *
+ */
+static int dgap_tty_open(struct tty_struct *tty, struct file *file)
+{
+ struct board_t *brd;
+ struct channel_t *ch;
+ struct un_t *un;
+ struct bs_t __iomem *bs;
+ uint major;
+ uint minor;
+ int rc;
+ ulong lock_flags;
+ ulong lock_flags2;
+ u16 head;
+
+ major = MAJOR(tty_devnum(tty));
+ minor = MINOR(tty_devnum(tty));
+
+ if (major > 255)
+ return -EIO;
+
+ /* Get board pointer from our array of majors we have allocated */
+ brd = dgap_boards_by_major[major];
+ if (!brd)
+ return -EIO;
+
+ /*
+ * If board is not yet up to a state of READY, go to
+ * sleep waiting for it to happen or they cancel the open.
+ */
+ rc = wait_event_interruptible(brd->state_wait,
+ (brd->state & BOARD_READY));
+
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&brd->bd_lock, lock_flags);
+
+ /* The wait above should guarantee this cannot happen */
+ if (brd->state != BOARD_READY) {
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ /* If opened device is greater than our number of ports, bail. */
+ if (MINOR(tty_devnum(tty)) > brd->nasync) {
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ ch = brd->channels[minor];
+ if (!ch) {
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ /* Grab channel lock */
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ /* Figure out our type */
+ if (major == brd->dgap_serial_major) {
+ un = &brd->channels[minor]->ch_tun;
+ un->un_type = DGAP_SERIAL;
+ } else if (major == brd->dgap_transparent_print_major) {
+ un = &brd->channels[minor]->ch_pun;
+ un->un_type = DGAP_PRINT;
+ } else {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ /* Store our unit into driver_data, so we always have it available. */
+ tty->driver_data = un;
+
+ /*
+ * Error if channel info pointer is NULL.
+ */
+ bs = ch->ch_bs;
+ if (!bs) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ /*
+ * Initialize tty's
+ */
+ if (!(un->un_flags & UN_ISOPEN)) {
+ /* Store important variables. */
+ un->un_tty = tty;
+
+ /* Maybe do something here to the TTY struct as well? */
+ }
+
+ /*
+ * Initialize if neither terminal or printer is open.
+ */
+ if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) {
+
+ ch->ch_mforce = 0;
+ ch->ch_mval = 0;
+
+ /*
+ * Flush input queue.
+ */
+ head = readw(&(bs->rx_head));
+ writew(head, &(bs->rx_tail));
+
+ ch->ch_flags = 0;
+ ch->pscan_state = 0;
+ ch->pscan_savechar = 0;
+
+ ch->ch_c_cflag = tty->termios.c_cflag;
+ ch->ch_c_iflag = tty->termios.c_iflag;
+ ch->ch_c_oflag = tty->termios.c_oflag;
+ ch->ch_c_lflag = tty->termios.c_lflag;
+ ch->ch_startc = tty->termios.c_cc[VSTART];
+ ch->ch_stopc = tty->termios.c_cc[VSTOP];
+
+ /* TODO: flush our TTY struct here? */
+ }
+
+ dgap_carrier(ch);
+ /*
+ * Run param in case we changed anything
+ */
+ dgap_param(tty);
+
+ /*
+ * follow protocol for opening port
+ */
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&brd->bd_lock, lock_flags);
+
+ rc = dgap_block_til_ready(tty, file, ch);
+
+ if (!un->un_tty)
+ return -ENODEV;
+
+ /* No going back now, increment our unit and channel counters */
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ ch->ch_open_count++;
+ un->un_open_count++;
+ un->un_flags |= (UN_ISOPEN);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ return rc;
+}
+
+/*
+ * dgap_block_til_ready()
+ *
+ * Wait for DCD, if needed.
+ */
+static int dgap_block_til_ready(struct tty_struct *tty, struct file *file,
+ struct channel_t *ch)
+{
+ int retval = 0;
+ struct un_t *un;
+ ulong lock_flags;
+ uint old_flags;
+ int sleep_on_un_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC || !file || !ch ||
+ ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ ch->ch_wopen++;
+
+ /* Loop forever */
+ while (1) {
+
+ sleep_on_un_flags = 0;
+
+ /*
+ * If board has failed somehow during our sleep,
+ * bail with error.
+ */
+ if (ch->ch_bd->state == BOARD_FAILED) {
+ retval = -EIO;
+ break;
+ }
+
+ /* If tty was hung up, break out of loop and set error. */
+ if (tty_hung_up_p(file)) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ /*
+ * If either unit is in the middle of the fragile part of close,
+ * we just cannot touch the channel safely.
+ * Go back to sleep, knowing that when the channel can be
+ * touched safely, the close routine will signal the
+ * ch_wait_flags to wake us back up.
+ */
+ if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) &
+ UN_CLOSING)) {
+
+ /*
+ * Our conditions to leave cleanly and happily:
+ * 1) NONBLOCKING on the tty is set.
+ * 2) CLOCAL is set.
+ * 3) DCD (fake or real) is active.
+ */
+
+ if (file->f_flags & O_NONBLOCK)
+ break;
+
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ break;
+
+ if (ch->ch_flags & CH_CD)
+ break;
+
+ if (ch->ch_flags & CH_FCAR)
+ break;
+ } else {
+ sleep_on_un_flags = 1;
+ }
+
+ /*
+ * If there is a signal pending, the user probably
+ * interrupted (ctrl-c) us.
+ * Leave loop with error set.
+ */
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ /*
+ * Store the flags before we let go of channel lock
+ */
+ if (sleep_on_un_flags)
+ old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags;
+ else
+ old_flags = ch->ch_flags;
+
+ /*
+ * Let go of channel lock before calling schedule.
+ * Our poller will get any FEP events and wake us up when DCD
+ * eventually goes active.
+ */
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ /*
+ * Wait for something in the flags to change
+ * from the current value.
+ */
+ if (sleep_on_un_flags) {
+ retval = wait_event_interruptible(un->un_flags_wait,
+ (old_flags != (ch->ch_tun.un_flags |
+ ch->ch_pun.un_flags)));
+ } else {
+ retval = wait_event_interruptible(ch->ch_flags_wait,
+ (old_flags != ch->ch_flags));
+ }
+
+ /*
+ * We got woken up for some reason.
+ * Before looping around, grab our channel lock.
+ */
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ }
+
+ ch->ch_wopen--;
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ if (retval)
+ return retval;
+
+ return 0;
+}
+
+/*
+ * dgap_tty_hangup()
+ *
+ * Hangup the port. Like a close, but don't wait for output to drain.
+ */
+static void dgap_tty_hangup(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ /* flush the transmit queues */
+ dgap_tty_flush_buffer(tty);
+}
+
+/*
+ * dgap_tty_close()
+ *
+ */
+static void dgap_tty_close(struct tty_struct *tty, struct file *file)
+{
+ struct ktermios *ts;
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ ts = &tty->termios;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ /*
+ * Determine if this is the last close or not - and if we agree about
+ * which type of close it is with the Line Discipline
+ */
+ if ((tty->count == 1) && (un->un_open_count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. un_open_count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ un->un_open_count = 1;
+ }
+
+ if (--un->un_open_count < 0)
+ un->un_open_count = 0;
+
+ ch->ch_open_count--;
+
+ if (ch->ch_open_count && un->un_open_count) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* OK, its the last close on the unit */
+
+ un->un_flags |= UN_CLOSING;
+
+ tty->closing = 1;
+
+ /*
+ * Only officially close channel if count is 0 and
+ * DIGI_PRINTER bit is not set.
+ */
+ if ((ch->ch_open_count == 0) &&
+ !(ch->ch_digi.digi_flags & DIGI_PRINTER)) {
+
+ ch->ch_flags &= ~(CH_RXBLOCK);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ /* wait for output to drain */
+ /* This will also return if we take an interrupt */
+
+ dgap_wait_for_drain(tty);
+
+ dgap_tty_flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ tty->closing = 0;
+
+ /*
+ * If we have HUPCL set, lower DTR and RTS
+ */
+ if (ch->ch_c_cflag & HUPCL) {
+ ch->ch_mostat &= ~(D_RTS(ch)|D_DTR(ch));
+ dgap_cmdb(ch, SMODEM, 0, D_DTR(ch)|D_RTS(ch), 0);
+
+ /*
+ * Go to sleep to ensure RTS/DTR
+ * have been dropped for modems to see it.
+ */
+ if (ch->ch_close_delay) {
+ spin_unlock_irqrestore(&ch->ch_lock,
+ lock_flags);
+ dgap_ms_sleep(ch->ch_close_delay);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ }
+ }
+
+ ch->pscan_state = 0;
+ ch->pscan_savechar = 0;
+ ch->ch_baud_info = 0;
+
+ }
+
+ /*
+ * turn off print device when closing print device.
+ */
+ if ((un->un_type == DGAP_PRINT) && (ch->ch_flags & CH_PRON)) {
+ dgap_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ ch->ch_flags &= ~CH_PRON;
+ }
+
+ un->un_tty = NULL;
+ un->un_flags &= ~(UN_ISOPEN | UN_CLOSING);
+ tty->driver_data = NULL;
+
+ wake_up_interruptible(&ch->ch_flags_wait);
+ wake_up_interruptible(&un->un_flags_wait);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+}
+
+/*
+ * dgap_tty_chars_in_buffer()
+ *
+ * Return number of characters that have not been transmitted yet.
+ *
+ * This routine is used by the line discipline to determine if there
+ * is data waiting to be transmitted/drained/flushed or not.
+ */
+static int dgap_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ struct bs_t __iomem *bs;
+ u8 tbusy;
+ uint chars;
+ u16 thead, ttail, tmask, chead, ctail;
+ ulong lock_flags = 0;
+ ulong lock_flags2 = 0;
+
+ if (!tty)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return 0;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ tmask = (ch->ch_tsize - 1);
+
+ /* Get Transmit queue pointers */
+ thead = readw(&(bs->tx_head)) & tmask;
+ ttail = readw(&(bs->tx_tail)) & tmask;
+
+ /* Get tbusy flag */
+ tbusy = readb(&(bs->tbusy));
+
+ /* Get Command queue pointers */
+ chead = readw(&(ch->ch_cm->cm_head));
+ ctail = readw(&(ch->ch_cm->cm_tail));
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ /*
+ * The only way we know for sure if there is no pending
+ * data left to be transferred, is if:
+ * 1) Transmit head and tail are equal (empty).
+ * 2) Command queue head and tail are equal (empty).
+ * 3) The "TBUSY" flag is 0. (Transmitter not busy).
+ */
+
+ if ((ttail == thead) && (tbusy == 0) && (chead == ctail)) {
+ chars = 0;
+ } else {
+ if (thead >= ttail)
+ chars = thead - ttail;
+ else
+ chars = thead - ttail + ch->ch_tsize;
+ /*
+ * Fudge factor here.
+ * If chars is zero, we know that the command queue had
+ * something in it or tbusy was set. Because we cannot
+ * be sure if there is still some data to be transmitted,
+ * lets lie, and tell ld we have 1 byte left.
+ */
+ if (chars == 0) {
+ /*
+ * If TBUSY is still set, and our tx buffers are empty,
+ * force the firmware to send me another wakeup after
+ * TBUSY has been cleared.
+ */
+ if (tbusy != 0) {
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ un->un_flags |= UN_EMPTY;
+ writeb(1, &(bs->iempty));
+ spin_unlock_irqrestore(&ch->ch_lock,
+ lock_flags);
+ }
+ chars = 1;
+ }
+ }
+
+ return chars;
+}
+
+static int dgap_wait_for_drain(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ struct bs_t __iomem *bs;
+ int ret = 0;
+ uint count = 1;
+ ulong lock_flags = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return -EIO;
+
+ /* Loop until data is drained */
+ while (count != 0) {
+
+ count = dgap_tty_chars_in_buffer(tty);
+
+ if (count == 0)
+ break;
+
+ /* Set flag waiting for drain */
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ un->un_flags |= UN_EMPTY;
+ writeb(1, &(bs->iempty));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ /* Go to sleep till we get woken up */
+ ret = wait_event_interruptible(un->un_flags_wait,
+ ((un->un_flags & UN_EMPTY) == 0));
+ /* If ret is non-zero, user ctrl-c'ed us */
+ if (ret)
+ break;
+ }
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ un->un_flags &= ~(UN_EMPTY);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ return ret;
+}
+
+/*
+ * dgap_maxcps_room
+ *
+ * Reduces bytes_available to the max number of characters
+ * that can be sent currently given the maxcps value, and
+ * returns the new bytes_available. This only affects printer
+ * output.
+ */
+static int dgap_maxcps_room(struct tty_struct *tty, int bytes_available)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!tty)
+ return bytes_available;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return bytes_available;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return bytes_available;
+
+ /*
+ * If its not the Transparent print device, return
+ * the full data amount.
+ */
+ if (un->un_type != DGAP_PRINT)
+ return bytes_available;
+
+ if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) {
+ int cps_limit = 0;
+ unsigned long current_time = jiffies;
+ unsigned long buffer_time = current_time +
+ (HZ * ch->ch_digi.digi_bufsize) /
+ ch->ch_digi.digi_maxcps;
+
+ if (ch->ch_cpstime < current_time) {
+ /* buffer is empty */
+ ch->ch_cpstime = current_time; /* reset ch_cpstime */
+ cps_limit = ch->ch_digi.digi_bufsize;
+ } else if (ch->ch_cpstime < buffer_time) {
+ /* still room in the buffer */
+ cps_limit = ((buffer_time - ch->ch_cpstime) *
+ ch->ch_digi.digi_maxcps) / HZ;
+ } else {
+ /* no room in the buffer */
+ cps_limit = 0;
+ }
+
+ bytes_available = min(cps_limit, bytes_available);
+ }
+
+ return bytes_available;
+}
+
+static inline void dgap_set_firmware_event(struct un_t *un, unsigned int event)
+{
+ struct channel_t *ch;
+ struct bs_t __iomem *bs;
+
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+ bs = ch->ch_bs;
+ if (!bs)
+ return;
+
+ if ((event & UN_LOW) != 0) {
+ if ((un->un_flags & UN_LOW) == 0) {
+ un->un_flags |= UN_LOW;
+ writeb(1, &(bs->ilow));
+ }
+ }
+ if ((event & UN_LOW) != 0) {
+ if ((un->un_flags & UN_EMPTY) == 0) {
+ un->un_flags |= UN_EMPTY;
+ writeb(1, &(bs->iempty));
+ }
+ }
+}
+
+/*
+ * dgap_tty_write_room()
+ *
+ * Return space available in Tx buffer
+ */
+static int dgap_tty_write_room(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ struct bs_t __iomem *bs;
+ u16 head, tail, tmask;
+ int ret;
+ ulong lock_flags = 0;
+
+ if (!tty)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return 0;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ tmask = ch->ch_tsize - 1;
+ head = readw(&(bs->tx_head)) & tmask;
+ tail = readw(&(bs->tx_tail)) & tmask;
+
+ ret = tail - head - 1;
+ if (ret < 0)
+ ret += ch->ch_tsize;
+
+ /* Limit printer to maxcps */
+ ret = dgap_maxcps_room(tty, ret);
+
+ /*
+ * If we are printer device, leave space for
+ * possibly both the on and off strings.
+ */
+ if (un->un_type == DGAP_PRINT) {
+ if (!(ch->ch_flags & CH_PRON))
+ ret -= ch->ch_digi.digi_onlen;
+ ret -= ch->ch_digi.digi_offlen;
+ } else {
+ if (ch->ch_flags & CH_PRON)
+ ret -= ch->ch_digi.digi_offlen;
+ }
+
+ if (ret < 0)
+ ret = 0;
+
+ /*
+ * Schedule FEP to wake us up if needed.
+ *
+ * TODO: This might be overkill...
+ * Do we really need to schedule callbacks from the FEP
+ * in every case? Can we get smarter based on ret?
+ */
+ dgap_set_firmware_event(un, UN_LOW | UN_EMPTY);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ return ret;
+}
+
+/*
+ * dgap_tty_put_char()
+ *
+ * Put a character into ch->ch_buf
+ *
+ * - used by the line discipline for OPOST processing
+ */
+static int dgap_tty_put_char(struct tty_struct *tty, unsigned char c)
+{
+ /*
+ * Simply call tty_write.
+ */
+ dgap_tty_write(tty, &c, 1);
+ return 1;
+}
+
+/*
+ * dgap_tty_write()
+ *
+ * Take data from the user or kernel and send it out to the FEP.
+ * In here exists all the Transparent Print magic as well.
+ */
+static int dgap_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int count)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ struct bs_t __iomem *bs;
+ char __iomem *vaddr;
+ u16 head, tail, tmask, remain;
+ int bufcount, n;
+ int orig_count;
+ ulong lock_flags;
+
+ if (!tty)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return 0;
+
+ if (!count)
+ return 0;
+
+ /*
+ * Store original amount of characters passed in.
+ * This helps to figure out if we should ask the FEP
+ * to send us an event when it has more space available.
+ */
+ orig_count = count;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ /* Get our space available for the channel from the board */
+ tmask = ch->ch_tsize - 1;
+ head = readw(&(bs->tx_head)) & tmask;
+ tail = readw(&(bs->tx_tail)) & tmask;
+
+ bufcount = tail - head - 1;
+ if (bufcount < 0)
+ bufcount += ch->ch_tsize;
+
+ /*
+ * Limit printer output to maxcps overall, with bursts allowed
+ * up to bufsize characters.
+ */
+ bufcount = dgap_maxcps_room(tty, bufcount);
+
+ /*
+ * Take minimum of what the user wants to send, and the
+ * space available in the FEP buffer.
+ */
+ count = min(count, bufcount);
+
+ /*
+ * Bail if no space left.
+ */
+ if (count <= 0) {
+ dgap_set_firmware_event(un, UN_LOW | UN_EMPTY);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ /*
+ * Output the printer ON string, if we are in terminal mode, but
+ * need to be in printer mode.
+ */
+ if ((un->un_type == DGAP_PRINT) && !(ch->ch_flags & CH_PRON)) {
+ dgap_wmove(ch, ch->ch_digi.digi_onstr,
+ (int) ch->ch_digi.digi_onlen);
+ head = readw(&(bs->tx_head)) & tmask;
+ ch->ch_flags |= CH_PRON;
+ }
+
+ /*
+ * On the other hand, output the printer OFF string, if we are
+ * currently in printer mode, but need to output to the terminal.
+ */
+ if ((un->un_type != DGAP_PRINT) && (ch->ch_flags & CH_PRON)) {
+ dgap_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ head = readw(&(bs->tx_head)) & tmask;
+ ch->ch_flags &= ~CH_PRON;
+ }
+
+ n = count;
+
+ /*
+ * If the write wraps over the top of the circular buffer,
+ * move the portion up to the wrap point, and reset the
+ * pointers to the bottom.
+ */
+ remain = ch->ch_tstart + ch->ch_tsize - head;
+
+ if (n >= remain) {
+ n -= remain;
+ vaddr = ch->ch_taddr + head;
+
+ memcpy_toio(vaddr, (u8 *) buf, remain);
+
+ head = ch->ch_tstart;
+ buf += remain;
+ }
+
+ if (n > 0) {
+
+ /*
+ * Move rest of data.
+ */
+ vaddr = ch->ch_taddr + head;
+ remain = n;
+
+ memcpy_toio(vaddr, (u8 *) buf, remain);
+ head += remain;
+
+ }
+
+ if (count) {
+ ch->ch_txcount += count;
+ head &= tmask;
+ writew(head, &(bs->tx_head));
+ }
+
+ dgap_set_firmware_event(un, UN_LOW | UN_EMPTY);
+
+ /*
+ * If this is the print device, and the
+ * printer is still on, we need to turn it
+ * off before going idle. If the buffer is
+ * non-empty, wait until it goes empty.
+ * Otherwise turn it off right now.
+ */
+ if ((un->un_type == DGAP_PRINT) && (ch->ch_flags & CH_PRON)) {
+ tail = readw(&(bs->tx_tail)) & tmask;
+
+ if (tail != head) {
+ un->un_flags |= UN_EMPTY;
+ writeb(1, &(bs->iempty));
+ } else {
+ dgap_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ head = readw(&(bs->tx_head)) & tmask;
+ ch->ch_flags &= ~CH_PRON;
+ }
+ }
+
+ /* Update printer buffer empty time. */
+ if ((un->un_type == DGAP_PRINT) && (ch->ch_digi.digi_maxcps > 0)
+ && (ch->ch_digi.digi_bufsize > 0)) {
+ ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps;
+ }
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ return count;
+}
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgap_tty_tiocmget(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ int result;
+ u8 mstat;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ mstat = readb(&(ch->ch_bs->m_stat));
+ /* Append any outbound signals that might be pending... */
+ mstat |= ch->ch_mostat;
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ result = 0;
+
+ if (mstat & D_DTR(ch))
+ result |= TIOCM_DTR;
+ if (mstat & D_RTS(ch))
+ result |= TIOCM_RTS;
+ if (mstat & D_CTS(ch))
+ result |= TIOCM_CTS;
+ if (mstat & D_DSR(ch))
+ result |= TIOCM_DSR;
+ if (mstat & D_RI(ch))
+ result |= TIOCM_RI;
+ if (mstat & D_CD(ch))
+ result |= TIOCM_CD;
+
+ return result;
+}
+
+/*
+ * dgap_tty_tiocmset()
+ *
+ * Set modem signals, called by ld.
+ */
+static int dgap_tty_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ if (set & TIOCM_RTS) {
+ ch->ch_mforce |= D_RTS(ch);
+ ch->ch_mval |= D_RTS(ch);
+ }
+
+ if (set & TIOCM_DTR) {
+ ch->ch_mforce |= D_DTR(ch);
+ ch->ch_mval |= D_DTR(ch);
+ }
+
+ if (clear & TIOCM_RTS) {
+ ch->ch_mforce |= D_RTS(ch);
+ ch->ch_mval &= ~(D_RTS(ch));
+ }
+
+ if (clear & TIOCM_DTR) {
+ ch->ch_mforce |= D_DTR(ch);
+ ch->ch_mval &= ~(D_DTR(ch));
+ }
+
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+/*
+ * dgap_tty_send_break()
+ *
+ * Send a Break, called by ld.
+ */
+static int dgap_tty_send_break(struct tty_struct *tty, int msec)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ switch (msec) {
+ case -1:
+ msec = 0xFFFF;
+ break;
+ case 0:
+ msec = 1;
+ break;
+ default:
+ msec /= 10;
+ break;
+ }
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+#if 0
+ dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0);
+#endif
+ dgap_cmdw(ch, SBREAK, (u16) msec, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+/*
+ * dgap_tty_wait_until_sent()
+ *
+ * wait until data has been transmitted, called by ld.
+ */
+static void dgap_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ dgap_wait_for_drain(tty);
+}
+
+/*
+ * dgap_send_xchar()
+ *
+ * send a high priority character, called by ld.
+ */
+static void dgap_tty_send_xchar(struct tty_struct *tty, char c)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ /*
+ * This is technically what we should do.
+ * However, the NIST tests specifically want
+ * to see each XON or XOFF character that it
+ * sends, so lets just send each character
+ * by hand...
+ */
+#if 0
+ if (c == STOP_CHAR(tty))
+ dgap_cmdw(ch, RPAUSE, 0, 0);
+ else if (c == START_CHAR(tty))
+ dgap_cmdw(ch, RRESUME, 0, 0);
+ else
+ dgap_wmove(ch, &c, 1);
+#else
+ dgap_wmove(ch, &c, 1);
+#endif
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return;
+}
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgap_get_modem_info(struct channel_t *ch, unsigned int __user *value)
+{
+ int result;
+ u8 mstat;
+ ulong lock_flags;
+ int rc;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+
+ mstat = readb(&(ch->ch_bs->m_stat));
+ /* Append any outbound signals that might be pending... */
+ mstat |= ch->ch_mostat;
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ result = 0;
+
+ if (mstat & D_DTR(ch))
+ result |= TIOCM_DTR;
+ if (mstat & D_RTS(ch))
+ result |= TIOCM_RTS;
+ if (mstat & D_CTS(ch))
+ result |= TIOCM_CTS;
+ if (mstat & D_DSR(ch))
+ result |= TIOCM_DSR;
+ if (mstat & D_RI(ch))
+ result |= TIOCM_RI;
+ if (mstat & D_CD(ch))
+ result |= TIOCM_CD;
+
+ rc = put_user(result, value);
+
+ return rc;
+}
+
+/*
+ * dgap_set_modem_info()
+ *
+ * Set modem signals, called by ld.
+ */
+static int dgap_set_modem_info(struct tty_struct *tty, unsigned int command,
+ unsigned int __user *value)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int ret;
+ unsigned int arg;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ ret = get_user(arg, value);
+ if (ret)
+ return ret;
+
+ switch (command) {
+ case TIOCMBIS:
+ if (arg & TIOCM_RTS) {
+ ch->ch_mforce |= D_RTS(ch);
+ ch->ch_mval |= D_RTS(ch);
+ }
+
+ if (arg & TIOCM_DTR) {
+ ch->ch_mforce |= D_DTR(ch);
+ ch->ch_mval |= D_DTR(ch);
+ }
+
+ break;
+
+ case TIOCMBIC:
+ if (arg & TIOCM_RTS) {
+ ch->ch_mforce |= D_RTS(ch);
+ ch->ch_mval &= ~(D_RTS(ch));
+ }
+
+ if (arg & TIOCM_DTR) {
+ ch->ch_mforce |= D_DTR(ch);
+ ch->ch_mval &= ~(D_DTR(ch));
+ }
+
+ break;
+
+ case TIOCMSET:
+ ch->ch_mforce = D_DTR(ch)|D_RTS(ch);
+
+ if (arg & TIOCM_RTS)
+ ch->ch_mval |= D_RTS(ch);
+ else
+ ch->ch_mval &= ~(D_RTS(ch));
+
+ if (arg & TIOCM_DTR)
+ ch->ch_mval |= (D_DTR(ch));
+ else
+ ch->ch_mval &= ~(D_DTR(ch));
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digigeta()
+ *
+ * Ioctl to get the information for ditty.
+ *
+ *
+ *
+ */
+static int dgap_tty_digigeta(struct tty_struct *tty,
+ struct digi_t __user *retinfo)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ struct digi_t tmp;
+ ulong lock_flags;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ memcpy(&tmp, &ch->ch_digi, sizeof(tmp));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digiseta()
+ *
+ * Ioctl to set the information for ditty.
+ *
+ *
+ *
+ */
+static int dgap_tty_digiseta(struct tty_struct *tty,
+ struct digi_t __user *new_info)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ struct digi_t new_digi;
+ ulong lock_flags = 0;
+ unsigned long lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EFAULT;
+
+ if (copy_from_user(&new_digi, new_info, sizeof(struct digi_t)))
+ return -EFAULT;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ memcpy(&ch->ch_digi, &new_digi, sizeof(struct digi_t));
+
+ if (ch->ch_digi.digi_maxcps < 1)
+ ch->ch_digi.digi_maxcps = 1;
+
+ if (ch->ch_digi.digi_maxcps > 10000)
+ ch->ch_digi.digi_maxcps = 10000;
+
+ if (ch->ch_digi.digi_bufsize < 10)
+ ch->ch_digi.digi_bufsize = 10;
+
+ if (ch->ch_digi.digi_maxchar < 1)
+ ch->ch_digi.digi_maxchar = 1;
+
+ if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+ ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+ if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+ ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+ if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+ ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digigetedelay()
+ *
+ * Ioctl to get the current edelay setting.
+ *
+ *
+ *
+ */
+static int dgap_tty_digigetedelay(struct tty_struct *tty, int __user *retinfo)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ int tmp;
+ ulong lock_flags;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ tmp = readw(&(ch->ch_bs->edelay));
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digisetedelay()
+ *
+ * Ioctl to set the EDELAY setting
+ *
+ */
+static int dgap_tty_digisetedelay(struct tty_struct *tty, int __user *new_info)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int new_digi;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EFAULT;
+
+ if (copy_from_user(&new_digi, new_info, sizeof(int)))
+ return -EFAULT;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ writew((u16) new_digi, &(ch->ch_bs->edelay));
+
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digigetcustombaud()
+ *
+ * Ioctl to get the current custom baud rate setting.
+ */
+static int dgap_tty_digigetcustombaud(struct tty_struct *tty,
+ int __user *retinfo)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ int tmp;
+ ulong lock_flags;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ spin_lock_irqsave(&ch->ch_lock, lock_flags);
+ tmp = dgap_get_custom_baud(ch);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
+
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * dgap_tty_digisetcustombaud()
+ *
+ * Ioctl to set the custom baud rate setting
+ */
+static int dgap_tty_digisetcustombaud(struct tty_struct *tty,
+ int __user *new_info)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ uint new_rate;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EFAULT;
+
+
+ if (copy_from_user(&new_rate, new_info, sizeof(unsigned int)))
+ return -EFAULT;
+
+ if (bd->bd_flags & BD_FEP5PLUS) {
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ ch->ch_custom_speed = new_rate;
+
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ }
+
+ return 0;
+}
+
+/*
+ * dgap_set_termios()
+ */
+static void dgap_tty_set_termios(struct tty_struct *tty,
+ struct ktermios *old_termios)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ unsigned long lock_flags;
+ unsigned long lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ ch->ch_c_cflag = tty->termios.c_cflag;
+ ch->ch_c_iflag = tty->termios.c_iflag;
+ ch->ch_c_oflag = tty->termios.c_oflag;
+ ch->ch_c_lflag = tty->termios.c_lflag;
+ ch->ch_startc = tty->termios.c_cc[VSTART];
+ ch->ch_stopc = tty->termios.c_cc[VSTOP];
+
+ dgap_carrier(ch);
+ dgap_param(tty);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+static void dgap_tty_throttle(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ ch->ch_flags |= (CH_RXBLOCK);
+#if 1
+ dgap_cmdw(ch, RPAUSE, 0, 0);
+#endif
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+}
+
+static void dgap_tty_unthrottle(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ ch->ch_flags &= ~(CH_RXBLOCK);
+
+#if 1
+ dgap_cmdw(ch, RRESUME, 0, 0);
+#endif
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+static void dgap_tty_start(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ dgap_cmdw(ch, RESUMETX, 0, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+static void dgap_tty_stop(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ dgap_cmdw(ch, PAUSETX, 0, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+/*
+ * dgap_tty_flush_chars()
+ *
+ * Flush the cook buffer
+ *
+ * Note to self, and any other poor souls who venture here:
+ *
+ * flush in this case DOES NOT mean dispose of the data.
+ * instead, it means "stop buffering and send it if you
+ * haven't already." Just guess how I figured that out... SRW 2-Jun-98
+ *
+ * It is also always called in interrupt context - JAR 8-Sept-99
+ */
+static void dgap_tty_flush_chars(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ /* TODO: Do something here */
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+/*
+ * dgap_tty_flush_buffer()
+ *
+ * Flush Tx buffer (make in == out)
+ */
+static void dgap_tty_flush_buffer(struct tty_struct *tty)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ ulong lock_flags2;
+ u16 head;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ ch->ch_flags &= ~CH_STOP;
+ head = readw(&(ch->ch_bs->tx_head));
+ dgap_cmdw(ch, FLUSHTX, (u16) head, 0);
+ dgap_cmdw(ch, RESUMETX, 0, 0);
+ if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+ }
+ if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+ }
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ if (waitqueue_active(&tty->write_wait))
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+/*****************************************************************************
+ *
+ * The IOCTL function and all of its helpers
+ *
+ *****************************************************************************/
+
+/*
+ * dgap_tty_ioctl()
+ *
+ * The usual assortment of ioctl's
+ */
+static int dgap_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int rc;
+ u16 head;
+ ulong lock_flags = 0;
+ ulong lock_flags2 = 0;
+ void __user *uarg = (void __user *) arg;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -ENODEV;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -ENODEV;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -ENODEV;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ if (un->un_open_count <= 0) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ switch (cmd) {
+
+ /* Here are all the standard ioctl's that we MUST implement */
+
+ case TCSBRK:
+ /*
+ * TCSBRK is SVID version: non-zero arg --> no break
+ * this behaviour is exploited by tcdrain().
+ *
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds so we'll ask for something
+ * in the middle: 0.375 seconds.
+ */
+ rc = tty_check_change(tty);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = dgap_wait_for_drain(tty);
+
+ if (rc)
+ return -EINTR;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP))
+ dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+
+ case TCSBRKP:
+ /* support for POSIX tcsendbreak()
+
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds so we'll ask for something
+ * in the middle: 0.375 seconds.
+ */
+ rc = tty_check_change(tty);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = dgap_wait_for_drain(tty);
+ if (rc)
+ return -EINTR;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+
+ case TIOCSBRK:
+ /*
+ * FEP5 doesn't support turning on a break unconditionally.
+ * The FEP5 device will stop sending a break automatically
+ * after the specified time value that was sent when turning on
+ * the break.
+ */
+ rc = tty_check_change(tty);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = dgap_wait_for_drain(tty);
+ if (rc)
+ return -EINTR;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ dgap_cmdw(ch, SBREAK, (u16) SBREAK_TIME, 0);
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+
+ case TIOCCBRK:
+ /*
+ * FEP5 doesn't support turning off a break unconditionally.
+ * The FEP5 device will stop sending a break automatically
+ * after the specified time value that was sent when turning on
+ * the break.
+ */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return 0;
+
+ case TIOCGSOFTCAR:
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ rc = put_user(C_CLOCAL(tty) ? 1 : 0,
+ (unsigned long __user *) arg);
+ return rc;
+
+ case TIOCSSOFTCAR:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ rc = get_user(arg, (unsigned long __user *) arg);
+ if (rc)
+ return rc;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+ tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ dgap_param(tty);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+
+ case TIOCMGET:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_get_modem_info(ch, uarg);
+
+ case TIOCMBIS:
+ case TIOCMBIC:
+ case TIOCMSET:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_set_modem_info(tty, cmd, uarg);
+
+ /*
+ * Here are any additional ioctl's that we want to implement
+ */
+
+ case TCFLSH:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+ rc = tty_check_change(tty);
+ if (rc) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return rc;
+ }
+
+ if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) {
+ if (!(un->un_type == DGAP_PRINT)) {
+ head = readw(&(ch->ch_bs->rx_head));
+ writew(head, &(ch->ch_bs->rx_tail));
+ writeb(0, &(ch->ch_bs->orun));
+ }
+ }
+
+ if ((arg != TCOFLUSH) && (arg != TCIOFLUSH)) {
+ /* pretend we didn't recognize this IOCTL */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return -ENOIOCTLCMD;
+ }
+
+ ch->ch_flags &= ~CH_STOP;
+ head = readw(&(ch->ch_bs->tx_head));
+ dgap_cmdw(ch, FLUSHTX, (u16) head, 0);
+ dgap_cmdw(ch, RESUMETX, 0, 0);
+ if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+ }
+ if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+ }
+ if (waitqueue_active(&tty->write_wait))
+ wake_up_interruptible(&tty->write_wait);
+
+ /* Can't hold any locks when calling tty_wakeup! */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ tty_wakeup(tty);
+
+ /* pretend we didn't recognize this IOCTL */
+ return -ENOIOCTLCMD;
+
+ case TCSETSF:
+ case TCSETSW:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+ if (cmd == TCSETSF) {
+ /* flush rx */
+ ch->ch_flags &= ~CH_STOP;
+ head = readw(&(ch->ch_bs->rx_head));
+ writew(head, &(ch->ch_bs->rx_tail));
+ }
+
+ /* now wait for all the output to drain */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ rc = dgap_wait_for_drain(tty);
+ if (rc)
+ return -EINTR;
+
+ /* pretend we didn't recognize this */
+ return -ENOIOCTLCMD;
+
+ case TCSETAW:
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ rc = dgap_wait_for_drain(tty);
+ if (rc)
+ return -EINTR;
+
+ /* pretend we didn't recognize this */
+ return -ENOIOCTLCMD;
+
+ case TCXONC:
+ /*
+ * The Linux Line Discipline (LD) would do this for us if we
+ * let it, but we have the special firmware options to do this
+ * the "right way" regardless of hardware or software flow
+ * control so we'll do it outselves instead of letting the LD
+ * do it.
+ */
+ rc = tty_check_change(tty);
+ if (rc) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return rc;
+ }
+
+ switch (arg) {
+
+ case TCOON:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ dgap_tty_start(tty);
+ return 0;
+ case TCOOFF:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ dgap_tty_stop(tty);
+ return 0;
+ case TCION:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ /* Make the ld do it */
+ return -ENOIOCTLCMD;
+ case TCIOFF:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ /* Make the ld do it */
+ return -ENOIOCTLCMD;
+ default:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return -EINVAL;
+ }
+
+ case DIGI_GETA:
+ /* get information for ditty */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digigeta(tty, uarg);
+
+ case DIGI_SETAW:
+ case DIGI_SETAF:
+
+ /* set information for ditty */
+ if (cmd == (DIGI_SETAW)) {
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ rc = dgap_wait_for_drain(tty);
+ if (rc)
+ return -EINTR;
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+ } else
+ tty_ldisc_flush(tty);
+ /* fall thru */
+
+ case DIGI_SETA:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digiseta(tty, uarg);
+
+ case DIGI_GEDELAY:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digigetedelay(tty, uarg);
+
+ case DIGI_SEDELAY:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digisetedelay(tty, uarg);
+
+ case DIGI_GETCUSTOMBAUD:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digigetcustombaud(tty, uarg);
+
+ case DIGI_SETCUSTOMBAUD:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return dgap_tty_digisetcustombaud(tty, uarg);
+
+ case DIGI_RESET_PORT:
+ dgap_firmware_reset_port(ch);
+ dgap_param(tty);
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return 0;
+
+ default:
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int dgap_after_config_loaded(int board)
+{
+ /*
+ * Initialize KME waitqueues...
+ */
+ init_waitqueue_head(&(dgap_board[board]->kme_wait));
+
+ /*
+ * allocate flip buffer for board.
+ */
+ dgap_board[board]->flipbuf = kmalloc(MYFLIPLEN, GFP_KERNEL);
+ if (!dgap_board[board]->flipbuf)
+ return -ENOMEM;
+
+ dgap_board[board]->flipflagbuf = kmalloc(MYFLIPLEN, GFP_KERNEL);
+ if (!dgap_board[board]->flipflagbuf) {
+ kfree(dgap_board[board]->flipbuf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Create pr and tty device entries
+ */
+static int dgap_tty_register_ports(struct board_t *brd)
+{
+ struct channel_t *ch;
+ int i;
+
+ brd->serial_ports = kcalloc(brd->nasync, sizeof(*brd->serial_ports),
+ GFP_KERNEL);
+ if (!brd->serial_ports)
+ return -ENOMEM;
+
+ brd->printer_ports = kcalloc(brd->nasync, sizeof(*brd->printer_ports),
+ GFP_KERNEL);
+ if (!brd->printer_ports) {
+ kfree(brd->serial_ports);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < brd->nasync; i++) {
+ tty_port_init(&brd->serial_ports[i]);
+ tty_port_init(&brd->printer_ports[i]);
+ }
+
+ ch = brd->channels[0];
+ for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
+
+ struct device *classp;
+
+ classp = tty_port_register_device(&brd->serial_ports[i],
+ brd->serial_driver,
+ brd->firstminor + i, NULL);
+
+ dgap_create_tty_sysfs(&ch->ch_tun, classp);
+ ch->ch_tun.un_sysfs = classp;
+
+ classp = tty_port_register_device(&brd->printer_ports[i],
+ brd->print_driver,
+ brd->firstminor + i, NULL);
+
+ dgap_create_tty_sysfs(&ch->ch_pun, classp);
+ ch->ch_pun.un_sysfs = classp;
+ }
+ dgap_create_ports_sysfiles(brd);
+
+ return 0;
+}
+
+/*
+ * Copies the BIOS code from the user to the board,
+ * and starts the BIOS running.
+ */
+static void dgap_do_bios_load(struct board_t *brd, const u8 *ubios, int len)
+{
+ u8 __iomem *addr;
+ uint offset;
+ int i;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase)
+ return;
+
+ addr = brd->re_map_membase;
+
+ /*
+ * clear POST area
+ */
+ for (i = 0; i < 16; i++)
+ writeb(0, addr + POSTAREA + i);
+
+ /*
+ * Download bios
+ */
+ offset = 0x1000;
+ memcpy_toio(addr + offset, ubios, len);
+
+ writel(0x0bf00401, addr);
+ writel(0, (addr + 4));
+
+ /* Clear the reset, and change states. */
+ writeb(FEPCLR, brd->re_map_port);
+}
+
+/*
+ * Checks to see if the BIOS completed running on the card.
+ */
+static int dgap_test_bios(struct board_t *brd)
+{
+ u8 __iomem *addr;
+ u16 word;
+ u16 err1;
+ u16 err2;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase)
+ return -EINVAL;
+
+ addr = brd->re_map_membase;
+ word = readw(addr + POSTAREA);
+
+ /*
+ * It can take 5-6 seconds for a board to
+ * pass the bios self test and post results.
+ * Give it 10 seconds.
+ */
+ brd->wait_for_bios = 0;
+ while (brd->wait_for_bios < 1000) {
+ /* Check to see if BIOS thinks board is good. (GD). */
+ if (word == *(u16 *) "GD")
+ return 0;
+ msleep_interruptible(10);
+ brd->wait_for_bios++;
+ word = readw(addr + POSTAREA);
+ }
+
+ /* Gave up on board after too long of time taken */
+ err1 = readw(addr + SEQUENCE);
+ err2 = readw(addr + ERROR);
+ pr_warn("dgap: %s failed diagnostics. Error #(%x,%x).\n",
+ brd->name, err1, err2);
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOBIOS;
+
+ return -EIO;
+}
+
+/*
+ * Copies the FEP code from the user to the board,
+ * and starts the FEP running.
+ */
+static void dgap_do_fep_load(struct board_t *brd, const u8 *ufep, int len)
+{
+ u8 __iomem *addr;
+ uint offset;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase)
+ return;
+
+ addr = brd->re_map_membase;
+
+ /*
+ * Download FEP
+ */
+ offset = 0x1000;
+ memcpy_toio(addr + offset, ufep, len);
+
+ /*
+ * If board is a concentrator product, we need to give
+ * it its config string describing how the concentrators look.
+ */
+ if ((brd->type == PCX) || (brd->type == PEPC)) {
+ u8 string[100];
+ u8 __iomem *config;
+ u8 *xconfig;
+ int i = 0;
+
+ xconfig = dgap_create_config_string(brd, string);
+
+ /* Write string to board memory */
+ config = addr + CONFIG;
+ for (; i < CONFIGSIZE; i++, config++, xconfig++) {
+ writeb(*xconfig, config);
+ if ((*xconfig & 0xff) == 0xff)
+ break;
+ }
+ }
+
+ writel(0xbfc01004, (addr + 0xc34));
+ writel(0x3, (addr + 0xc30));
+
+}
+
+/*
+ * Waits for the FEP to report thats its ready for us to use.
+ */
+static int dgap_test_fep(struct board_t *brd)
+{
+ u8 __iomem *addr;
+ u16 word;
+ u16 err1;
+ u16 err2;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase)
+ return -EINVAL;
+
+ addr = brd->re_map_membase;
+ word = readw(addr + FEPSTAT);
+
+ /*
+ * It can take 2-3 seconds for the FEP to
+ * be up and running. Give it 5 secs.
+ */
+ brd->wait_for_fep = 0;
+ while (brd->wait_for_fep < 500) {
+ /* Check to see if FEP is up and running now. */
+ if (word == *(u16 *) "OS") {
+ /*
+ * Check to see if the board can support FEP5+ commands.
+ */
+ word = readw(addr + FEP5_PLUS);
+ if (word == *(u16 *) "5A")
+ brd->bd_flags |= BD_FEP5PLUS;
+
+ return 0;
+ }
+ msleep_interruptible(10);
+ brd->wait_for_fep++;
+ word = readw(addr + FEPSTAT);
+ }
+
+ /* Gave up on board after too long of time taken */
+ err1 = readw(addr + SEQUENCE);
+ err2 = readw(addr + ERROR);
+ pr_warn("dgap: FEPOS for %s not functioning. Error #(%x,%x).\n",
+ brd->name, err1, err2);
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+
+ return -EIO;
+}
+
+/*
+ * Physically forces the FEP5 card to reset itself.
+ */
+static void dgap_do_reset_board(struct board_t *brd)
+{
+ u8 check;
+ u32 check1;
+ u32 check2;
+ int i;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) ||
+ !brd->re_map_membase || !brd->re_map_port)
+ return;
+
+ /* FEPRST does not vary among supported boards */
+ writeb(FEPRST, brd->re_map_port);
+
+ for (i = 0; i <= 1000; i++) {
+ check = readb(brd->re_map_port) & 0xe;
+ if (check == FEPRST)
+ break;
+ udelay(10);
+
+ }
+ if (i > 1000) {
+ pr_warn("dgap: Board not resetting... Failing board.\n");
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+ return;
+ }
+
+ /*
+ * Make sure there really is memory out there.
+ */
+ writel(0xa55a3cc3, (brd->re_map_membase + LOWMEM));
+ writel(0x5aa5c33c, (brd->re_map_membase + HIGHMEM));
+ check1 = readl(brd->re_map_membase + LOWMEM);
+ check2 = readl(brd->re_map_membase + HIGHMEM);
+
+ if ((check1 != 0xa55a3cc3) || (check2 != 0x5aa5c33c)) {
+ pr_warn("dgap: No memory at %p for board.\n",
+ brd->re_map_membase);
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+ return;
+ }
+}
+
+#ifdef DIGI_CONCENTRATORS_SUPPORTED
+/*
+ * Sends a concentrator image into the FEP5 board.
+ */
+static void dgap_do_conc_load(struct board_t *brd, u8 *uaddr, int len)
+{
+ char __iomem *vaddr;
+ u16 offset;
+ struct downld_t *to_dp;
+
+ if (!brd || (brd->magic != DGAP_BOARD_MAGIC) || !brd->re_map_membase)
+ return;
+
+ vaddr = brd->re_map_membase;
+
+ offset = readw((u16 *) (vaddr + DOWNREQ));
+ to_dp = (struct downld_t *) (vaddr + (int) offset);
+ memcpy_toio(to_dp, uaddr, len);
+
+ /* Tell card we have data for it */
+ writew(0, vaddr + (DOWNREQ));
+
+ brd->conc_dl_status = NO_PENDING_CONCENTRATOR_REQUESTS;
+}
+#endif
+
+#define EXPANSION_ROM_SIZE (64 * 1024)
+#define FEP5_ROM_MAGIC (0xFEFFFFFF)
+
+static void dgap_get_vpd(struct board_t *brd)
+{
+ u32 magic;
+ u32 base_offset;
+ u16 rom_offset;
+ u16 vpd_offset;
+ u16 image_length;
+ u16 i;
+ u8 byte1;
+ u8 byte2;
+
+ /*
+ * Poke the magic number at the PCI Rom Address location.
+ * If VPD is supported, the value read from that address
+ * will be non-zero.
+ */
+ magic = FEP5_ROM_MAGIC;
+ pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic);
+ pci_read_config_dword(brd->pdev, PCI_ROM_ADDRESS, &magic);
+
+ /* VPD not supported, bail */
+ if (!magic)
+ return;
+
+ /*
+ * To get to the OTPROM memory, we have to send the boards base
+ * address or'ed with 1 into the PCI Rom Address location.
+ */
+ magic = brd->membase | 0x01;
+ pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic);
+ pci_read_config_dword(brd->pdev, PCI_ROM_ADDRESS, &magic);
+
+ byte1 = readb(brd->re_map_membase);
+ byte2 = readb(brd->re_map_membase + 1);
+
+ /*
+ * If the board correctly swapped to the OTPROM memory,
+ * the first 2 bytes (header) should be 0x55, 0xAA
+ */
+ if (byte1 == 0x55 && byte2 == 0xAA) {
+
+ base_offset = 0;
+
+ /*
+ * We have to run through all the OTPROM memory looking
+ * for the VPD offset.
+ */
+ while (base_offset <= EXPANSION_ROM_SIZE) {
+
+ /*
+ * Lots of magic numbers here.
+ *
+ * The VPD offset is located inside the ROM Data
+ * Structure.
+ *
+ * We also have to remember the length of each
+ * ROM Data Structure, so we can "hop" to the next
+ * entry if the VPD isn't in the current
+ * ROM Data Structure.
+ */
+ rom_offset = readw(brd->re_map_membase +
+ base_offset + 0x18);
+ image_length = readw(brd->re_map_membase +
+ rom_offset + 0x10) * 512;
+ vpd_offset = readw(brd->re_map_membase +
+ rom_offset + 0x08);
+
+ /* Found the VPD entry */
+ if (vpd_offset)
+ break;
+
+ /* We didn't find a VPD entry, go to next ROM entry. */
+ base_offset += image_length;
+
+ byte1 = readb(brd->re_map_membase + base_offset);
+ byte2 = readb(brd->re_map_membase + base_offset + 1);
+
+ /*
+ * If the new ROM offset doesn't have 0x55, 0xAA
+ * as its header, we have run out of ROM.
+ */
+ if (byte1 != 0x55 || byte2 != 0xAA)
+ break;
+ }
+
+ /*
+ * If we have a VPD offset, then mark the board
+ * as having a valid VPD, and copy VPDSIZE (512) bytes of
+ * that VPD to the buffer we have in our board structure.
+ */
+ if (vpd_offset) {
+ brd->bd_flags |= BD_HAS_VPD;
+ for (i = 0; i < VPDSIZE; i++) {
+ brd->vpd[i] = readb(brd->re_map_membase +
+ vpd_offset + i);
+ }
+ }
+ }
+
+ /*
+ * We MUST poke the magic number at the PCI Rom Address location again.
+ * This makes the card report the regular board memory back to us,
+ * rather than the OTPROM memory.
+ */
+ magic = FEP5_ROM_MAGIC;
+ pci_write_config_dword(brd->pdev, PCI_ROM_ADDRESS, magic);
+}
+
+/*
+ * Our board poller function.
+ */
+static void dgap_poll_tasklet(unsigned long data)
+{
+ struct board_t *bd = (struct board_t *) data;
+ ulong lock_flags;
+ char __iomem *vaddr;
+ u16 head, tail;
+
+ if (!bd || (bd->magic != DGAP_BOARD_MAGIC))
+ return;
+
+ if (bd->inhibit_poller)
+ return;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+
+ vaddr = bd->re_map_membase;
+
+ /*
+ * If board is ready, parse deeper to see if there is anything to do.
+ */
+ if (bd->state == BOARD_READY) {
+
+ struct ev_t __iomem *eaddr;
+
+ if (!bd->re_map_membase) {
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+ if (!bd->re_map_port) {
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+
+ if (!bd->nasync)
+ goto out;
+
+ eaddr = (struct ev_t __iomem *) (vaddr + EVBUF);
+
+ /* Get our head and tail */
+ head = readw(&(eaddr->ev_head));
+ tail = readw(&(eaddr->ev_tail));
+
+ /*
+ * If there is an event pending. Go service it.
+ */
+ if (head != tail) {
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ dgap_event(bd);
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ }
+
+out:
+ /*
+ * If board is doing interrupts, ACK the interrupt.
+ */
+ if (bd && bd->intr_running)
+ readb(bd->re_map_port + 2);
+
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return;
+ }
+
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+}
+
+/*=======================================================================
+ *
+ * dgap_cmdb - Sends a 2 byte command to the FEP.
+ *
+ * ch - Pointer to channel structure.
+ * cmd - Command to be sent.
+ * byte1 - Integer containing first byte to be sent.
+ * byte2 - Integer containing second byte to be sent.
+ * ncmds - Wait until ncmds or fewer cmds are left
+ * in the cmd buffer before returning.
+ *
+ *=======================================================================*/
+static void dgap_cmdb(struct channel_t *ch, u8 cmd, u8 byte1,
+ u8 byte2, uint ncmds)
+{
+ char __iomem *vaddr;
+ struct __iomem cm_t *cm_addr;
+ uint count;
+ uint n;
+ u16 head;
+ u16 tail;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * Check if board is still alive.
+ */
+ if (ch->ch_bd->state == BOARD_FAILED)
+ return;
+
+ /*
+ * Make sure the pointers are in range before
+ * writing to the FEP memory.
+ */
+ vaddr = ch->ch_bd->re_map_membase;
+
+ if (!vaddr)
+ return;
+
+ cm_addr = (struct cm_t __iomem *) (vaddr + CMDBUF);
+ head = readw(&(cm_addr->cm_head));
+
+ /*
+ * Forget it if pointers out of range.
+ */
+ if (head >= (CMDMAX - CMDSTART) || (head & 03)) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+
+ /*
+ * Put the data in the circular command buffer.
+ */
+ writeb(cmd, (vaddr + head + CMDSTART + 0));
+ writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1));
+ writeb(byte1, (vaddr + head + CMDSTART + 2));
+ writeb(byte2, (vaddr + head + CMDSTART + 3));
+
+ head = (head + 4) & (CMDMAX - CMDSTART - 4);
+
+ writew(head, &(cm_addr->cm_head));
+
+ /*
+ * Wait if necessary before updating the head
+ * pointer to limit the number of outstanding
+ * commands to the FEP. If the time spent waiting
+ * is outlandish, declare the FEP dead.
+ */
+ for (count = dgap_count ;;) {
+
+ head = readw(&(cm_addr->cm_head));
+ tail = readw(&(cm_addr->cm_tail));
+
+ n = (head - tail) & (CMDMAX - CMDSTART - 4);
+
+ if (n <= ncmds * sizeof(struct cm_t))
+ break;
+
+ if (--count == 0) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+ udelay(10);
+ }
+}
+
+/*=======================================================================
+ *
+ * dgap_cmdw - Sends a 1 word command to the FEP.
+ *
+ * ch - Pointer to channel structure.
+ * cmd - Command to be sent.
+ * word - Integer containing word to be sent.
+ * ncmds - Wait until ncmds or fewer cmds are left
+ * in the cmd buffer before returning.
+ *
+ *=======================================================================*/
+static void dgap_cmdw(struct channel_t *ch, u8 cmd, u16 word, uint ncmds)
+{
+ char __iomem *vaddr;
+ struct __iomem cm_t *cm_addr;
+ uint count;
+ uint n;
+ u16 head;
+ u16 tail;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * Check if board is still alive.
+ */
+ if (ch->ch_bd->state == BOARD_FAILED)
+ return;
+
+ /*
+ * Make sure the pointers are in range before
+ * writing to the FEP memory.
+ */
+ vaddr = ch->ch_bd->re_map_membase;
+ if (!vaddr)
+ return;
+
+ cm_addr = (struct cm_t __iomem *) (vaddr + CMDBUF);
+ head = readw(&(cm_addr->cm_head));
+
+ /*
+ * Forget it if pointers out of range.
+ */
+ if (head >= (CMDMAX - CMDSTART) || (head & 03)) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+
+ /*
+ * Put the data in the circular command buffer.
+ */
+ writeb(cmd, (vaddr + head + CMDSTART + 0));
+ writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1));
+ writew((u16) word, (vaddr + head + CMDSTART + 2));
+
+ head = (head + 4) & (CMDMAX - CMDSTART - 4);
+
+ writew(head, &(cm_addr->cm_head));
+
+ /*
+ * Wait if necessary before updating the head
+ * pointer to limit the number of outstanding
+ * commands to the FEP. If the time spent waiting
+ * is outlandish, declare the FEP dead.
+ */
+ for (count = dgap_count ;;) {
+
+ head = readw(&(cm_addr->cm_head));
+ tail = readw(&(cm_addr->cm_tail));
+
+ n = (head - tail) & (CMDMAX - CMDSTART - 4);
+
+ if (n <= ncmds * sizeof(struct cm_t))
+ break;
+
+ if (--count == 0) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+ udelay(10);
+ }
+}
+
+/*=======================================================================
+ *
+ * dgap_cmdw_ext - Sends a extended word command to the FEP.
+ *
+ * ch - Pointer to channel structure.
+ * cmd - Command to be sent.
+ * word - Integer containing word to be sent.
+ * ncmds - Wait until ncmds or fewer cmds are left
+ * in the cmd buffer before returning.
+ *
+ *=======================================================================*/
+static void dgap_cmdw_ext(struct channel_t *ch, u16 cmd, u16 word, uint ncmds)
+{
+ char __iomem *vaddr;
+ struct __iomem cm_t *cm_addr;
+ uint count;
+ uint n;
+ u16 head;
+ u16 tail;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * Check if board is still alive.
+ */
+ if (ch->ch_bd->state == BOARD_FAILED)
+ return;
+
+ /*
+ * Make sure the pointers are in range before
+ * writing to the FEP memory.
+ */
+ vaddr = ch->ch_bd->re_map_membase;
+ if (!vaddr)
+ return;
+
+ cm_addr = (struct cm_t __iomem *) (vaddr + CMDBUF);
+ head = readw(&(cm_addr->cm_head));
+
+ /*
+ * Forget it if pointers out of range.
+ */
+ if (head >= (CMDMAX - CMDSTART) || (head & 03)) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+
+ /*
+ * Put the data in the circular command buffer.
+ */
+
+ /* Write an FF to tell the FEP that we want an extended command */
+ writeb((u8) 0xff, (vaddr + head + CMDSTART + 0));
+
+ writeb((u8) ch->ch_portnum, (vaddr + head + CMDSTART + 1));
+ writew((u16) cmd, (vaddr + head + CMDSTART + 2));
+
+ /*
+ * If the second part of the command won't fit,
+ * put it at the beginning of the circular buffer.
+ */
+ if (((head + 4) >= ((CMDMAX - CMDSTART)) || (head & 03)))
+ writew((u16) word, (vaddr + CMDSTART));
+ else
+ writew((u16) word, (vaddr + head + CMDSTART + 4));
+
+ head = (head + 8) & (CMDMAX - CMDSTART - 4);
+
+ writew(head, &(cm_addr->cm_head));
+
+ /*
+ * Wait if necessary before updating the head
+ * pointer to limit the number of outstanding
+ * commands to the FEP. If the time spent waiting
+ * is outlandish, declare the FEP dead.
+ */
+ for (count = dgap_count ;;) {
+
+ head = readw(&(cm_addr->cm_head));
+ tail = readw(&(cm_addr->cm_tail));
+
+ n = (head - tail) & (CMDMAX - CMDSTART - 4);
+
+ if (n <= ncmds * sizeof(struct cm_t))
+ break;
+
+ if (--count == 0) {
+ ch->ch_bd->state = BOARD_FAILED;
+ return;
+ }
+ udelay(10);
+ }
+}
+
+/*=======================================================================
+ *
+ * dgap_wmove - Write data to FEP buffer.
+ *
+ * ch - Pointer to channel structure.
+ * buf - Poiter to characters to be moved.
+ * cnt - Number of characters to move.
+ *
+ *=======================================================================*/
+static void dgap_wmove(struct channel_t *ch, char *buf, uint cnt)
+{
+ int n;
+ char __iomem *taddr;
+ struct bs_t __iomem *bs;
+ u16 head;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * Check parameters.
+ */
+ bs = ch->ch_bs;
+ head = readw(&(bs->tx_head));
+
+ /*
+ * If pointers are out of range, just return.
+ */
+ if ((cnt > ch->ch_tsize) ||
+ (unsigned)(head - ch->ch_tstart) >= ch->ch_tsize)
+ return;
+
+ /*
+ * If the write wraps over the top of the circular buffer,
+ * move the portion up to the wrap point, and reset the
+ * pointers to the bottom.
+ */
+ n = ch->ch_tstart + ch->ch_tsize - head;
+
+ if (cnt >= n) {
+ cnt -= n;
+ taddr = ch->ch_taddr + head;
+ memcpy_toio(taddr, buf, n);
+ head = ch->ch_tstart;
+ buf += n;
+ }
+
+ /*
+ * Move rest of data.
+ */
+ taddr = ch->ch_taddr + head;
+ n = cnt;
+ memcpy_toio(taddr, buf, n);
+ head += cnt;
+
+ writew(head, &(bs->tx_head));
+}
+
+/*
+ * Retrives the current custom baud rate from FEP memory,
+ * and returns it back to the user.
+ * Returns 0 on error.
+ */
+static uint dgap_get_custom_baud(struct channel_t *ch)
+{
+ u8 __iomem *vaddr;
+ ulong offset;
+ uint value;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+
+ if (!ch->ch_bd || ch->ch_bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+
+ if (!(ch->ch_bd->bd_flags & BD_FEP5PLUS))
+ return 0;
+
+ vaddr = ch->ch_bd->re_map_membase;
+
+ if (!vaddr)
+ return 0;
+
+ /*
+ * Go get from fep mem, what the fep
+ * believes the custom baud rate is.
+ */
+ offset = (ioread16(vaddr + ECS_SEG) << 4) + (ch->ch_portnum * 0x28)
+ + LINE_SPEED;
+
+ value = readw(vaddr + offset);
+ return value;
+}
+
+/*
+ * Calls the firmware to reset this channel.
+ */
+static void dgap_firmware_reset_port(struct channel_t *ch)
+{
+ dgap_cmdb(ch, CHRESET, 0, 0, 0);
+
+ /*
+ * Now that the channel is reset, we need to make sure
+ * all the current settings get reapplied to the port
+ * in the firmware.
+ *
+ * So we will set the driver's cache of firmware
+ * settings all to 0, and then call param.
+ */
+ ch->ch_fepiflag = 0;
+ ch->ch_fepcflag = 0;
+ ch->ch_fepoflag = 0;
+ ch->ch_fepstartc = 0;
+ ch->ch_fepstopc = 0;
+ ch->ch_fepastartc = 0;
+ ch->ch_fepastopc = 0;
+ ch->ch_mostat = 0;
+ ch->ch_hflow = 0;
+}
+
+/*=======================================================================
+ *
+ * dgap_param - Set Digi parameters.
+ *
+ * struct tty_struct * - TTY for port.
+ *
+ *=======================================================================*/
+static int dgap_param(struct tty_struct *tty)
+{
+ struct ktermios *ts;
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct bs_t __iomem *bs;
+ struct un_t *un;
+ u16 head;
+ u16 cflag;
+ u16 iflag;
+ u8 mval;
+ u8 hflow;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EIO;
+
+ un = (struct un_t *) tty->driver_data;
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return -EIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return -EIO;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ bs = ch->ch_bs;
+ if (!bs)
+ return -EIO;
+
+ ts = &tty->termios;
+
+ /*
+ * If baud rate is zero, flush queues, and set mval to drop DTR.
+ */
+ if ((ch->ch_c_cflag & (CBAUD)) == 0) {
+
+ /* flush rx */
+ head = readw(&(ch->ch_bs->rx_head));
+ writew(head, &(ch->ch_bs->rx_tail));
+
+ /* flush tx */
+ head = readw(&(ch->ch_bs->tx_head));
+ writew(head, &(ch->ch_bs->tx_tail));
+
+ ch->ch_flags |= (CH_BAUD0);
+
+ /* Drop RTS and DTR */
+ ch->ch_mval &= ~(D_RTS(ch)|D_DTR(ch));
+ mval = D_DTR(ch) | D_RTS(ch);
+ ch->ch_baud_info = 0;
+
+ } else if (ch->ch_custom_speed && (bd->bd_flags & BD_FEP5PLUS)) {
+ /*
+ * Tell the fep to do the command
+ */
+
+ dgap_cmdw_ext(ch, 0xff01, ch->ch_custom_speed, 0);
+
+ /*
+ * Now go get from fep mem, what the fep
+ * believes the custom baud rate is.
+ */
+ ch->ch_custom_speed = dgap_get_custom_baud(ch);
+ ch->ch_baud_info = ch->ch_custom_speed;
+
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+ ch->ch_mval |= (D_RTS(ch)|D_DTR(ch));
+ }
+ mval = D_DTR(ch) | D_RTS(ch);
+
+ } else {
+ /*
+ * Set baud rate, character size, and parity.
+ */
+
+
+ int iindex = 0;
+ int jindex = 0;
+ int baud = 0;
+
+ ulong bauds[4][16] = {
+ { /* slowbaud */
+ 0, 50, 75, 110,
+ 134, 150, 200, 300,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* slowbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud */
+ 0, 57600, 76800, 115200,
+ 14400, 57600, 230400, 76800,
+ 115200, 230400, 28800, 460800,
+ 921600, 9600, 19200, 38400 },
+ { /* fastbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 }
+ };
+
+ /*
+ * Only use the TXPrint baud rate if the
+ * terminal unit is NOT open
+ */
+ if (!(ch->ch_tun.un_flags & UN_ISOPEN) &&
+ (un->un_type == DGAP_PRINT))
+ baud = C_BAUD(ch->ch_pun.un_tty) & 0xff;
+ else
+ baud = C_BAUD(ch->ch_tun.un_tty) & 0xff;
+
+ if (ch->ch_c_cflag & CBAUDEX)
+ iindex = 1;
+
+ if (ch->ch_digi.digi_flags & DIGI_FAST)
+ iindex += 2;
+
+ jindex = baud;
+
+ if ((iindex >= 0) && (iindex < 4) &&
+ (jindex >= 0) && (jindex < 16))
+ baud = bauds[iindex][jindex];
+ else
+ baud = 0;
+
+ if (baud == 0)
+ baud = 9600;
+
+ ch->ch_baud_info = baud;
+
+ /*
+ * CBAUD has bit position 0x1000 set these days to
+ * indicate Linux baud rate remap.
+ * We use a different bit assignment for high speed.
+ * Clear this bit out while grabbing the parts of
+ * "cflag" we want.
+ */
+ cflag = ch->ch_c_cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB |
+ CSTOPB | CSIZE);
+
+ /*
+ * HUPCL bit is used by FEP to indicate fast baud
+ * table is to be used.
+ */
+ if ((ch->ch_digi.digi_flags & DIGI_FAST) ||
+ (ch->ch_c_cflag & CBAUDEX))
+ cflag |= HUPCL;
+
+ if ((ch->ch_c_cflag & CBAUDEX) &&
+ !(ch->ch_digi.digi_flags & DIGI_FAST)) {
+ /*
+ * The below code is trying to guarantee that only
+ * baud rates 115200, 230400, 460800, 921600 are
+ * remapped. We use exclusive or because the various
+ * baud rates share common bit positions and therefore
+ * can't be tested for easily.
+ */
+ tcflag_t tcflag = (ch->ch_c_cflag & CBAUD) | CBAUDEX;
+ int baudpart = 0;
+
+ /*
+ * Map high speed requests to index
+ * into FEP's baud table
+ */
+ switch (tcflag) {
+ case B57600:
+ baudpart = 1;
+ break;
+#ifdef B76800
+ case B76800:
+ baudpart = 2;
+ break;
+#endif
+ case B115200:
+ baudpart = 3;
+ break;
+ case B230400:
+ baudpart = 9;
+ break;
+ case B460800:
+ baudpart = 11;
+ break;
+#ifdef B921600
+ case B921600:
+ baudpart = 12;
+ break;
+#endif
+ default:
+ baudpart = 0;
+ }
+
+ if (baudpart)
+ cflag = (cflag & ~(CBAUD | CBAUDEX)) | baudpart;
+ }
+
+ cflag &= 0xffff;
+
+ if (cflag != ch->ch_fepcflag) {
+ ch->ch_fepcflag = (u16) (cflag & 0xffff);
+
+ /*
+ * Okay to have channel and board
+ * locks held calling this
+ */
+ dgap_cmdw(ch, SCFLAG, (u16) cflag, 0);
+ }
+
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+ ch->ch_mval |= (D_RTS(ch)|D_DTR(ch));
+ }
+ mval = D_DTR(ch) | D_RTS(ch);
+ }
+
+ /*
+ * Get input flags.
+ */
+ iflag = ch->ch_c_iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK |
+ INPCK | ISTRIP | IXON | IXANY | IXOFF);
+
+ if ((ch->ch_startc == _POSIX_VDISABLE) ||
+ (ch->ch_stopc == _POSIX_VDISABLE)) {
+ iflag &= ~(IXON | IXOFF);
+ ch->ch_c_iflag &= ~(IXON | IXOFF);
+ }
+
+ /*
+ * Only the IBM Xr card can switch between
+ * 232 and 422 modes on the fly
+ */
+ if (bd->device == PCI_DEV_XR_IBM_DID) {
+ if (ch->ch_digi.digi_flags & DIGI_422)
+ dgap_cmdb(ch, SCOMMODE, MODE_422, 0, 0);
+ else
+ dgap_cmdb(ch, SCOMMODE, MODE_232, 0, 0);
+ }
+
+ if (ch->ch_digi.digi_flags & DIGI_ALTPIN)
+ iflag |= IALTPIN;
+
+ if (iflag != ch->ch_fepiflag) {
+ ch->ch_fepiflag = iflag;
+
+ /* Okay to have channel and board locks held calling this */
+ dgap_cmdw(ch, SIFLAG, (u16) ch->ch_fepiflag, 0);
+ }
+
+ /*
+ * Select hardware handshaking.
+ */
+ hflow = 0;
+
+ if (ch->ch_c_cflag & CRTSCTS)
+ hflow |= (D_RTS(ch) | D_CTS(ch));
+ if (ch->ch_digi.digi_flags & RTSPACE)
+ hflow |= D_RTS(ch);
+ if (ch->ch_digi.digi_flags & DTRPACE)
+ hflow |= D_DTR(ch);
+ if (ch->ch_digi.digi_flags & CTSPACE)
+ hflow |= D_CTS(ch);
+ if (ch->ch_digi.digi_flags & DSRPACE)
+ hflow |= D_DSR(ch);
+ if (ch->ch_digi.digi_flags & DCDPACE)
+ hflow |= D_CD(ch);
+
+ if (hflow != ch->ch_hflow) {
+ ch->ch_hflow = hflow;
+
+ /* Okay to have channel and board locks held calling this */
+ dgap_cmdb(ch, SHFLOW, (u8) hflow, 0xff, 0);
+ }
+
+ /*
+ * Set RTS and/or DTR Toggle if needed,
+ * but only if product is FEP5+ based.
+ */
+ if (bd->bd_flags & BD_FEP5PLUS) {
+ u16 hflow2 = 0;
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)
+ hflow2 |= (D_RTS(ch));
+ if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE)
+ hflow2 |= (D_DTR(ch));
+
+ dgap_cmdw_ext(ch, 0xff03, hflow2, 0);
+ }
+
+ /*
+ * Set modem control lines.
+ */
+
+ mval ^= ch->ch_mforce & (mval ^ ch->ch_mval);
+
+ if (ch->ch_mostat ^ mval) {
+ ch->ch_mostat = mval;
+
+ /* Okay to have channel and board locks held calling this */
+ dgap_cmdb(ch, SMODEM, (u8) mval, D_RTS(ch)|D_DTR(ch), 0);
+ }
+
+ /*
+ * Read modem signals, and then call carrier function.
+ */
+ ch->ch_mistat = readb(&(bs->m_stat));
+ dgap_carrier(ch);
+
+ /*
+ * Set the start and stop characters.
+ */
+ if (ch->ch_startc != ch->ch_fepstartc ||
+ ch->ch_stopc != ch->ch_fepstopc) {
+ ch->ch_fepstartc = ch->ch_startc;
+ ch->ch_fepstopc = ch->ch_stopc;
+
+ /* Okay to have channel and board locks held calling this */
+ dgap_cmdb(ch, SFLOWC, ch->ch_fepstartc, ch->ch_fepstopc, 0);
+ }
+
+ /*
+ * Set the Auxiliary start and stop characters.
+ */
+ if (ch->ch_astartc != ch->ch_fepastartc ||
+ ch->ch_astopc != ch->ch_fepastopc) {
+ ch->ch_fepastartc = ch->ch_astartc;
+ ch->ch_fepastopc = ch->ch_astopc;
+
+ /* Okay to have channel and board locks held calling this */
+ dgap_cmdb(ch, SAFLOWC, ch->ch_fepastartc, ch->ch_fepastopc, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * dgap_parity_scan()
+ *
+ * Convert the FEP5 way of reporting parity errors and breaks into
+ * the Linux line discipline way.
+ */
+static void dgap_parity_scan(struct channel_t *ch, unsigned char *cbuf,
+ unsigned char *fbuf, int *len)
+{
+ int l = *len;
+ int count = 0;
+ unsigned char *in, *cout, *fout;
+ unsigned char c;
+
+ in = cbuf;
+ cout = cbuf;
+ fout = fbuf;
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return;
+
+ while (l--) {
+ c = *in++;
+ switch (ch->pscan_state) {
+ default:
+ /* reset to sanity and fall through */
+ ch->pscan_state = 0;
+
+ case 0:
+ /* No FF seen yet */
+ if (c == (unsigned char) '\377')
+ /* delete this character from stream */
+ ch->pscan_state = 1;
+ else {
+ *cout++ = c;
+ *fout++ = TTY_NORMAL;
+ count += 1;
+ }
+ break;
+
+ case 1:
+ /* first FF seen */
+ if (c == (unsigned char) '\377') {
+ /* doubled ff, transform to single ff */
+ *cout++ = c;
+ *fout++ = TTY_NORMAL;
+ count += 1;
+ ch->pscan_state = 0;
+ } else {
+ /* save value examination in next state */
+ ch->pscan_savechar = c;
+ ch->pscan_state = 2;
+ }
+ break;
+
+ case 2:
+ /* third character of ff sequence */
+
+ *cout++ = c;
+
+ if (ch->pscan_savechar == 0x0) {
+
+ if (c == 0x0) {
+ ch->ch_err_break++;
+ *fout++ = TTY_BREAK;
+ } else {
+ ch->ch_err_parity++;
+ *fout++ = TTY_PARITY;
+ }
+ }
+
+ count += 1;
+ ch->pscan_state = 0;
+ }
+ }
+ *len = count;
+}
+
+static void dgap_write_wakeup(struct board_t *bd, struct channel_t *ch,
+ struct un_t *un, u32 mask,
+ unsigned long *irq_flags1,
+ unsigned long *irq_flags2)
+{
+ if (!(un->un_flags & mask))
+ return;
+
+ un->un_flags &= ~mask;
+
+ if (!(un->un_flags & UN_ISOPEN))
+ return;
+
+ if ((un->un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ un->un_tty->ldisc->ops->write_wakeup) {
+ spin_unlock_irqrestore(&ch->ch_lock, *irq_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, *irq_flags1);
+
+ (un->un_tty->ldisc->ops->write_wakeup)(un->un_tty);
+
+ spin_lock_irqsave(&bd->bd_lock, *irq_flags1);
+ spin_lock_irqsave(&ch->ch_lock, *irq_flags2);
+ }
+ wake_up_interruptible(&un->un_tty->write_wait);
+ wake_up_interruptible(&un->un_flags_wait);
+}
+
+/*=======================================================================
+ *
+ * dgap_event - FEP to host event processing routine.
+ *
+ * bd - Board of current event.
+ *
+ *=======================================================================*/
+static int dgap_event(struct board_t *bd)
+{
+ struct channel_t *ch;
+ ulong lock_flags;
+ ulong lock_flags2;
+ struct bs_t __iomem *bs;
+ u8 __iomem *event;
+ u8 __iomem *vaddr;
+ struct ev_t __iomem *eaddr;
+ uint head;
+ uint tail;
+ int port;
+ int reason;
+ int modem;
+ int b1;
+
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return -EIO;
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+
+ vaddr = bd->re_map_membase;
+
+ if (!vaddr) {
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ eaddr = (struct ev_t __iomem *) (vaddr + EVBUF);
+
+ /* Get our head and tail */
+ head = readw(&(eaddr->ev_head));
+ tail = readw(&(eaddr->ev_tail));
+
+ /*
+ * Forget it if pointers out of range.
+ */
+
+ if (head >= EVMAX - EVSTART || tail >= EVMAX - EVSTART ||
+ (head | tail) & 03) {
+ /* Let go of board lock */
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+ return -EIO;
+ }
+
+ /*
+ * Loop to process all the events in the buffer.
+ */
+ while (tail != head) {
+
+ /*
+ * Get interrupt information.
+ */
+
+ event = bd->re_map_membase + tail + EVSTART;
+
+ port = ioread8(event);
+ reason = ioread8(event + 1);
+ modem = ioread8(event + 2);
+ b1 = ioread8(event + 3);
+
+ /*
+ * Make sure the interrupt is valid.
+ */
+ if (port >= bd->nasync)
+ goto next;
+
+ if (!(reason & (IFMODEM | IFBREAK | IFTLW | IFTEM | IFDATA)))
+ goto next;
+
+ ch = bd->channels[port];
+
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ goto next;
+
+ /*
+ * If we have made it here, the event was valid.
+ * Lock down the channel.
+ */
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ bs = ch->ch_bs;
+
+ if (!bs) {
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ goto next;
+ }
+
+ /*
+ * Process received data.
+ */
+ if (reason & IFDATA) {
+
+ /*
+ * ALL LOCKS *MUST* BE DROPPED BEFORE CALLING INPUT!
+ * input could send some data to ld, which in turn
+ * could do a callback to one of our other functions.
+ */
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ dgap_input(ch);
+
+ spin_lock_irqsave(&bd->bd_lock, lock_flags);
+ spin_lock_irqsave(&ch->ch_lock, lock_flags2);
+
+ if (ch->ch_flags & CH_RACTIVE)
+ ch->ch_flags |= CH_RENABLE;
+ else
+ writeb(1, &(bs->idata));
+
+ if (ch->ch_flags & CH_RWAIT) {
+ ch->ch_flags &= ~CH_RWAIT;
+
+ wake_up_interruptible
+ (&ch->ch_tun.un_flags_wait);
+ }
+ }
+
+ /*
+ * Process Modem change signals.
+ */
+ if (reason & IFMODEM) {
+ ch->ch_mistat = modem;
+ dgap_carrier(ch);
+ }
+
+ /*
+ * Process break.
+ */
+ if (reason & IFBREAK) {
+
+ if (ch->ch_tun.un_tty) {
+ /* A break has been indicated */
+ ch->ch_err_break++;
+ tty_buffer_request_room
+ (ch->ch_tun.un_tty->port, 1);
+ tty_insert_flip_char(ch->ch_tun.un_tty->port,
+ 0, TTY_BREAK);
+ tty_flip_buffer_push(ch->ch_tun.un_tty->port);
+ }
+ }
+
+ /*
+ * Process Transmit low.
+ */
+ if (reason & IFTLW) {
+ dgap_write_wakeup(bd, ch, &ch->ch_tun, UN_LOW,
+ &lock_flags, &lock_flags2);
+ dgap_write_wakeup(bd, ch, &ch->ch_pun, UN_LOW,
+ &lock_flags, &lock_flags2);
+ if (ch->ch_flags & CH_WLOW) {
+ ch->ch_flags &= ~CH_WLOW;
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+ }
+
+ /*
+ * Process Transmit empty.
+ */
+ if (reason & IFTEM) {
+ dgap_write_wakeup(bd, ch, &ch->ch_tun, UN_EMPTY,
+ &lock_flags, &lock_flags2);
+ dgap_write_wakeup(bd, ch, &ch->ch_pun, UN_EMPTY,
+ &lock_flags, &lock_flags2);
+ if (ch->ch_flags & CH_WEMPTY) {
+ ch->ch_flags &= ~CH_WEMPTY;
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+ }
+
+ spin_unlock_irqrestore(&ch->ch_lock, lock_flags2);
+
+next:
+ tail = (tail + 4) & (EVMAX - EVSTART - 4);
+ }
+
+ writew(tail, &(eaddr->ev_tail));
+ spin_unlock_irqrestore(&bd->bd_lock, lock_flags);
+
+ return 0;
+}
+
+static ssize_t dgap_driver_version_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", DG_PART);
+}
+static DRIVER_ATTR(version, S_IRUSR, dgap_driver_version_show, NULL);
+
+
+static ssize_t dgap_driver_boards_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", dgap_numboards);
+}
+static DRIVER_ATTR(boards, S_IRUSR, dgap_driver_boards_show, NULL);
+
+
+static ssize_t dgap_driver_maxboards_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", MAXBOARDS);
+}
+static DRIVER_ATTR(maxboards, S_IRUSR, dgap_driver_maxboards_show, NULL);
+
+
+static ssize_t dgap_driver_pollcounter_show(struct device_driver *ddp,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%ld\n", dgap_poll_counter);
+}
+static DRIVER_ATTR(pollcounter, S_IRUSR, dgap_driver_pollcounter_show, NULL);
+
+static ssize_t dgap_driver_pollrate_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%dms\n", dgap_poll_tick);
+}
+
+static ssize_t dgap_driver_pollrate_store(struct device_driver *ddp,
+ const char *buf, size_t count)
+{
+ if (sscanf(buf, "%d\n", &dgap_poll_tick) != 1)
+ return -EINVAL;
+ return count;
+}
+static DRIVER_ATTR(pollrate, (S_IRUSR | S_IWUSR), dgap_driver_pollrate_show,
+ dgap_driver_pollrate_store);
+
+static int dgap_create_driver_sysfiles(struct pci_driver *dgap_driver)
+{
+ int rc = 0;
+ struct device_driver *driverfs = &dgap_driver->driver;
+
+ rc |= driver_create_file(driverfs, &driver_attr_version);
+ rc |= driver_create_file(driverfs, &driver_attr_boards);
+ rc |= driver_create_file(driverfs, &driver_attr_maxboards);
+ rc |= driver_create_file(driverfs, &driver_attr_pollrate);
+ rc |= driver_create_file(driverfs, &driver_attr_pollcounter);
+
+ return rc;
+}
+
+static void dgap_remove_driver_sysfiles(struct pci_driver *dgap_driver)
+{
+ struct device_driver *driverfs = &dgap_driver->driver;
+ driver_remove_file(driverfs, &driver_attr_version);
+ driver_remove_file(driverfs, &driver_attr_boards);
+ driver_remove_file(driverfs, &driver_attr_maxboards);
+ driver_remove_file(driverfs, &driver_attr_pollrate);
+ driver_remove_file(driverfs, &driver_attr_pollcounter);
+}
+
+static struct board_t *dgap_verify_board(struct device *p)
+{
+ struct board_t *bd;
+
+ if (!p)
+ return NULL;
+
+ bd = dev_get_drvdata(p);
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC || bd->state != BOARD_READY)
+ return NULL;
+
+ return bd;
+}
+
+static ssize_t dgap_ports_state_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %s\n", bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_open_count ? "Open" : "Closed");
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_state, S_IRUSR, dgap_ports_state_show, NULL);
+
+static ssize_t dgap_ports_baud_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %d\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_baud_info);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_baud, S_IRUSR, dgap_ports_baud_show, NULL);
+
+static ssize_t dgap_ports_msignals_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++) {
+ if (bd->channels[i]->ch_open_count)
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %s %s %s %s %s %s\n",
+ bd->channels[i]->ch_portnum,
+ (bd->channels[i]->ch_mostat &
+ UART_MCR_RTS) ? "RTS" : "",
+ (bd->channels[i]->ch_mistat &
+ UART_MSR_CTS) ? "CTS" : "",
+ (bd->channels[i]->ch_mostat &
+ UART_MCR_DTR) ? "DTR" : "",
+ (bd->channels[i]->ch_mistat &
+ UART_MSR_DSR) ? "DSR" : "",
+ (bd->channels[i]->ch_mistat &
+ UART_MSR_DCD) ? "DCD" : "",
+ (bd->channels[i]->ch_mistat &
+ UART_MSR_RI) ? "RI" : "");
+ else
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d\n", bd->channels[i]->ch_portnum);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_msignals, S_IRUSR, dgap_ports_msignals_show, NULL);
+
+static ssize_t dgap_ports_iflag_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_c_iflag);
+ return count;
+}
+static DEVICE_ATTR(ports_iflag, S_IRUSR, dgap_ports_iflag_show, NULL);
+
+static ssize_t dgap_ports_cflag_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_c_cflag);
+ return count;
+}
+static DEVICE_ATTR(ports_cflag, S_IRUSR, dgap_ports_cflag_show, NULL);
+
+static ssize_t dgap_ports_oflag_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_c_oflag);
+ return count;
+}
+static DEVICE_ATTR(ports_oflag, S_IRUSR, dgap_ports_oflag_show, NULL);
+
+static ssize_t dgap_ports_lflag_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_c_lflag);
+ return count;
+}
+static DEVICE_ATTR(ports_lflag, S_IRUSR, dgap_ports_lflag_show, NULL);
+
+static ssize_t dgap_ports_digi_flag_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_digi.digi_flags);
+ return count;
+}
+static DEVICE_ATTR(ports_digi_flag, S_IRUSR, dgap_ports_digi_flag_show, NULL);
+
+static ssize_t dgap_ports_rxcount_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_rxcount);
+ return count;
+}
+static DEVICE_ATTR(ports_rxcount, S_IRUSR, dgap_ports_rxcount_show, NULL);
+
+static ssize_t dgap_ports_txcount_show(struct device *p,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ int count = 0;
+ int i;
+
+ bd = dgap_verify_board(p);
+ if (!bd)
+ return 0;
+
+ for (i = 0; i < bd->nasync; i++)
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+ bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_txcount);
+ return count;
+}
+static DEVICE_ATTR(ports_txcount, S_IRUSR, dgap_ports_txcount_show, NULL);
+
+/* this function creates the sys files that will export each signal status
+ * to sysfs each value will be put in a separate filename
+ */
+static void dgap_create_ports_sysfiles(struct board_t *bd)
+{
+ dev_set_drvdata(&bd->pdev->dev, bd);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_state);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+ device_create_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+}
+
+/* removes all the sys files created for that port */
+static void dgap_remove_ports_sysfiles(struct board_t *bd)
+{
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_state);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+}
+
+static ssize_t dgap_tty_state_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%s", un->un_open_count ?
+ "Open" : "Closed");
+}
+static DEVICE_ATTR(state, S_IRUSR, dgap_tty_state_show, NULL);
+
+static ssize_t dgap_tty_baud_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_baud_info);
+}
+static DEVICE_ATTR(baud, S_IRUSR, dgap_tty_baud_show, NULL);
+
+static ssize_t dgap_tty_msignals_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ if (ch->ch_open_count) {
+ return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n",
+ (ch->ch_mostat & UART_MCR_RTS) ? "RTS" : "",
+ (ch->ch_mistat & UART_MSR_CTS) ? "CTS" : "",
+ (ch->ch_mostat & UART_MCR_DTR) ? "DTR" : "",
+ (ch->ch_mistat & UART_MSR_DSR) ? "DSR" : "",
+ (ch->ch_mistat & UART_MSR_DCD) ? "DCD" : "",
+ (ch->ch_mistat & UART_MSR_RI) ? "RI" : "");
+ }
+ return 0;
+}
+static DEVICE_ATTR(msignals, S_IRUSR, dgap_tty_msignals_show, NULL);
+
+static ssize_t dgap_tty_iflag_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_iflag);
+}
+static DEVICE_ATTR(iflag, S_IRUSR, dgap_tty_iflag_show, NULL);
+
+static ssize_t dgap_tty_cflag_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_cflag);
+}
+static DEVICE_ATTR(cflag, S_IRUSR, dgap_tty_cflag_show, NULL);
+
+static ssize_t dgap_tty_oflag_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_oflag);
+}
+static DEVICE_ATTR(oflag, S_IRUSR, dgap_tty_oflag_show, NULL);
+
+static ssize_t dgap_tty_lflag_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_lflag);
+}
+static DEVICE_ATTR(lflag, S_IRUSR, dgap_tty_lflag_show, NULL);
+
+static ssize_t dgap_tty_digi_flag_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags);
+}
+static DEVICE_ATTR(digi_flag, S_IRUSR, dgap_tty_digi_flag_show, NULL);
+
+static ssize_t dgap_tty_rxcount_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_rxcount);
+}
+static DEVICE_ATTR(rxcount, S_IRUSR, dgap_tty_rxcount_show, NULL);
+
+static ssize_t dgap_tty_txcount_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_txcount);
+}
+static DEVICE_ATTR(txcount, S_IRUSR, dgap_tty_txcount_show, NULL);
+
+static ssize_t dgap_tty_name_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct board_t *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int cn;
+ int bn;
+ struct cnode *cptr;
+ int found = FALSE;
+ int ncount = 0;
+ int starto = 0;
+ int i;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGAP_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGAP_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGAP_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ bn = bd->boardnum;
+ cn = ch->ch_portnum;
+
+ for (cptr = bd->bd_config; cptr; cptr = cptr->next) {
+
+ if ((cptr->type == BNODE) &&
+ ((cptr->u.board.type == APORT2_920P) ||
+ (cptr->u.board.type == APORT4_920P) ||
+ (cptr->u.board.type == APORT8_920P) ||
+ (cptr->u.board.type == PAPORT4) ||
+ (cptr->u.board.type == PAPORT8))) {
+
+ found = TRUE;
+ if (cptr->u.board.v_start)
+ starto = cptr->u.board.start;
+ else
+ starto = 1;
+ }
+
+ if (cptr->type == TNODE && found == TRUE) {
+ char *ptr1;
+ if (strstr(cptr->u.ttyname, "tty")) {
+ ptr1 = cptr->u.ttyname;
+ ptr1 += 3;
+ } else
+ ptr1 = cptr->u.ttyname;
+
+ for (i = 0; i < dgap_config_get_num_prts(bd); i++) {
+ if (cn != i)
+ continue;
+
+ return snprintf(buf, PAGE_SIZE, "%s%s%02d\n",
+ (un->un_type == DGAP_PRINT) ?
+ "pr" : "tty",
+ ptr1, i + starto);
+ }
+ }
+
+ if (cptr->type == CNODE) {
+
+ for (i = 0; i < cptr->u.conc.nport; i++) {
+ if (cn != (i + ncount))
+ continue;
+
+ return snprintf(buf, PAGE_SIZE, "%s%s%02ld\n",
+ (un->un_type == DGAP_PRINT) ?
+ "pr" : "tty",
+ cptr->u.conc.id,
+ i + (cptr->u.conc.v_start ?
+ cptr->u.conc.start : 1));
+ }
+
+ ncount += cptr->u.conc.nport;
+ }
+
+ if (cptr->type == MNODE) {
+
+ for (i = 0; i < cptr->u.module.nport; i++) {
+ if (cn != (i + ncount))
+ continue;
+
+ return snprintf(buf, PAGE_SIZE, "%s%s%02ld\n",
+ (un->un_type == DGAP_PRINT) ?
+ "pr" : "tty",
+ cptr->u.module.id,
+ i + (cptr->u.module.v_start ?
+ cptr->u.module.start : 1));
+ }
+
+ ncount += cptr->u.module.nport;
+
+ }
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%s_dgap_%d_%d\n",
+ (un->un_type == DGAP_PRINT) ? "pr" : "tty", bn, cn);
+}
+static DEVICE_ATTR(custom_name, S_IRUSR, dgap_tty_name_show, NULL);
+
+static struct attribute *dgap_sysfs_tty_entries[] = {
+ &dev_attr_state.attr,
+ &dev_attr_baud.attr,
+ &dev_attr_msignals.attr,
+ &dev_attr_iflag.attr,
+ &dev_attr_cflag.attr,
+ &dev_attr_oflag.attr,
+ &dev_attr_lflag.attr,
+ &dev_attr_digi_flag.attr,
+ &dev_attr_rxcount.attr,
+ &dev_attr_txcount.attr,
+ &dev_attr_custom_name.attr,
+ NULL
+};
+
+static struct attribute_group dgap_tty_attribute_group = {
+ .name = NULL,
+ .attrs = dgap_sysfs_tty_entries,
+};
+
+static void dgap_create_tty_sysfs(struct un_t *un, struct device *c)
+{
+ int ret;
+
+ ret = sysfs_create_group(&c->kobj, &dgap_tty_attribute_group);
+ if (ret)
+ return;
+
+ dev_set_drvdata(c, un);
+
+}
+
+static void dgap_remove_tty_sysfs(struct device *c)
+{
+ sysfs_remove_group(&c->kobj, &dgap_tty_attribute_group);
+}
+
+/*
+ * Parse a configuration file read into memory as a string.
+ */
+static int dgap_parsefile(char **in, int remove)
+{
+ struct cnode *p, *brd, *line, *conc;
+ int rc;
+ char *s;
+ int linecnt = 0;
+
+ p = &dgap_head;
+ brd = line = conc = NULL;
+
+ /* perhaps we are adding to an existing list? */
+ while (p->next)
+ p = p->next;
+
+ /* file must start with a BEGIN */
+ while ((rc = dgap_gettok(in, p)) != BEGIN) {
+ if (rc == 0) {
+ dgap_err("unexpected EOF");
+ return -1;
+ }
+ }
+
+ for (; ;) {
+ rc = dgap_gettok(in, p);
+ if (rc == 0) {
+ dgap_err("unexpected EOF");
+ return -1;
+ }
+
+ switch (rc) {
+ case 0:
+ dgap_err("unexpected end of file");
+ return -1;
+
+ case BEGIN: /* should only be 1 begin */
+ dgap_err("unexpected config_begin\n");
+ return -1;
+
+ case END:
+ return 0;
+
+ case BOARD: /* board info */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(BNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+
+ p->u.board.status = kstrdup("No", GFP_KERNEL);
+ line = conc = NULL;
+ brd = p;
+ linecnt = -1;
+ break;
+
+ case APORT2_920P: /* AccelePort_4 */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_2r_920 string");
+ return -1;
+ }
+ p->u.board.type = APORT2_920P;
+ p->u.board.v_type = 1;
+ break;
+
+ case APORT4_920P: /* AccelePort_4 */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_4r_920 string");
+ return -1;
+ }
+ p->u.board.type = APORT4_920P;
+ p->u.board.v_type = 1;
+ break;
+
+ case APORT8_920P: /* AccelePort_8 */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_8r_920 string");
+ return -1;
+ }
+ p->u.board.type = APORT8_920P;
+ p->u.board.v_type = 1;
+ break;
+
+ case PAPORT4: /* AccelePort_4 PCI */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_4r(PCI) string");
+ return -1;
+ }
+ p->u.board.type = PAPORT4;
+ p->u.board.v_type = 1;
+ break;
+
+ case PAPORT8: /* AccelePort_8 PCI */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_8r string");
+ return -1;
+ }
+ p->u.board.type = PAPORT8;
+ p->u.board.v_type = 1;
+ break;
+
+ case PCX: /* PCI C/X */
+ if (p->type != BNODE) {
+ dgap_err("unexpected Digi_C/X_(PCI) string");
+ return -1;
+ }
+ p->u.board.type = PCX;
+ p->u.board.v_type = 1;
+ p->u.board.conc1 = 0;
+ p->u.board.conc2 = 0;
+ p->u.board.module1 = 0;
+ p->u.board.module2 = 0;
+ break;
+
+ case PEPC: /* PCI EPC/X */
+ if (p->type != BNODE) {
+ dgap_err("unexpected \"Digi_EPC/X_(PCI)\" string");
+ return -1;
+ }
+ p->u.board.type = PEPC;
+ p->u.board.v_type = 1;
+ p->u.board.conc1 = 0;
+ p->u.board.conc2 = 0;
+ p->u.board.module1 = 0;
+ p->u.board.module2 = 0;
+ break;
+
+ case PPCM: /* PCI/Xem */
+ if (p->type != BNODE) {
+ dgap_err("unexpected PCI/Xem string");
+ return -1;
+ }
+ p->u.board.type = PPCM;
+ p->u.board.v_type = 1;
+ p->u.board.conc1 = 0;
+ p->u.board.conc2 = 0;
+ break;
+
+ case IO: /* i/o port */
+ if (p->type != BNODE) {
+ dgap_err("IO port only vaild for boards");
+ return -1;
+ }
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.portstr = kstrdup(s, GFP_KERNEL);
+ if (kstrtol(s, 0, &p->u.board.port)) {
+ dgap_err("bad number for IO port");
+ return -1;
+ }
+ p->u.board.v_port = 1;
+ break;
+
+ case MEM: /* memory address */
+ if (p->type != BNODE) {
+ dgap_err("memory address only vaild for boards");
+ return -1;
+ }
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.addrstr = kstrdup(s, GFP_KERNEL);
+ if (kstrtoul(s, 0, &p->u.board.addr)) {
+ dgap_err("bad number for memory address");
+ return -1;
+ }
+ p->u.board.v_addr = 1;
+ break;
+
+ case PCIINFO: /* pci information */
+ if (p->type != BNODE) {
+ dgap_err("memory address only vaild for boards");
+ return -1;
+ }
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.pcibusstr = kstrdup(s, GFP_KERNEL);
+ if (kstrtoul(s, 0, &p->u.board.pcibus)) {
+ dgap_err("bad number for pci bus");
+ return -1;
+ }
+ p->u.board.v_pcibus = 1;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.pcislotstr = kstrdup(s, GFP_KERNEL);
+ if (kstrtoul(s, 0, &p->u.board.pcislot)) {
+ dgap_err("bad number for pci slot");
+ return -1;
+ }
+ p->u.board.v_pcislot = 1;
+ break;
+
+ case METHOD:
+ if (p->type != BNODE) {
+ dgap_err("install method only vaild for boards");
+ return -1;
+ }
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.method = kstrdup(s, GFP_KERNEL);
+ p->u.board.v_method = 1;
+ break;
+
+ case STATUS:
+ if (p->type != BNODE) {
+ dgap_err("config status only vaild for boards");
+ return -1;
+ }
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.board.status = kstrdup(s, GFP_KERNEL);
+ break;
+
+ case NPORTS: /* number of ports */
+ if (p->type == BNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.board.nport)) {
+ dgap_err("bad number for number of ports");
+ return -1;
+ }
+ p->u.board.v_nport = 1;
+ } else if (p->type == CNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.conc.nport)) {
+ dgap_err("bad number for number of ports");
+ return -1;
+ }
+ p->u.conc.v_nport = 1;
+ } else if (p->type == MNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.module.nport)) {
+ dgap_err("bad number for number of ports");
+ return -1;
+ }
+ p->u.module.v_nport = 1;
+ } else {
+ dgap_err("nports only valid for concentrators or modules");
+ return -1;
+ }
+ break;
+
+ case ID: /* letter ID used in tty name */
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+
+ p->u.board.status = kstrdup(s, GFP_KERNEL);
+
+ if (p->type == CNODE) {
+ p->u.conc.id = kstrdup(s, GFP_KERNEL);
+ p->u.conc.v_id = 1;
+ } else if (p->type == MNODE) {
+ p->u.module.id = kstrdup(s, GFP_KERNEL);
+ p->u.module.v_id = 1;
+ } else {
+ dgap_err("id only valid for concentrators or modules");
+ return -1;
+ }
+ break;
+
+ case STARTO: /* start offset of ID */
+ if (p->type == BNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.board.start)) {
+ dgap_err("bad number for start of tty count");
+ return -1;
+ }
+ p->u.board.v_start = 1;
+ } else if (p->type == CNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.conc.start)) {
+ dgap_err("bad number for start of tty count");
+ return -1;
+ }
+ p->u.conc.v_start = 1;
+ } else if (p->type == MNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.module.start)) {
+ dgap_err("bad number for start of tty count");
+ return -1;
+ }
+ p->u.module.v_start = 1;
+ } else {
+ dgap_err("start only valid for concentrators or modules");
+ return -1;
+ }
+ break;
+
+ case TTYN: /* tty name prefix */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(TNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpeced end of file");
+ return -1;
+ }
+ p->u.ttyname = kstrdup(s, GFP_KERNEL);
+ if (!p->u.ttyname) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ break;
+
+ case CU: /* cu name prefix */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(CUNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpeced end of file");
+ return -1;
+ }
+ p->u.cuname = kstrdup(s, GFP_KERNEL);
+ if (!p->u.cuname) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ break;
+
+ case LINE: /* line information */
+ if (dgap_checknode(p))
+ return -1;
+ if (!brd) {
+ dgap_err("must specify board before line info");
+ return -1;
+ }
+ switch (brd->u.board.type) {
+ case PPCM:
+ dgap_err("line not vaild for PC/em");
+ return -1;
+ }
+ p->next = dgap_newnode(LNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ conc = NULL;
+ line = p;
+ linecnt++;
+ break;
+
+ case CONC: /* concentrator information */
+ if (dgap_checknode(p))
+ return -1;
+ if (!line) {
+ dgap_err("must specify line info before concentrator");
+ return -1;
+ }
+ p->next = dgap_newnode(CNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ conc = p;
+ if (linecnt)
+ brd->u.board.conc2++;
+ else
+ brd->u.board.conc1++;
+
+ break;
+
+ case CX: /* c/x type concentrator */
+ if (p->type != CNODE) {
+ dgap_err("cx only valid for concentrators");
+ return -1;
+ }
+ p->u.conc.type = CX;
+ p->u.conc.v_type = 1;
+ break;
+
+ case EPC: /* epc type concentrator */
+ if (p->type != CNODE) {
+ dgap_err("cx only valid for concentrators");
+ return -1;
+ }
+ p->u.conc.type = EPC;
+ p->u.conc.v_type = 1;
+ break;
+
+ case MOD: /* EBI module */
+ if (dgap_checknode(p))
+ return -1;
+ if (!brd) {
+ dgap_err("must specify board info before EBI modules");
+ return -1;
+ }
+ switch (brd->u.board.type) {
+ case PPCM:
+ linecnt = 0;
+ break;
+ default:
+ if (!conc) {
+ dgap_err("must specify concentrator info before EBI module");
+ return -1;
+ }
+ }
+ p->next = dgap_newnode(MNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ if (linecnt)
+ brd->u.board.module2++;
+ else
+ brd->u.board.module1++;
+
+ break;
+
+ case PORTS: /* ports type EBI module */
+ if (p->type != MNODE) {
+ dgap_err("ports only valid for EBI modules");
+ return -1;
+ }
+ p->u.module.type = PORTS;
+ p->u.module.v_type = 1;
+ break;
+
+ case MODEM: /* ports type EBI module */
+ if (p->type != MNODE) {
+ dgap_err("modem only valid for modem modules");
+ return -1;
+ }
+ p->u.module.type = MODEM;
+ p->u.module.v_type = 1;
+ break;
+
+ case CABLE:
+ if (p->type == LNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.line.cable = kstrdup(s, GFP_KERNEL);
+ p->u.line.v_cable = 1;
+ }
+ break;
+
+ case SPEED: /* sync line speed indication */
+ if (p->type == LNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.line.speed)) {
+ dgap_err("bad number for line speed");
+ return -1;
+ }
+ p->u.line.v_speed = 1;
+ } else if (p->type == CNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.conc.speed)) {
+ dgap_err("bad number for line speed");
+ return -1;
+ }
+ p->u.conc.v_speed = 1;
+ } else {
+ dgap_err("speed valid only for lines or concentrators.");
+ return -1;
+ }
+ break;
+
+ case CONNECT:
+ if (p->type == CNODE) {
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ p->u.conc.connect = kstrdup(s, GFP_KERNEL);
+ p->u.conc.v_connect = 1;
+ }
+ break;
+ case PRINT: /* transparent print name prefix */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(PNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpeced end of file");
+ return -1;
+ }
+ p->u.printname = kstrdup(s, GFP_KERNEL);
+ if (!p->u.printname) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ break;
+
+ case CMAJOR: /* major number */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(JNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.majornumber)) {
+ dgap_err("bad number for major number");
+ return -1;
+ }
+ break;
+
+ case ALTPIN: /* altpin setting */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(ANODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.altpin)) {
+ dgap_err("bad number for altpin");
+ return -1;
+ }
+ break;
+
+ case USEINTR: /* enable interrupt setting */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(INTRNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.useintr)) {
+ dgap_err("bad number for useintr");
+ return -1;
+ }
+ break;
+
+ case TTSIZ: /* size of tty structure */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(TSNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.ttysize)) {
+ dgap_err("bad number for ttysize");
+ return -1;
+ }
+ break;
+
+ case CHSIZ: /* channel structure size */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(CSNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.chsize)) {
+ dgap_err("bad number for chsize");
+ return -1;
+ }
+ break;
+
+ case BSSIZ: /* board structure size */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(BSNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.bssize)) {
+ dgap_err("bad number for bssize");
+ return -1;
+ }
+ break;
+
+ case UNTSIZ: /* sched structure size */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(USNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.unsize)) {
+ dgap_err("bad number for schedsize");
+ return -1;
+ }
+ break;
+
+ case F2SIZ: /* f2200 structure size */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(FSNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.f2size)) {
+ dgap_err("bad number for f2200size");
+ return -1;
+ }
+ break;
+
+ case VPSIZ: /* vpix structure size */
+ if (dgap_checknode(p))
+ return -1;
+ p->next = dgap_newnode(VSNODE);
+ if (!p->next) {
+ dgap_err("out of memory");
+ return -1;
+ }
+ p = p->next;
+ s = dgap_getword(in);
+ if (!s) {
+ dgap_err("unexpected end of file");
+ return -1;
+ }
+ if (kstrtol(s, 0, &p->u.vpixsize)) {
+ dgap_err("bad number for vpixsize");
+ return -1;
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * dgap_sindex: much like index(), but it looks for a match of any character in
+ * the group, and returns that position. If the first character is a ^, then
+ * this will match the first occurrence not in that group.
+ */
+static char *dgap_sindex(char *string, char *group)
+{
+ char *ptr;
+
+ if (!string || !group)
+ return (char *) NULL;
+
+ if (*group == '^') {
+ group++;
+ for (; *string; string++) {
+ for (ptr = group; *ptr; ptr++) {
+ if (*ptr == *string)
+ break;
+ }
+ if (*ptr == '\0')
+ return string;
+ }
+ } else {
+ for (; *string; string++) {
+ for (ptr = group; *ptr; ptr++) {
+ if (*ptr == *string)
+ return string;
+ }
+ }
+ }
+
+ return (char *) NULL;
+}
+
+/*
+ * Get a token from the input file; return 0 if end of file is reached
+ */
+static int dgap_gettok(char **in, struct cnode *p)
+{
+ char *w;
+ struct toklist *t;
+
+ if (strstr(dgap_cword, "boar")) {
+ w = dgap_getword(in);
+ snprintf(dgap_cword, MAXCWORD, "%s", w);
+ for (t = dgap_tlist; t->token != 0; t++) {
+ if (!strcmp(w, t->string))
+ return t->token;
+ }
+ dgap_err("board !!type not specified");
+ return 1;
+ } else {
+ while ((w = dgap_getword(in))) {
+ snprintf(dgap_cword, MAXCWORD, "%s", w);
+ for (t = dgap_tlist; t->token != 0; t++) {
+ if (!strcmp(w, t->string))
+ return t->token;
+ }
+ }
+ return 0;
+ }
+}
+
+/*
+ * get a word from the input stream, also keep track of current line number.
+ * words are separated by whitespace.
+ */
+static char *dgap_getword(char **in)
+{
+ char *ret_ptr = *in;
+
+ char *ptr = dgap_sindex(*in, " \t\n");
+
+ /* If no word found, return null */
+ if (!ptr)
+ return NULL;
+
+ /* Mark new location for our buffer */
+ *ptr = '\0';
+ *in = ptr + 1;
+
+ /* Eat any extra spaces/tabs/newlines that might be present */
+ while (*in && **in && ((**in == ' ') ||
+ (**in == '\t') ||
+ (**in == '\n'))) {
+ **in = '\0';
+ *in = *in + 1;
+ }
+
+ return ret_ptr;
+}
+
+/*
+ * print an error message, giving the line number in the file where
+ * the error occurred.
+ */
+static void dgap_err(char *s)
+{
+ pr_err("dgap: parse: %s\n", s);
+}
+
+/*
+ * allocate a new configuration node of type t
+ */
+static struct cnode *dgap_newnode(int t)
+{
+ struct cnode *n;
+
+ n = kmalloc(sizeof(struct cnode), GFP_KERNEL);
+ if (n) {
+ memset((char *)n, 0, sizeof(struct cnode));
+ n->type = t;
+ }
+ return n;
+}
+
+/*
+ * dgap_checknode: see if all the necessary info has been supplied for a node
+ * before creating the next node.
+ */
+static int dgap_checknode(struct cnode *p)
+{
+ switch (p->type) {
+ case BNODE:
+ if (p->u.board.v_type == 0) {
+ dgap_err("board type !not specified");
+ return 1;
+ }
+
+ return 0;
+
+ case LNODE:
+ if (p->u.line.v_speed == 0) {
+ dgap_err("line speed not specified");
+ return 1;
+ }
+ return 0;
+
+ case CNODE:
+ if (p->u.conc.v_type == 0) {
+ dgap_err("concentrator type not specified");
+ return 1;
+ }
+ if (p->u.conc.v_speed == 0) {
+ dgap_err("concentrator line speed not specified");
+ return 1;
+ }
+ if (p->u.conc.v_nport == 0) {
+ dgap_err("number of ports on concentrator not specified");
+ return 1;
+ }
+ if (p->u.conc.v_id == 0) {
+ dgap_err("concentrator id letter not specified");
+ return 1;
+ }
+ return 0;
+
+ case MNODE:
+ if (p->u.module.v_type == 0) {
+ dgap_err("EBI module type not specified");
+ return 1;
+ }
+ if (p->u.module.v_nport == 0) {
+ dgap_err("number of ports on EBI module not specified");
+ return 1;
+ }
+ if (p->u.module.v_id == 0) {
+ dgap_err("EBI module id letter not specified");
+ return 1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Given a board pointer, returns whether we should use interrupts or not.
+ */
+static uint dgap_config_get_useintr(struct board_t *bd)
+{
+ struct cnode *p;
+
+ if (!bd)
+ return 0;
+
+ for (p = bd->bd_config; p; p = p->next) {
+ if (p->type == INTRNODE) {
+ /*
+ * check for pcxr types.
+ */
+ return p->u.useintr;
+ }
+ }
+
+ /* If not found, then don't turn on interrupts. */
+ return 0;
+}
+
+/*
+ * Given a board pointer, returns whether we turn on altpin or not.
+ */
+static uint dgap_config_get_altpin(struct board_t *bd)
+{
+ struct cnode *p;
+
+ if (!bd)
+ return 0;
+
+ for (p = bd->bd_config; p; p = p->next) {
+ if (p->type == ANODE) {
+ /*
+ * check for pcxr types.
+ */
+ return p->u.altpin;
+ }
+ }
+
+ /* If not found, then don't turn on interrupts. */
+ return 0;
+}
+
+/*
+ * Given a specific type of board, if found, detached link and
+ * returns the first occurrence in the list.
+ */
+static struct cnode *dgap_find_config(int type, int bus, int slot)
+{
+ struct cnode *p, *prev, *prev2, *found;
+
+ p = &dgap_head;
+
+ while (p->next) {
+ prev = p;
+ p = p->next;
+
+ if (p->type != BNODE)
+ continue;
+
+ if (p->u.board.type != type)
+ continue;
+
+ if (p->u.board.v_pcibus &&
+ p->u.board.pcibus != bus)
+ continue;
+
+ if (p->u.board.v_pcislot &&
+ p->u.board.pcislot != slot)
+ continue;
+
+ found = p;
+ /*
+ * Keep walking thru the list till we
+ * find the next board.
+ */
+ while (p->next) {
+ prev2 = p;
+ p = p->next;
+
+ if (p->type != BNODE)
+ continue;
+
+ /*
+ * Mark the end of our 1 board
+ * chain of configs.
+ */
+ prev2->next = NULL;
+
+ /*
+ * Link the "next" board to the
+ * previous board, effectively
+ * "unlinking" our board from
+ * the main config.
+ */
+ prev->next = p;
+
+ return found;
+ }
+ /*
+ * It must be the last board in the list.
+ */
+ prev->next = NULL;
+ return found;
+ }
+ return NULL;
+}
+
+/*
+ * Given a board pointer, walks the config link, counting up
+ * all ports user specified should be on the board.
+ * (This does NOT mean they are all actually present right now tho)
+ */
+static uint dgap_config_get_num_prts(struct board_t *bd)
+{
+ int count = 0;
+ struct cnode *p;
+
+ if (!bd)
+ return 0;
+
+ for (p = bd->bd_config; p; p = p->next) {
+
+ switch (p->type) {
+ case BNODE:
+ /*
+ * check for pcxr types.
+ */
+ if (p->u.board.type > EPCFE)
+ count += p->u.board.nport;
+ break;
+ case CNODE:
+ count += p->u.conc.nport;
+ break;
+ case MNODE:
+ count += p->u.module.nport;
+ break;
+ }
+ }
+ return count;
+}
+
+static char *dgap_create_config_string(struct board_t *bd, char *string)
+{
+ char *ptr = string;
+ struct cnode *p;
+ struct cnode *q;
+ int speed;
+
+ if (!bd) {
+ *ptr = 0xff;
+ return string;
+ }
+
+ for (p = bd->bd_config; p; p = p->next) {
+
+ switch (p->type) {
+ case LNODE:
+ *ptr = '\0';
+ ptr++;
+ *ptr = p->u.line.speed;
+ ptr++;
+ break;
+ case CNODE:
+ /*
+ * Because the EPC/con concentrators can have EM modules
+ * hanging off of them, we have to walk ahead in the
+ * list and keep adding the number of ports on each EM
+ * to the config. UGH!
+ */
+ speed = p->u.conc.speed;
+ q = p->next;
+ if (q && (q->type == MNODE)) {
+ *ptr = (p->u.conc.nport + 0x80);
+ ptr++;
+ p = q;
+ while (q->next && (q->next->type) == MNODE) {
+ *ptr = (q->u.module.nport + 0x80);
+ ptr++;
+ p = q;
+ q = q->next;
+ }
+ *ptr = q->u.module.nport;
+ ptr++;
+ } else {
+ *ptr = p->u.conc.nport;
+ ptr++;
+ }
+
+ *ptr = speed;
+ ptr++;
+ break;
+ }
+ }
+
+ *ptr = 0xff;
+ return string;
+}
diff --git a/drivers/staging/dgap/dgap.h b/drivers/staging/dgap/dgap.h
new file mode 100644
index 00000000000..03c020e35f8
--- /dev/null
+++ b/drivers/staging/dgap/dgap.h
@@ -0,0 +1,1249 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *************************************************************************
+ *
+ * Driver includes
+ *
+ *************************************************************************/
+
+#ifndef __DGAP_DRIVER_H
+#define __DGAP_DRIVER_H
+
+#include <linux/types.h> /* To pick up the varions Linux types */
+#include <linux/tty.h> /* To pick up the various tty structs/defines */
+#include <linux/interrupt.h> /* For irqreturn_t type */
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+#if !defined(TTY_FLIPBUF_SIZE)
+# define TTY_FLIPBUF_SIZE 512
+#endif
+
+/*************************************************************************
+ *
+ * Driver defines
+ *
+ *************************************************************************/
+
+/*
+ * Driver identification
+ */
+#define DG_NAME "dgap-1.3-16"
+#define DG_PART "40002347_C"
+#define DRVSTR "dgap"
+
+/*
+ * defines from dgap_pci.h
+ */
+#define PCIMAX 32 /* maximum number of PCI boards */
+
+#define DIGI_VID 0x114F
+
+#define PCI_DEV_EPC_DID 0x0002
+#define PCI_DEV_XEM_DID 0x0004
+#define PCI_DEV_XR_DID 0x0005
+#define PCI_DEV_CX_DID 0x0006
+#define PCI_DEV_XRJ_DID 0x0009 /* PLX-based Xr adapter */
+#define PCI_DEV_XR_IBM_DID 0x0011 /* IBM 8-port Async Adapter */
+#define PCI_DEV_XR_BULL_DID 0x0013 /* BULL 8-port Async Adapter */
+#define PCI_DEV_XR_SAIP_DID 0x001c /* SAIP card - Xr adapter */
+#define PCI_DEV_XR_422_DID 0x0012 /* Xr-422 */
+#define PCI_DEV_920_2_DID 0x0034 /* XR-Plus 920 K, 2 port */
+#define PCI_DEV_920_4_DID 0x0026 /* XR-Plus 920 K, 4 port */
+#define PCI_DEV_920_8_DID 0x0027 /* XR-Plus 920 K, 8 port */
+#define PCI_DEV_EPCJ_DID 0x000a /* PLX 9060 chip for PCI */
+#define PCI_DEV_CX_IBM_DID 0x001b /* IBM 128-port Async Adapter */
+#define PCI_DEV_920_8_HP_DID 0x0058 /* HP XR-Plus 920 K, 8 port */
+#define PCI_DEV_XEM_HP_DID 0x0059 /* HP Xem PCI */
+
+#define PCI_DEV_XEM_NAME "AccelePort XEM"
+#define PCI_DEV_CX_NAME "AccelePort CX"
+#define PCI_DEV_XR_NAME "AccelePort Xr"
+#define PCI_DEV_XRJ_NAME "AccelePort Xr (PLX)"
+#define PCI_DEV_XR_SAIP_NAME "AccelePort Xr (SAIP)"
+#define PCI_DEV_920_2_NAME "AccelePort Xr920 2 port"
+#define PCI_DEV_920_4_NAME "AccelePort Xr920 4 port"
+#define PCI_DEV_920_8_NAME "AccelePort Xr920 8 port"
+#define PCI_DEV_XR_422_NAME "AccelePort Xr 422"
+#define PCI_DEV_EPCJ_NAME "AccelePort EPC (PLX)"
+#define PCI_DEV_XR_BULL_NAME "AccelePort Xr (BULL)"
+#define PCI_DEV_XR_IBM_NAME "AccelePort Xr (IBM)"
+#define PCI_DEV_CX_IBM_NAME "AccelePort CX (IBM)"
+#define PCI_DEV_920_8_HP_NAME "AccelePort Xr920 8 port (HP)"
+#define PCI_DEV_XEM_HP_NAME "AccelePort XEM (HP)"
+
+/*
+ * On the PCI boards, there is no IO space allocated
+ * The I/O registers will be in the first 3 bytes of the
+ * upper 2MB of the 4MB memory space. The board memory
+ * will be mapped into the low 2MB of the 4MB memory space
+ */
+
+/* Potential location of PCI Bios from E0000 to FFFFF*/
+#define PCI_BIOS_SIZE 0x00020000
+
+/* Size of Memory and I/O for PCI (4MB) */
+#define PCI_RAM_SIZE 0x00400000
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE 0x00200000
+
+/* Max PCI Window Size (2MB) */
+#define PCI_WIN_SIZE 0x00200000
+
+#define PCI_WIN_SHIFT 21 /* 21 bits max */
+
+/* Offset of I/0 in Memory (2MB) */
+#define PCI_IO_OFFSET 0x00200000
+
+/* Size of IO (2MB) */
+#define PCI_IO_SIZE 0x00200000
+
+/* Number of boards we support at once. */
+#define MAXBOARDS 32
+#define MAXPORTS 224
+#define MAXTTYNAMELEN 200
+
+/* Our 3 magic numbers for our board, channel and unit structs */
+#define DGAP_BOARD_MAGIC 0x5c6df104
+#define DGAP_CHANNEL_MAGIC 0x6c6df104
+#define DGAP_UNIT_MAGIC 0x7c6df104
+
+/* Serial port types */
+#define DGAP_SERIAL 0
+#define DGAP_PRINT 1
+
+#define SERIAL_TYPE_NORMAL 1
+
+/* 4 extra for alignment play space */
+#define WRITEBUFLEN ((4096) + 4)
+#define MYFLIPLEN N_TTY_BUF_SIZE
+
+#define SBREAK_TIME 0x25
+#define U2BSIZE 0x400
+
+#define dgap_jiffies_from_ms(a) (((a) * HZ) / 1000)
+
+/*
+ * Our major for the mgmt devices.
+ *
+ * We can use 22, because Digi was allocated 22 and 23 for the epca driver.
+ * 22 has now become obsolete now that the "cu" devices have
+ * been removed from 2.6.
+ * Also, this *IS* the epca driver, just PCI only now.
+ */
+#ifndef DIGI_DGAP_MAJOR
+# define DIGI_DGAP_MAJOR 22
+#endif
+
+/*
+ * The parameters we use to define the periods of the moving averages.
+ */
+#define MA_PERIOD (HZ / 10)
+#define SMA_DUR (1 * HZ)
+#define EMA_DUR (1 * HZ)
+#define SMA_NPERIODS (SMA_DUR / MA_PERIOD)
+#define EMA_NPERIODS (EMA_DUR / MA_PERIOD)
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially. This is the same structure that is defined
+ * as the default in tty_io.c with the same settings overriden as in serial.c
+ *
+ * In short, this should match the internal serial ports' defaults.
+ */
+#define DEFAULT_IFLAGS (ICRNL | IXON)
+#define DEFAULT_OFLAGS (OPOST | ONLCR)
+#define DEFAULT_CFLAGS (B9600 | CS8 | CREAD | HUPCL | CLOCAL)
+#define DEFAULT_LFLAGS (ISIG | ICANON | ECHO | ECHOE | ECHOK | \
+ ECHOCTL | ECHOKE | IEXTEN)
+
+#ifndef _POSIX_VDISABLE
+#define _POSIX_VDISABLE ('\0')
+#endif
+
+#define SNIFF_MAX 65536 /* Sniff buffer size (2^n) */
+#define SNIFF_MASK (SNIFF_MAX - 1) /* Sniff wrap mask */
+
+#define VPDSIZE (512)
+
+/************************************************************************
+ * FEP memory offsets
+ ************************************************************************/
+#define START 0x0004L /* Execution start address */
+
+#define CMDBUF 0x0d10L /* Command (cm_t) structure offset */
+#define CMDSTART 0x0400L /* Start of command buffer */
+#define CMDMAX 0x0800L /* End of command buffer */
+
+#define EVBUF 0x0d18L /* Event (ev_t) structure */
+#define EVSTART 0x0800L /* Start of event buffer */
+#define EVMAX 0x0c00L /* End of event buffer */
+#define FEP5_PLUS 0x0E40 /* ASCII '5' and ASCII 'A' is here */
+#define ECS_SEG 0x0E44 /* Segment of the extended */
+ /* channel structure */
+#define LINE_SPEED 0x10 /* Offset into ECS_SEG for line */
+ /* speed if the fep has extended */
+ /* capabilities */
+
+/* BIOS MAGIC SPOTS */
+#define ERROR 0x0C14L /* BIOS error code */
+#define SEQUENCE 0x0C12L /* BIOS sequence indicator */
+#define POSTAREA 0x0C00L /* POST complete message area */
+
+/* FEP MAGIC SPOTS */
+#define FEPSTAT POSTAREA /* OS here when FEP comes up */
+#define NCHAN 0x0C02L /* number of ports FEP sees */
+#define PANIC 0x0C10L /* PANIC area for FEP */
+#define KMEMEM 0x0C30L /* Memory for KME use */
+#define CONFIG 0x0CD0L /* Concentrator configuration info */
+#define CONFIGSIZE 0x0030 /* configuration info size */
+#define DOWNREQ 0x0D00 /* Download request buffer pointer */
+
+#define CHANBUF 0x1000L /* Async channel (bs_t) structs */
+#define FEPOSSIZE 0x1FFF /* 8K FEPOS */
+
+#define XEMPORTS 0xC02 /*
+ * Offset in board memory where FEP5 stores
+ * how many ports it has detected.
+ * NOTE: FEP5 reports 64 ports when the user
+ * has the cable in EBI OUT instead of EBI IN.
+ */
+
+#define FEPCLR 0x00
+#define FEPMEM 0x02
+#define FEPRST 0x04
+#define FEPINT 0x08
+#define FEPMASK 0x0e
+#define FEPWIN 0x80
+
+#define LOWMEM 0x0100
+#define HIGHMEM 0x7f00
+
+#define FEPTIMEOUT 200000
+
+#define ENABLE_INTR 0x0e04 /* Enable interrupts flag */
+#define FEPPOLL_MIN 1 /* minimum of 1 millisecond */
+#define FEPPOLL_MAX 20 /* maximum of 20 milliseconds */
+#define FEPPOLL 0x0c26 /* Fep event poll interval */
+
+#define IALTPIN 0x0080 /* Input flag to swap DSR <-> DCD */
+
+/************************************************************************
+ * FEP supported functions
+ ************************************************************************/
+#define SRLOW 0xe0 /* Set receive low water */
+#define SRHIGH 0xe1 /* Set receive high water */
+#define FLUSHTX 0xe2 /* Flush transmit buffer */
+#define PAUSETX 0xe3 /* Pause data transmission */
+#define RESUMETX 0xe4 /* Resume data transmission */
+#define SMINT 0xe5 /* Set Modem Interrupt */
+#define SAFLOWC 0xe6 /* Set Aux. flow control chars */
+#define SBREAK 0xe8 /* Send break */
+#define SMODEM 0xe9 /* Set 8530 modem control lines */
+#define SIFLAG 0xea /* Set UNIX iflags */
+#define SFLOWC 0xeb /* Set flow control characters */
+#define STLOW 0xec /* Set transmit low water mark */
+#define RPAUSE 0xee /* Pause receive */
+#define RRESUME 0xef /* Resume receive */
+#define CHRESET 0xf0 /* Reset Channel */
+#define BUFSETALL 0xf2 /* Set Tx & Rx buffer size avail*/
+#define SOFLAG 0xf3 /* Set UNIX oflags */
+#define SHFLOW 0xf4 /* Set hardware handshake */
+#define SCFLAG 0xf5 /* Set UNIX cflags */
+#define SVNEXT 0xf6 /* Set VNEXT character */
+#define SPINTFC 0xfc /* Reserved */
+#define SCOMMODE 0xfd /* Set RS232/422 mode */
+
+/************************************************************************
+ * Modes for SCOMMODE
+ ************************************************************************/
+#define MODE_232 0x00
+#define MODE_422 0x01
+
+/************************************************************************
+ * Event flags.
+ ************************************************************************/
+#define IFBREAK 0x01 /* Break received */
+#define IFTLW 0x02 /* Transmit low water */
+#define IFTEM 0x04 /* Transmitter empty */
+#define IFDATA 0x08 /* Receive data present */
+#define IFMODEM 0x20 /* Modem status change */
+
+/************************************************************************
+ * Modem flags
+ ************************************************************************/
+# define DM_RTS 0x02 /* Request to send */
+# define DM_CD 0x80 /* Carrier detect */
+# define DM_DSR 0x20 /* Data set ready */
+# define DM_CTS 0x10 /* Clear to send */
+# define DM_RI 0x40 /* Ring indicator */
+# define DM_DTR 0x01 /* Data terminal ready */
+
+/*
+ * defines from dgap_conf.h
+ */
+#define NULLNODE 0 /* header node, not used */
+#define BNODE 1 /* Board node */
+#define LNODE 2 /* Line node */
+#define CNODE 3 /* Concentrator node */
+#define MNODE 4 /* EBI Module node */
+#define TNODE 5 /* tty name prefix node */
+#define CUNODE 6 /* cu name prefix (non-SCO) */
+#define PNODE 7 /* trans. print prefix node */
+#define JNODE 8 /* maJor number node */
+#define ANODE 9 /* altpin */
+#define TSNODE 10 /* tty structure size */
+#define CSNODE 11 /* channel structure size */
+#define BSNODE 12 /* board structure size */
+#define USNODE 13 /* unit schedule structure size */
+#define FSNODE 14 /* f2200 structure size */
+#define VSNODE 15 /* size of VPIX structures */
+#define INTRNODE 16 /* enable interrupt */
+
+/* Enumeration of tokens */
+#define BEGIN 1
+#define END 2
+#define BOARD 10
+
+#define EPCFS 11 /* start of EPC family definitions */
+#define ICX 11
+#define MCX 13
+#define PCX 14
+#define IEPC 15
+#define EEPC 16
+#define MEPC 17
+#define IPCM 18
+#define EPCM 19
+#define MPCM 20
+#define PEPC 21
+#define PPCM 22
+#ifdef CP
+#define ICP 23
+#define ECP 24
+#define MCP 25
+#endif
+#define EPCFE 25 /* end of EPC family definitions */
+#define PC2E 26
+#define PC4E 27
+#define PC4E8K 28
+#define PC8E 29
+#define PC8E8K 30
+#define PC16E 31
+#define MC2E8K 34
+#define MC4E8K 35
+#define MC8E8K 36
+
+#define AVANFS 42 /* start of Avanstar family definitions */
+#define A8P 42
+#define A16P 43
+#define AVANFE 43 /* end of Avanstar family definitions */
+
+#define DA2000FS 44 /* start of AccelePort 2000 family definitions */
+#define DA22 44 /* AccelePort 2002 */
+#define DA24 45 /* AccelePort 2004 */
+#define DA28 46 /* AccelePort 2008 */
+#define DA216 47 /* AccelePort 2016 */
+#define DAR4 48 /* AccelePort RAS 4 port */
+#define DAR8 49 /* AccelePort RAS 8 port */
+#define DDR24 50 /* DataFire RAS 24 port */
+#define DDR30 51 /* DataFire RAS 30 port */
+#define DDR48 52 /* DataFire RAS 48 port */
+#define DDR60 53 /* DataFire RAS 60 port */
+#define DA2000FE 53 /* end of AccelePort 2000/RAS family definitions */
+
+#define PCXRFS 106 /* start of PCXR family definitions */
+#define APORT4 106
+#define APORT8 107
+#define PAPORT4 108
+#define PAPORT8 109
+#define APORT4_920I 110
+#define APORT8_920I 111
+#define APORT4_920P 112
+#define APORT8_920P 113
+#define APORT2_920P 114
+#define PCXRFE 117 /* end of PCXR family definitions */
+
+#define LINE 82
+#ifdef T1
+#define T1M 83
+#define E1M 84
+#endif
+#define CONC 64
+#define CX 65
+#define EPC 66
+#define MOD 67
+#define PORTS 68
+#define METHOD 69
+#define CUSTOM 70
+#define BASIC 71
+#define STATUS 72
+#define MODEM 73
+/* The following tokens can appear in multiple places */
+#define SPEED 74
+#define NPORTS 75
+#define ID 76
+#define CABLE 77
+#define CONNECT 78
+#define IO 79
+#define MEM 80
+#define DPSZ 81
+
+#define TTYN 90
+#define CU 91
+#define PRINT 92
+#define XPRINT 93
+#define CMAJOR 94
+#define ALTPIN 95
+#define STARTO 96
+#define USEINTR 97
+#define PCIINFO 98
+
+#define TTSIZ 100
+#define CHSIZ 101
+#define BSSIZ 102
+#define UNTSIZ 103
+#define F2SIZ 104
+#define VPSIZ 105
+
+#define TOTAL_BOARD 2
+#define CURRENT_BRD 4
+#define BOARD_TYPE 6
+#define IO_ADDRESS 8
+#define MEM_ADDRESS 10
+
+#define FIELDS_PER_PAGE 18
+
+#define TB_FIELD 1
+#define CB_FIELD 3
+#define BT_FIELD 5
+#define IO_FIELD 7
+#define ID_FIELD 8
+#define ME_FIELD 9
+#define TTY_FIELD 11
+#define CU_FIELD 13
+#define PR_FIELD 15
+#define MPR_FIELD 17
+
+#define MAX_FIELD 512
+
+#define INIT 0
+#define NITEMS 128
+#define MAX_ITEM 512
+
+#define DSCRINST 1
+#define DSCRNUM 3
+#define ALTPINQ 5
+#define SSAVE 7
+
+#define DSCR "32"
+#define ONETONINE "123456789"
+#define ALL "1234567890"
+
+/*
+ * All the possible states the driver can be while being loaded.
+ */
+enum {
+ DRIVER_INITIALIZED = 0,
+ DRIVER_READY
+};
+
+/*
+ * All the possible states the board can be while booting up.
+ */
+enum {
+ BOARD_FAILED = 0,
+ BOARD_READY
+};
+
+/*
+ * All the possible states that a requested concentrator image can be in.
+ */
+enum {
+ NO_PENDING_CONCENTRATOR_REQUESTS = 0,
+ NEED_CONCENTRATOR,
+ REQUESTED_CONCENTRATOR
+};
+
+/*
+ * Modem line constants are defined as macros because DSR and
+ * DCD are swapable using the ditty altpin option.
+ */
+#define D_CD(ch) ch->ch_cd /* Carrier detect */
+#define D_DSR(ch) ch->ch_dsr /* Data set ready */
+#define D_RTS(ch) DM_RTS /* Request to send */
+#define D_CTS(ch) DM_CTS /* Clear to send */
+#define D_RI(ch) DM_RI /* Ring indicator */
+#define D_DTR(ch) DM_DTR /* Data terminal ready */
+
+/*************************************************************************
+ *
+ * Structures and closely related defines.
+ *
+ *************************************************************************/
+
+/*
+ * A structure to hold a statistics counter. We also
+ * compute moving averages for this counter.
+ */
+struct macounter {
+ u32 cnt; /* Total count */
+ ulong accum; /* Acuumulator per period */
+ ulong sma; /* Simple moving average */
+ ulong ema; /* Exponential moving average */
+};
+
+/************************************************************************
+ * Device flag definitions for bd_flags.
+ ************************************************************************/
+#define BD_FEP5PLUS 0x0001 /* Supports FEP5 Plus commands */
+#define BD_HAS_VPD 0x0002 /* Board has VPD info available */
+
+/*
+ * Per-board information
+ */
+struct board_t {
+ int magic; /* Board Magic number. */
+ int boardnum; /* Board number: 0-3 */
+ int firstminor; /* First minor, e.g. 0, 30, 60 */
+
+ int type; /* Type of board */
+ char *name; /* Product Name */
+ struct pci_dev *pdev; /* Pointer to the pci_dev struct */
+ u16 vendor; /* PCI vendor ID */
+ u16 device; /* PCI device ID */
+ u16 subvendor; /* PCI subsystem vendor ID */
+ u16 subdevice; /* PCI subsystem device ID */
+ u8 rev; /* PCI revision ID */
+ uint pci_bus; /* PCI bus value */
+ uint pci_slot; /* PCI slot value */
+ u16 maxports; /* MAX ports this board can handle */
+ u8 vpd[VPDSIZE]; /* VPD of board, if found */
+ u32 bd_flags; /* Board flags */
+
+ spinlock_t bd_lock; /* Used to protect board */
+
+ u32 state; /* State of card. */
+ wait_queue_head_t state_wait; /* Place to sleep on for state change */
+
+ struct tasklet_struct helper_tasklet; /* Poll helper tasklet */
+
+ u32 wait_for_bios;
+ u32 wait_for_fep;
+
+ struct cnode *bd_config; /* Config of board */
+
+ u16 nasync; /* Number of ports on card */
+
+ u32 use_interrupts; /* Should we be interrupt driven? */
+ ulong irq; /* Interrupt request number */
+ ulong intr_count; /* Count of interrupts */
+ u32 intr_used; /* Non-zero if using interrupts */
+ u32 intr_running; /* Non-zero if FEP knows its doing */
+ /* interrupts */
+
+ ulong port; /* Start of base io port of the card */
+ ulong port_end; /* End of base io port of the card */
+ ulong membase; /* Start of base memory of the card */
+ ulong membase_end; /* End of base memory of the card */
+
+ u8 __iomem *re_map_port; /* Remapped io port of the card */
+ u8 __iomem *re_map_membase;/* Remapped memory of the card */
+
+ u8 runwait; /* # Processes waiting for FEP */
+ u8 inhibit_poller; /* Tells the poller to leave us alone */
+
+ struct channel_t *channels[MAXPORTS]; /* array of pointers to our */
+ /* channels. */
+
+ struct tty_driver *serial_driver;
+ struct tty_port *serial_ports;
+ char serial_name[200];
+ struct tty_driver *print_driver;
+ struct tty_port *printer_ports;
+ char print_name[200];
+
+ u32 dgap_major_serial_registered;
+ u32 dgap_major_transparent_print_registered;
+
+ u32 dgap_serial_major;
+ u32 dgap_transparent_print_major;
+
+ struct bs_t __iomem *bd_bs; /* Base structure pointer */
+
+ char *flipbuf; /* Our flip buffer, alloced if */
+ /* board is found */
+ char *flipflagbuf; /* Our flip flag buffer, alloced */
+ /* if board is found */
+
+ u16 dpatype; /* The board "type", as defined */
+ /* by DPA */
+ u16 dpastatus; /* The board "status", as defined */
+ /* by DPA */
+ wait_queue_head_t kme_wait; /* Needed for DPA support */
+
+ u32 conc_dl_status; /* Status of any pending conc */
+ /* download */
+};
+
+/************************************************************************
+ * Unit flag definitions for un_flags.
+ ************************************************************************/
+#define UN_ISOPEN 0x0001 /* Device is open */
+#define UN_CLOSING 0x0002 /* Line is being closed */
+#define UN_IMM 0x0004 /* Service immediately */
+#define UN_BUSY 0x0008 /* Some work this channel */
+#define UN_BREAKI 0x0010 /* Input break received */
+#define UN_PWAIT 0x0020 /* Printer waiting for terminal */
+#define UN_TIME 0x0040 /* Waiting on time */
+#define UN_EMPTY 0x0080 /* Waiting output queue empty */
+#define UN_LOW 0x0100 /* Waiting output low water mark*/
+#define UN_EXCL_OPEN 0x0200 /* Open for exclusive use */
+#define UN_WOPEN 0x0400 /* Device waiting for open */
+#define UN_WIOCTL 0x0800 /* Device waiting for open */
+#define UN_HANGUP 0x8000 /* Carrier lost */
+
+struct device;
+
+/************************************************************************
+ * Structure for terminal or printer unit.
+ ************************************************************************/
+struct un_t {
+ int magic; /* Unit Magic Number. */
+ struct channel_t *un_ch;
+ u32 un_time;
+ u32 un_type;
+ int un_open_count; /* Counter of opens to port */
+ struct tty_struct *un_tty;/* Pointer to unit tty structure */
+ u32 un_flags; /* Unit flags */
+ wait_queue_head_t un_flags_wait; /* Place to sleep to wait on unit */
+ u32 un_dev; /* Minor device number */
+ tcflag_t un_oflag; /* oflags being done on board */
+ tcflag_t un_lflag; /* lflags being done on board */
+ struct device *un_sysfs;
+};
+
+/************************************************************************
+ * Device flag definitions for ch_flags.
+ ************************************************************************/
+#define CH_PRON 0x0001 /* Printer on string */
+#define CH_OUT 0x0002 /* Dial-out device open */
+#define CH_STOP 0x0004 /* Output is stopped */
+#define CH_STOPI 0x0008 /* Input is stopped */
+#define CH_CD 0x0010 /* Carrier is present */
+#define CH_FCAR 0x0020 /* Carrier forced on */
+
+#define CH_RXBLOCK 0x0080 /* Enable rx blocked flag */
+#define CH_WLOW 0x0100 /* Term waiting low event */
+#define CH_WEMPTY 0x0200 /* Term waiting empty event */
+#define CH_RENABLE 0x0400 /* Buffer just emptied */
+#define CH_RACTIVE 0x0800 /* Process active in xxread() */
+#define CH_RWAIT 0x1000 /* Process waiting in xxread() */
+#define CH_BAUD0 0x2000 /* Used for checking B0 transitions */
+#define CH_HANGUP 0x8000 /* Hangup received */
+
+/*
+ * Definitions for ch_sniff_flags
+ */
+#define SNIFF_OPEN 0x1
+#define SNIFF_WAIT_DATA 0x2
+#define SNIFF_WAIT_SPACE 0x4
+
+/************************************************************************
+ *** Definitions for Digi ditty(1) command.
+ ************************************************************************/
+
+/************************************************************************
+ * This module provides application access to special Digi
+ * serial line enhancements which are not standard UNIX(tm) features.
+ ************************************************************************/
+
+#if !defined(TIOCMODG)
+
+#define TIOCMODG (('d'<<8) | 250) /* get modem ctrl state */
+#define TIOCMODS (('d'<<8) | 251) /* set modem ctrl state */
+
+#ifndef TIOCM_LE
+#define TIOCM_LE 0x01 /* line enable */
+#define TIOCM_DTR 0x02 /* data terminal ready */
+#define TIOCM_RTS 0x04 /* request to send */
+#define TIOCM_ST 0x08 /* secondary transmit */
+#define TIOCM_SR 0x10 /* secondary receive */
+#define TIOCM_CTS 0x20 /* clear to send */
+#define TIOCM_CAR 0x40 /* carrier detect */
+#define TIOCM_RNG 0x80 /* ring indicator */
+#define TIOCM_DSR 0x100 /* data set ready */
+#define TIOCM_RI TIOCM_RNG /* ring (alternate) */
+#define TIOCM_CD TIOCM_CAR /* carrier detect (alt) */
+#endif
+
+#endif
+
+#if !defined(TIOCMSET)
+#define TIOCMSET (('d'<<8) | 252) /* set modem ctrl state */
+#define TIOCMGET (('d'<<8) | 253) /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMBIC)
+#define TIOCMBIC (('d'<<8) | 254) /* set modem ctrl state */
+#define TIOCMBIS (('d'<<8) | 255) /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCSDTR)
+#define TIOCSDTR (('e'<<8) | 0) /* set DTR */
+#define TIOCCDTR (('e'<<8) | 1) /* clear DTR */
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA (('e'<<8) | 94) /* Read params */
+
+#define DIGI_SETA (('e'<<8) | 95) /* Set params */
+#define DIGI_SETAW (('e'<<8) | 96) /* Drain & set params */
+#define DIGI_SETAF (('e'<<8) | 97) /* Drain, flush & set params */
+
+#define DIGI_KME (('e'<<8) | 98) /* Read/Write Host */
+ /* Adapter Memory */
+
+#define DIGI_GETFLOW (('e'<<8) | 99) /* Get startc/stopc flow */
+ /* control characters */
+#define DIGI_SETFLOW (('e'<<8) | 100) /* Set startc/stopc flow */
+ /* control characters */
+#define DIGI_GETAFLOW (('e'<<8) | 101) /* Get Aux. startc/stopc */
+ /* flow control chars */
+#define DIGI_SETAFLOW (('e'<<8) | 102) /* Set Aux. startc/stopc */
+ /* flow control chars */
+
+#define DIGI_GEDELAY (('d'<<8) | 246) /* Get edelay */
+#define DIGI_SEDELAY (('d'<<8) | 247) /* Set edelay */
+
+struct digiflow_t {
+ unsigned char startc; /* flow cntl start char */
+ unsigned char stopc; /* flow cntl stop char */
+};
+
+#ifdef FLOW_2200
+#define F2200_GETA (('e'<<8) | 104) /* Get 2x36 flow cntl flags */
+#define F2200_SETAW (('e'<<8) | 105) /* Set 2x36 flow cntl flags */
+#define F2200_MASK 0x03 /* 2200 flow cntl bit mask */
+#define FCNTL_2200 0x01 /* 2x36 terminal flow cntl */
+#define PCNTL_2200 0x02 /* 2x36 printer flow cntl */
+#define F2200_XON 0xf8
+#define P2200_XON 0xf9
+#define F2200_XOFF 0xfa
+#define P2200_XOFF 0xfb
+
+#define FXOFF_MASK 0x03 /* 2200 flow status mask */
+#define RCVD_FXOFF 0x01 /* 2x36 Terminal XOFF rcvd */
+#define RCVD_PXOFF 0x02 /* 2x36 Printer XOFF rcvd */
+#endif
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON 0x0001 /* Handle IXON in the FEP */
+#define DIGI_FAST 0x0002 /* Fast baud rates */
+#define RTSPACE 0x0004 /* RTS input flow control */
+#define CTSPACE 0x0008 /* CTS output flow control */
+#define DSRPACE 0x0010 /* DSR output flow control */
+#define DCDPACE 0x0020 /* DCD output flow control */
+#define DTRPACE 0x0040 /* DTR input flow control */
+#define DIGI_COOK 0x0080 /* Cooked processing done in FEP */
+#define DIGI_FORCEDCD 0x0100 /* Force carrier */
+#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+#define DIGI_PRINTER 0x0800 /* Hold port open for flow cntrl*/
+#define DIGI_PP_INPUT 0x1000 /* Change parallel port to input*/
+#define DIGI_DTR_TOGGLE 0x2000 /* Support DTR Toggle */
+#define DIGI_422 0x4000 /* for 422/232 selectable panel */
+#define DIGI_RTS_TOGGLE 0x8000 /* Support RTS Toggle */
+
+/************************************************************************
+ * These options are not supported on the comxi.
+ ************************************************************************/
+#define DIGI_COMXI (DIGI_FAST|DIGI_COOK|DSRPACE|DCDPACE|DTRPACE)
+
+#define DIGI_PLEN 28 /* String length */
+#define DIGI_TSIZ 10 /* Terminal string len */
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_t {
+ unsigned short digi_flags; /* Flags (see above) */
+ unsigned short digi_maxcps; /* Max printer CPS */
+ unsigned short digi_maxchar; /* Max chars in print queue */
+ unsigned short digi_bufsize; /* Buffer size */
+ unsigned char digi_onlen; /* Length of ON string */
+ unsigned char digi_offlen; /* Length of OFF string */
+ char digi_onstr[DIGI_PLEN]; /* Printer on string */
+ char digi_offstr[DIGI_PLEN]; /* Printer off string */
+ char digi_term[DIGI_TSIZ]; /* terminal string */
+};
+
+/************************************************************************
+ * KME definitions and structures.
+ ************************************************************************/
+#define RW_IDLE 0 /* Operation complete */
+#define RW_READ 1 /* Read Concentrator Memory */
+#define RW_WRITE 2 /* Write Concentrator Memory */
+
+struct rw_t {
+ unsigned char rw_req; /* Request type */
+ unsigned char rw_board; /* Host Adapter board number */
+ unsigned char rw_conc; /* Concentrator number */
+ unsigned char rw_reserved; /* Reserved for expansion */
+ unsigned long rw_addr; /* Address in concentrator */
+ unsigned short rw_size; /* Read/write request length */
+ unsigned char rw_data[128]; /* Data to read/write */
+};
+
+/************************************************************************
+ * Structure to get driver status information
+ ************************************************************************/
+struct digi_dinfo {
+ unsigned long dinfo_nboards; /* # boards configured */
+ char dinfo_reserved[12]; /* for future expansion */
+ char dinfo_version[16]; /* driver version */
+};
+
+#define DIGI_GETDD (('d'<<8) | 248) /* get driver info */
+
+/************************************************************************
+ * Structure used with ioctl commands for per-board information
+ *
+ * physsize and memsize differ when board has "windowed" memory
+ ************************************************************************/
+struct digi_info {
+ unsigned long info_bdnum; /* Board number (0 based) */
+ unsigned long info_ioport; /* io port address */
+ unsigned long info_physaddr; /* memory address */
+ unsigned long info_physsize; /* Size of host mem window */
+ unsigned long info_memsize; /* Amount of dual-port mem */
+ /* on board */
+ unsigned short info_bdtype; /* Board type */
+ unsigned short info_nports; /* number of ports */
+ char info_bdstate; /* board state */
+ char info_reserved[7]; /* for future expansion */
+};
+
+#define DIGI_GETBD (('d'<<8) | 249) /* get board info */
+
+struct digi_stat {
+ unsigned int info_chan; /* Channel number (0 based) */
+ unsigned int info_brd; /* Board number (0 based) */
+ unsigned long info_cflag; /* cflag for channel */
+ unsigned long info_iflag; /* iflag for channel */
+ unsigned long info_oflag; /* oflag for channel */
+ unsigned long info_mstat; /* mstat for channel */
+ unsigned long info_tx_data; /* tx_data for channel */
+ unsigned long info_rx_data; /* rx_data for channel */
+ unsigned long info_hflow; /* hflow for channel */
+ unsigned long info_reserved[8]; /* for future expansion */
+};
+
+#define DIGI_GETSTAT (('d'<<8) | 244) /* get board info */
+/************************************************************************
+ *
+ * Structure used with ioctl commands for per-channel information
+ *
+ ************************************************************************/
+struct digi_ch {
+ unsigned long info_bdnum; /* Board number (0 based) */
+ unsigned long info_channel; /* Channel index number */
+ unsigned long info_ch_cflag; /* Channel cflag */
+ unsigned long info_ch_iflag; /* Channel iflag */
+ unsigned long info_ch_oflag; /* Channel oflag */
+ unsigned long info_chsize; /* Channel structure size */
+ unsigned long info_sleep_stat; /* sleep status */
+ dev_t info_dev; /* device number */
+ unsigned char info_initstate; /* Channel init state */
+ unsigned char info_running; /* Channel running state */
+ long reserved[8]; /* reserved for future use */
+};
+
+/*
+* This structure is used with the DIGI_FEPCMD ioctl to
+* tell the driver which port to send the command for.
+*/
+struct digi_cmd {
+ int cmd;
+ int word;
+ int ncmds;
+ int chan; /* channel index (zero based) */
+ int bdid; /* board index (zero based) */
+};
+
+/*
+* info_sleep_stat defines
+*/
+#define INFO_RUNWAIT 0x0001
+#define INFO_WOPEN 0x0002
+#define INFO_TTIOW 0x0004
+#define INFO_CH_RWAIT 0x0008
+#define INFO_CH_WEMPTY 0x0010
+#define INFO_CH_WLOW 0x0020
+#define INFO_XXBUF_BUSY 0x0040
+
+#define DIGI_GETCH (('d'<<8) | 245) /* get board info */
+
+/* Board type definitions */
+
+#define SUBTYPE 0007
+#define T_PCXI 0000
+#define T_PCXM 0001
+#define T_PCXE 0002
+#define T_PCXR 0003
+#define T_SP 0004
+#define T_SP_PLUS 0005
+# define T_HERC 0000
+# define T_HOU 0001
+# define T_LON 0002
+# define T_CHA 0003
+#define FAMILY 0070
+#define T_COMXI 0000
+#define T_PCXX 0010
+#define T_CX 0020
+#define T_EPC 0030
+#define T_PCLITE 0040
+#define T_SPXX 0050
+#define T_AVXX 0060
+#define T_DXB 0070
+#define T_A2K_4_8 0070
+#define BUSTYPE 0700
+#define T_ISABUS 0000
+#define T_MCBUS 0100
+#define T_EISABUS 0200
+#define T_PCIBUS 0400
+
+/* Board State Definitions */
+
+#define BD_RUNNING 0x0
+#define BD_REASON 0x7f
+#define BD_NOTFOUND 0x1
+#define BD_NOIOPORT 0x2
+#define BD_NOMEM 0x3
+#define BD_NOBIOS 0x4
+#define BD_NOFEP 0x5
+#define BD_FAILED 0x6
+#define BD_ALLOCATED 0x7
+#define BD_TRIBOOT 0x8
+#define BD_BADKME 0x80
+
+#define DIGI_LOOPBACK (('d'<<8) | 252) /* Enable/disable UART */
+ /* internal loopback */
+#define DIGI_SPOLL (('d'<<8) | 254) /* change poller rate */
+
+#define DIGI_SETCUSTOMBAUD _IOW('e', 106, int) /* Set integer baud rate */
+#define DIGI_GETCUSTOMBAUD _IOR('e', 107, int) /* Get integer baud rate */
+#define DIGI_RESET_PORT (('e'<<8) | 93) /* Reset port */
+
+/************************************************************************
+ * Channel information structure.
+ ************************************************************************/
+struct channel_t {
+ int magic; /* Channel Magic Number */
+ struct bs_t __iomem *ch_bs; /* Base structure pointer */
+ struct cm_t __iomem *ch_cm; /* Command queue pointer */
+ struct board_t *ch_bd; /* Board structure pointer */
+ u8 __iomem *ch_vaddr; /* FEP memory origin */
+ u8 __iomem *ch_taddr; /* Write buffer origin */
+ u8 __iomem *ch_raddr; /* Read buffer origin */
+ struct digi_t ch_digi; /* Transparent Print structure */
+ struct un_t ch_tun; /* Terminal unit info */
+ struct un_t ch_pun; /* Printer unit info */
+
+ spinlock_t ch_lock; /* provide for serialization */
+ wait_queue_head_t ch_flags_wait;
+
+ u32 pscan_state;
+ u8 pscan_savechar;
+
+ u32 ch_portnum; /* Port number, 0 offset. */
+ u32 ch_open_count; /* open count */
+ u32 ch_flags; /* Channel flags */
+
+ u32 ch_close_delay; /* How long we should drop */
+ /* RTS/DTR for */
+
+ u32 ch_cpstime; /* Time for CPS calculations */
+
+ tcflag_t ch_c_iflag; /* channel iflags */
+ tcflag_t ch_c_cflag; /* channel cflags */
+ tcflag_t ch_c_oflag; /* channel oflags */
+ tcflag_t ch_c_lflag; /* channel lflags */
+
+ u16 ch_fepiflag; /* FEP tty iflags */
+ u16 ch_fepcflag; /* FEP tty cflags */
+ u16 ch_fepoflag; /* FEP tty oflags */
+ u16 ch_wopen; /* Waiting for open process cnt */
+ u16 ch_tstart; /* Transmit buffer start */
+ u16 ch_tsize; /* Transmit buffer size */
+ u16 ch_rstart; /* Receive buffer start */
+ u16 ch_rsize; /* Receive buffer size */
+ u16 ch_rdelay; /* Receive delay time */
+
+ u16 ch_tlw; /* Our currently set low water mark */
+
+ u16 ch_cook; /* Output character mask */
+
+ u8 ch_card; /* Card channel is on */
+ u8 ch_stopc; /* Stop character */
+ u8 ch_startc; /* Start character */
+
+ u8 ch_mostat; /* FEP output modem status */
+ u8 ch_mistat; /* FEP input modem status */
+ u8 ch_mforce; /* Modem values to be forced */
+ u8 ch_mval; /* Force values */
+ u8 ch_fepstopc; /* FEP stop character */
+ u8 ch_fepstartc; /* FEP start character */
+
+ u8 ch_astopc; /* Auxiliary Stop character */
+ u8 ch_astartc; /* Auxiliary Start character */
+ u8 ch_fepastopc; /* Auxiliary FEP stop char */
+ u8 ch_fepastartc; /* Auxiliary FEP start char */
+
+ u8 ch_hflow; /* FEP hardware handshake */
+ u8 ch_dsr; /* stores real dsr value */
+ u8 ch_cd; /* stores real cd value */
+ u8 ch_tx_win; /* channel tx buffer window */
+ u8 ch_rx_win; /* channel rx buffer window */
+ uint ch_custom_speed; /* Custom baud, if set */
+ uint ch_baud_info; /* Current baud info for /proc output */
+ ulong ch_rxcount; /* total of data received so far */
+ ulong ch_txcount; /* total of data transmitted so far */
+ ulong ch_err_parity; /* Count of parity errors on channel */
+ ulong ch_err_frame; /* Count of framing errors on channel */
+ ulong ch_err_break; /* Count of breaks on channel */
+ ulong ch_err_overrun; /* Count of overruns on channel */
+};
+
+/************************************************************************
+ * Command structure definition.
+ ************************************************************************/
+struct cm_t {
+ unsigned short cm_head; /* Command buffer head offset */
+ unsigned short cm_tail; /* Command buffer tail offset */
+ unsigned short cm_start; /* start offset of buffer */
+ unsigned short cm_max; /* last offset of buffer */
+};
+
+/************************************************************************
+ * Event structure definition.
+ ************************************************************************/
+struct ev_t {
+ unsigned short ev_head; /* Command buffer head offset */
+ unsigned short ev_tail; /* Command buffer tail offset */
+ unsigned short ev_start; /* start offset of buffer */
+ unsigned short ev_max; /* last offset of buffer */
+};
+
+/************************************************************************
+ * Download buffer structure.
+ ************************************************************************/
+struct downld_t {
+ u8 dl_type; /* Header */
+ u8 dl_seq; /* Download sequence */
+ ushort dl_srev; /* Software revision number */
+ ushort dl_lrev; /* Low revision number */
+ ushort dl_hrev; /* High revision number */
+ ushort dl_seg; /* Start segment address */
+ ushort dl_size; /* Number of bytes to download */
+ u8 dl_data[1024]; /* Download data */
+};
+
+/************************************************************************
+ * Per channel buffer structure
+ ************************************************************************
+ * Base Structure Entries Usage Meanings to Host *
+ * *
+ * W = read write R = read only *
+ * C = changed by commands only *
+ * U = unknown (may be changed w/o notice) *
+ ************************************************************************/
+struct bs_t {
+ unsigned short tp_jmp; /* Transmit poll jump */
+ unsigned short tc_jmp; /* Cooked procedure jump */
+ unsigned short ri_jmp; /* Not currently used */
+ unsigned short rp_jmp; /* Receive poll jump */
+
+ unsigned short tx_seg; /* W Tx segment */
+ unsigned short tx_head; /* W Tx buffer head offset */
+ unsigned short tx_tail; /* R Tx buffer tail offset */
+ unsigned short tx_max; /* W Tx buffer size - 1 */
+
+ unsigned short rx_seg; /* W Rx segment */
+ unsigned short rx_head; /* W Rx buffer head offset */
+ unsigned short rx_tail; /* R Rx buffer tail offset */
+ unsigned short rx_max; /* W Rx buffer size - 1 */
+
+ unsigned short tx_lw; /* W Tx buffer low water mark */
+ unsigned short rx_lw; /* W Rx buffer low water mark */
+ unsigned short rx_hw; /* W Rx buffer high water mark*/
+ unsigned short incr; /* W Increment to next channel*/
+
+ unsigned short fepdev; /* U SCC device base address */
+ unsigned short edelay; /* W Exception delay */
+ unsigned short blen; /* W Break length */
+ unsigned short btime; /* U Break complete time */
+
+ unsigned short iflag; /* C UNIX input flags */
+ unsigned short oflag; /* C UNIX output flags */
+ unsigned short cflag; /* C UNIX control flags */
+ unsigned short wfill[13]; /* U Reserved for expansion */
+
+ unsigned char num; /* U Channel number */
+ unsigned char ract; /* U Receiver active counter */
+ unsigned char bstat; /* U Break status bits */
+ unsigned char tbusy; /* W Transmit busy */
+ unsigned char iempty; /* W Transmit empty event */
+ /* enable */
+ unsigned char ilow; /* W Transmit low-water event */
+ /* enable */
+ unsigned char idata; /* W Receive data interrupt */
+ /* enable */
+ unsigned char eflag; /* U Host event flags */
+
+ unsigned char tflag; /* U Transmit flags */
+ unsigned char rflag; /* U Receive flags */
+ unsigned char xmask; /* U Transmit ready flags */
+ unsigned char xval; /* U Transmit ready value */
+ unsigned char m_stat; /* RC Modem status bits */
+ unsigned char m_change; /* U Modem bits which changed */
+ unsigned char m_int; /* W Modem interrupt enable */
+ /* bits */
+ unsigned char m_last; /* U Last modem status */
+
+ unsigned char mtran; /* C Unreported modem trans */
+ unsigned char orun; /* C Buffer overrun occurred */
+ unsigned char astartc; /* W Auxiliary Xon char */
+ unsigned char astopc; /* W Auxiliary Xoff char */
+ unsigned char startc; /* W Xon character */
+ unsigned char stopc; /* W Xoff character */
+ unsigned char vnextc; /* W Vnext character */
+ unsigned char hflow; /* C Software flow control */
+
+ unsigned char fillc; /* U Delay Fill character */
+ unsigned char ochar; /* U Saved output character */
+ unsigned char omask; /* U Output character mask */
+
+ unsigned char bfill[13]; /* U Reserved for expansion */
+
+ unsigned char scc[16]; /* U SCC registers */
+};
+
+struct cnode {
+ struct cnode *next;
+ int type;
+ int numbrd;
+
+ union {
+ struct {
+ char type; /* Board Type */
+ long port; /* I/O Address */
+ char *portstr; /* I/O Address in string */
+ long addr; /* Memory Address */
+ char *addrstr; /* Memory Address in string */
+ long pcibus; /* PCI BUS */
+ char *pcibusstr; /* PCI BUS in string */
+ long pcislot; /* PCI SLOT */
+ char *pcislotstr; /* PCI SLOT in string */
+ long nport; /* Number of Ports */
+ char *id; /* tty id */
+ long start; /* start of tty counting */
+ char *method; /* Install method */
+ char v_type;
+ char v_port;
+ char v_addr;
+ char v_pcibus;
+ char v_pcislot;
+ char v_nport;
+ char v_id;
+ char v_start;
+ char v_method;
+ char line1;
+ char line2;
+ char conc1; /* total concs in line1 */
+ char conc2; /* total concs in line2 */
+ char module1; /* total modules for line1 */
+ char module2; /* total modules for line2 */
+ char *status; /* config status */
+ char *dimstatus; /* Y/N */
+ int status_index; /* field pointer */
+ } board;
+
+ struct {
+ char *cable;
+ char v_cable;
+ long speed;
+ char v_speed;
+ } line;
+
+ struct {
+ char type;
+ char *connect;
+ long speed;
+ long nport;
+ char *id;
+ char *idstr;
+ long start;
+ char v_type;
+ char v_connect;
+ char v_speed;
+ char v_nport;
+ char v_id;
+ char v_start;
+ } conc;
+
+ struct {
+ char type;
+ long nport;
+ char *id;
+ char *idstr;
+ long start;
+ char v_type;
+ char v_nport;
+ char v_id;
+ char v_start;
+ } module;
+
+ char *ttyname;
+ char *cuname;
+ char *printname;
+ long majornumber;
+ long altpin;
+ long ttysize;
+ long chsize;
+ long bssize;
+ long unsize;
+ long f2size;
+ long vpixsize;
+ long useintr;
+ } u;
+};
+#endif
diff --git a/drivers/staging/dgnc/Kconfig b/drivers/staging/dgnc/Kconfig
new file mode 100644
index 00000000000..032c2a79523
--- /dev/null
+++ b/drivers/staging/dgnc/Kconfig
@@ -0,0 +1,6 @@
+config DGNC
+ tristate "Digi Neo and Classic PCI Products"
+ default n
+ depends on TTY && PCI
+ ---help---
+ Driver for the Digi International Neo and Classic PCI based product line.
diff --git a/drivers/staging/dgnc/Makefile b/drivers/staging/dgnc/Makefile
new file mode 100644
index 00000000000..888c4334236
--- /dev/null
+++ b/drivers/staging/dgnc/Makefile
@@ -0,0 +1,7 @@
+EXTRA_CFLAGS += -DDG_NAME=\"dgnc-1.3-16\" -DDG_PART=\"40002369_F\"
+
+obj-$(CONFIG_DGNC) += dgnc.o
+
+dgnc-objs := dgnc_cls.o dgnc_driver.o\
+ dgnc_mgmt.o dgnc_neo.o\
+ dgnc_trace.o dgnc_tty.o dgnc_sysfs.o
diff --git a/drivers/staging/dgnc/TODO b/drivers/staging/dgnc/TODO
new file mode 100644
index 00000000000..1ff2d1874aa
--- /dev/null
+++ b/drivers/staging/dgnc/TODO
@@ -0,0 +1,17 @@
+* remove kzalloc casts
+* checkpatch fixes
+* sparse fixes
+* fix use of sizeof(). Example replace sizeof(struct board_t)
+ with sizeof(*brd) and remove sizeof(char)
+* change name of board_t to dgnc_board
+* split two assignments into the two assignments on two lines;
+ don't use two equals signs
+* remove unecessary comments
+* remove unecessary error messages. Example kzalloc() has its
+ own error message. Adding an extra one is useless.
+* use goto statements for error handling when appropriate
+* there is a lot of unecessary code in the driver. It was
+ originally a standalone driver. Remove uneeded code.
+
+Please send patches to Greg Kroah-Hartman <greg@kroah.com> and
+Cc: Lidza Louina <lidza.louina@gmail.com>
diff --git a/drivers/staging/dgnc/dgnc_cls.c b/drivers/staging/dgnc/dgnc_cls.c
new file mode 100644
index 00000000000..8e265c20db5
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_cls.c
@@ -0,0 +1,1434 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h> /* For jiffies, task states */
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/delay.h> /* For udelay */
+#include <linux/io.h> /* For read[bwl]/write[bwl] */
+#include <linux/serial.h> /* For struct async_serial */
+#include <linux/serial_reg.h> /* For the various UART offsets */
+#include <linux/pci.h>
+
+#include "dgnc_driver.h" /* Driver main header file */
+#include "dgnc_cls.h"
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+
+static inline void cls_parse_isr(struct dgnc_board *brd, uint port);
+static inline void cls_clear_break(struct channel_t *ch, int force);
+static inline void cls_set_cts_flow_control(struct channel_t *ch);
+static inline void cls_set_rts_flow_control(struct channel_t *ch);
+static inline void cls_set_ixon_flow_control(struct channel_t *ch);
+static inline void cls_set_ixoff_flow_control(struct channel_t *ch);
+static inline void cls_set_no_output_flow_control(struct channel_t *ch);
+static inline void cls_set_no_input_flow_control(struct channel_t *ch);
+static void cls_parse_modem(struct channel_t *ch, uchar signals);
+static void cls_tasklet(unsigned long data);
+static void cls_vpd(struct dgnc_board *brd);
+static void cls_uart_init(struct channel_t *ch);
+static void cls_uart_off(struct channel_t *ch);
+static int cls_drain(struct tty_struct *tty, uint seconds);
+static void cls_param(struct tty_struct *tty);
+static void cls_assert_modem_signals(struct channel_t *ch);
+static void cls_flush_uart_write(struct channel_t *ch);
+static void cls_flush_uart_read(struct channel_t *ch);
+static void cls_disable_receiver(struct channel_t *ch);
+static void cls_enable_receiver(struct channel_t *ch);
+static void cls_send_break(struct channel_t *ch, int msecs);
+static void cls_send_start_character(struct channel_t *ch);
+static void cls_send_stop_character(struct channel_t *ch);
+static void cls_copy_data_from_uart_to_queue(struct channel_t *ch);
+static void cls_copy_data_from_queue_to_uart(struct channel_t *ch);
+static uint cls_get_uart_bytes_left(struct channel_t *ch);
+static void cls_send_immediate_char(struct channel_t *ch, unsigned char);
+static irqreturn_t cls_intr(int irq, void *voidbrd);
+
+struct board_ops dgnc_cls_ops = {
+ .tasklet = cls_tasklet,
+ .intr = cls_intr,
+ .uart_init = cls_uart_init,
+ .uart_off = cls_uart_off,
+ .drain = cls_drain,
+ .param = cls_param,
+ .vpd = cls_vpd,
+ .assert_modem_signals = cls_assert_modem_signals,
+ .flush_uart_write = cls_flush_uart_write,
+ .flush_uart_read = cls_flush_uart_read,
+ .disable_receiver = cls_disable_receiver,
+ .enable_receiver = cls_enable_receiver,
+ .send_break = cls_send_break,
+ .send_start_character = cls_send_start_character,
+ .send_stop_character = cls_send_stop_character,
+ .copy_data_from_queue_to_uart = cls_copy_data_from_queue_to_uart,
+ .get_uart_bytes_left = cls_get_uart_bytes_left,
+ .send_immediate_char = cls_send_immediate_char
+};
+
+
+static inline void cls_set_cts_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Setting CTSFLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn on CTS flow control, turn off IXON flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_CTSDSR);
+ isr_fcr &= ~(UART_EXAR654_EFR_IXON);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /*
+ * Enable interrupts for CTS flow, turn off interrupts for
+ * received XOFF chars
+ */
+ ier |= (UART_EXAR654_IER_CTSDSR);
+ ier &= ~(UART_EXAR654_IER_XOFF);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+ ch->ch_t_tlevel = 16;
+
+}
+
+
+static inline void cls_set_ixon_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Setting IXON FLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn on IXON flow control, turn off CTS flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXON);
+ isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Now set our current start/stop chars while in enhanced mode */
+ writeb(ch->ch_startc, &ch->ch_cls_uart->mcr);
+ writeb(0, &ch->ch_cls_uart->lsr);
+ writeb(ch->ch_stopc, &ch->ch_cls_uart->msr);
+ writeb(0, &ch->ch_cls_uart->spr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /*
+ * Disable interrupts for CTS flow, turn on interrupts for
+ * received XOFF chars
+ */
+ ier &= ~(UART_EXAR654_IER_CTSDSR);
+ ier |= (UART_EXAR654_IER_XOFF);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+}
+
+
+static inline void cls_set_no_output_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Unsetting Output FLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn off IXON flow control, turn off CTS flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB);
+ isr_fcr &= ~(UART_EXAR654_EFR_CTSDSR | UART_EXAR654_EFR_IXON);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /*
+ * Disable interrupts for CTS flow, turn off interrupts for
+ * received XOFF chars
+ */
+ ier &= ~(UART_EXAR654_IER_CTSDSR);
+ ier &= ~(UART_EXAR654_IER_XOFF);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+ ch->ch_r_watermark = 0;
+ ch->ch_t_tlevel = 16;
+ ch->ch_r_tlevel = 16;
+
+}
+
+
+static inline void cls_set_rts_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Setting RTSFLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn on RTS flow control, turn off IXOFF flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_RTSDTR);
+ isr_fcr &= ~(UART_EXAR654_EFR_IXOFF);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /* Enable interrupts for RTS flow */
+ ier |= (UART_EXAR654_IER_RTSDTR);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_56 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+
+ ch->ch_r_watermark = 4;
+ ch->ch_r_tlevel = 8;
+
+}
+
+
+static inline void cls_set_ixoff_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Setting IXOFF FLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn on IXOFF flow control, turn off RTS flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB | UART_EXAR654_EFR_IXOFF);
+ isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Now set our current start/stop chars while in enhanced mode */
+ writeb(ch->ch_startc, &ch->ch_cls_uart->mcr);
+ writeb(0, &ch->ch_cls_uart->lsr);
+ writeb(ch->ch_stopc, &ch->ch_cls_uart->msr);
+ writeb(0, &ch->ch_cls_uart->spr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /* Disable interrupts for RTS flow */
+ ier &= ~(UART_EXAR654_IER_RTSDTR);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+}
+
+
+static inline void cls_set_no_input_flow_control(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar ier = readb(&ch->ch_cls_uart->ier);
+ uchar isr_fcr = 0;
+
+ DPR_PARAM(("Unsetting Input FLOW\n"));
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn off IXOFF flow control, turn off RTS flow control */
+ isr_fcr |= (UART_EXAR654_EFR_ECB);
+ isr_fcr &= ~(UART_EXAR654_EFR_RTSDTR | UART_EXAR654_EFR_IXOFF);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /* Disable interrupts for RTS flow */
+ ier &= ~(UART_EXAR654_IER_RTSDTR);
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ /* Set the usual FIFO values */
+ writeb((UART_FCR_ENABLE_FIFO), &ch->ch_cls_uart->isr_fcr);
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_16654_FCR_RXTRIGGER_16 |
+ UART_16654_FCR_TXTRIGGER_16 | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+
+ ch->ch_t_tlevel = 16;
+ ch->ch_r_tlevel = 16;
+
+}
+
+
+/*
+ * cls_clear_break.
+ * Determines whether its time to shut off break condition.
+ *
+ * No locks are assumed to be held when calling this function.
+ * channel lock is held and released in this function.
+ */
+static inline void cls_clear_break(struct channel_t *ch, int force)
+{
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Bail if we aren't currently sending a break. */
+ if (!ch->ch_stop_sending_break) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* Turn break off, and unset some variables */
+ if (ch->ch_flags & CH_BREAK_SENDING) {
+ if (time_after(jiffies, ch->ch_stop_sending_break) || force) {
+ uchar temp = readb(&ch->ch_cls_uart->lcr);
+ writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+ ch->ch_flags &= ~(CH_BREAK_SENDING);
+ ch->ch_stop_sending_break = 0;
+ DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n",
+ jiffies));
+ }
+ }
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/* Parse the ISR register for the specific port */
+static inline void cls_parse_isr(struct dgnc_board *brd, uint port)
+{
+ struct channel_t *ch;
+ uchar isr = 0;
+ ulong lock_flags;
+
+ /*
+ * No need to verify board pointer, it was already
+ * verified in the interrupt routine.
+ */
+
+ if (port > brd->nasync)
+ return;
+
+ ch = brd->channels[port];
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ /* Here we try to figure out what caused the interrupt to happen */
+ while (1) {
+
+ isr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Bail if no pending interrupt on port */
+ if (isr & UART_IIR_NO_INT)
+ break;
+
+ DPR_INTR(("%s:%d port: %x isr: %x\n", __FILE__, __LINE__,
+ port, isr));
+
+ /* Receive Interrupt pending */
+ if (isr & (UART_IIR_RDI | UART_IIR_RDI_TIMEOUT)) {
+ /* Read data from uart -> queue */
+ brd->intr_rx++;
+ ch->ch_intr_rx++;
+ cls_copy_data_from_uart_to_queue(ch);
+ dgnc_check_queue_flow_control(ch);
+ }
+
+ /* Transmit Hold register empty pending */
+ if (isr & UART_IIR_THRI) {
+ /* Transfer data (if any) from Write Queue -> UART. */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ brd->intr_tx++;
+ ch->ch_intr_tx++;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ cls_copy_data_from_queue_to_uart(ch);
+ }
+
+ /* CTS/RTS change of state */
+ if (isr & UART_IIR_CTSRTS) {
+ brd->intr_modem++;
+ ch->ch_intr_modem++;
+ /*
+ * Don't need to do anything, the cls_parse_modem
+ * below will grab the updated modem signals.
+ */
+ }
+
+ /* Parse any modem signal changes */
+ DPR_INTR(("MOD_STAT: sending to parse_modem_sigs\n"));
+ cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
+ }
+}
+
+
+/*
+ * cls_param()
+ * Send any/all changes to the line to the UART.
+ */
+static void cls_param(struct tty_struct *tty)
+{
+ uchar lcr = 0;
+ uchar uart_lcr = 0;
+ uchar ier = 0;
+ uchar uart_ier = 0;
+ uint baud = 9600;
+ int quot = 0;
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = (struct un_t *) tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_PARAM(("param start: tdev: %x cflags: %x oflags: %x iflags: %x\n",
+ ch->ch_tun.un_dev, ch->ch_c_cflag, ch->ch_c_oflag,
+ ch->ch_c_iflag));
+
+ /*
+ * If baud rate is zero, flush queues, and set mval to drop DTR.
+ */
+ if ((ch->ch_c_cflag & (CBAUD)) == 0) {
+ ch->ch_r_head = ch->ch_r_tail = 0;
+ ch->ch_e_head = ch->ch_e_tail = 0;
+ ch->ch_w_head = ch->ch_w_tail = 0;
+
+ cls_flush_uart_write(ch);
+ cls_flush_uart_read(ch);
+
+ /* The baudrate is B0 so all modem lines are to be dropped. */
+ ch->ch_flags |= (CH_BAUD0);
+ ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR);
+ cls_assert_modem_signals(ch);
+ ch->ch_old_baud = 0;
+ return;
+ } else if (ch->ch_custom_speed) {
+
+ baud = ch->ch_custom_speed;
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+
+ /*
+ * Bring back up RTS and DTR...
+ * Also handle RTS or DTR toggle if set.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+ }
+
+ } else {
+ int iindex = 0;
+ int jindex = 0;
+
+ ulong bauds[4][16] = {
+ { /* slowbaud */
+ 0, 50, 75, 110,
+ 134, 150, 200, 300,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* slowbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud */
+ 0, 57600, 76800, 115200,
+ 131657, 153600, 230400, 460800,
+ 921600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 }
+ };
+
+ /*
+ * Only use the TXPrint baud rate if the terminal
+ * unit is NOT open
+ */
+ if (!(ch->ch_tun.un_flags & UN_ISOPEN) &&
+ (un->un_type == DGNC_PRINT))
+ baud = C_BAUD(ch->ch_pun.un_tty) & 0xff;
+ else
+ baud = C_BAUD(ch->ch_tun.un_tty) & 0xff;
+
+ if (ch->ch_c_cflag & CBAUDEX)
+ iindex = 1;
+
+ if (ch->ch_digi.digi_flags & DIGI_FAST)
+ iindex += 2;
+
+ jindex = baud;
+
+ if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) &&
+ (jindex < 16)) {
+ baud = bauds[iindex][jindex];
+ } else {
+ DPR_IOCTL(("baud indices were out of range (%d)(%d)",
+ iindex, jindex));
+ baud = 0;
+ }
+
+ if (baud == 0)
+ baud = 9600;
+
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+
+ /*
+ * Bring back up RTS and DTR...
+ * Also handle RTS or DTR toggle if set.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+ }
+ }
+
+ if (ch->ch_c_cflag & PARENB)
+ lcr |= UART_LCR_PARITY;
+
+ if (!(ch->ch_c_cflag & PARODD))
+ lcr |= UART_LCR_EPAR;
+
+ /*
+ * Not all platforms support mark/space parity,
+ * so this will hide behind an ifdef.
+ */
+#ifdef CMSPAR
+ if (ch->ch_c_cflag & CMSPAR)
+ lcr |= UART_LCR_SPAR;
+#endif
+
+ if (ch->ch_c_cflag & CSTOPB)
+ lcr |= UART_LCR_STOP;
+
+ switch (ch->ch_c_cflag & CSIZE) {
+ case CS5:
+ lcr |= UART_LCR_WLEN5;
+ break;
+ case CS6:
+ lcr |= UART_LCR_WLEN6;
+ break;
+ case CS7:
+ lcr |= UART_LCR_WLEN7;
+ break;
+ case CS8:
+ default:
+ lcr |= UART_LCR_WLEN8;
+ break;
+ }
+
+ ier = uart_ier = readb(&ch->ch_cls_uart->ier);
+ uart_lcr = readb(&ch->ch_cls_uart->lcr);
+
+ if (baud == 0)
+ baud = 9600;
+
+ quot = ch->ch_bd->bd_dividend / baud;
+
+ if (quot != 0 && ch->ch_old_baud != baud) {
+ ch->ch_old_baud = baud;
+ writeb(UART_LCR_DLAB, &ch->ch_cls_uart->lcr);
+ writeb((quot & 0xff), &ch->ch_cls_uart->txrx);
+ writeb((quot >> 8), &ch->ch_cls_uart->ier);
+ writeb(lcr, &ch->ch_cls_uart->lcr);
+ }
+
+ if (uart_lcr != lcr)
+ writeb(lcr, &ch->ch_cls_uart->lcr);
+
+ if (ch->ch_c_cflag & CREAD)
+ ier |= (UART_IER_RDI | UART_IER_RLSI);
+ else
+ ier &= ~(UART_IER_RDI | UART_IER_RLSI);
+
+ /*
+ * Have the UART interrupt on modem signal changes ONLY when
+ * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set.
+ */
+ if ((ch->ch_digi.digi_flags & CTSPACE) ||
+ (ch->ch_digi.digi_flags & RTSPACE) ||
+ (ch->ch_c_cflag & CRTSCTS) ||
+ !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+ !(ch->ch_c_cflag & CLOCAL))
+ ier |= UART_IER_MSI;
+ else
+ ier &= ~UART_IER_MSI;
+
+ ier |= UART_IER_THRI;
+
+ if (ier != uart_ier)
+ writeb(ier, &ch->ch_cls_uart->ier);
+
+ if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ cls_set_cts_flow_control(ch);
+ } else if (ch->ch_c_iflag & IXON) {
+ /*
+ * If start/stop is set to disable, then we should
+ * disable flow control
+ */
+ if ((ch->ch_startc == _POSIX_VDISABLE) ||
+ (ch->ch_stopc == _POSIX_VDISABLE))
+ cls_set_no_output_flow_control(ch);
+ else
+ cls_set_ixon_flow_control(ch);
+ } else {
+ cls_set_no_output_flow_control(ch);
+ }
+
+ if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ cls_set_rts_flow_control(ch);
+ } else if (ch->ch_c_iflag & IXOFF) {
+ /*
+ * If start/stop is set to disable, then we should disable
+ * flow control
+ */
+ if ((ch->ch_startc == _POSIX_VDISABLE) ||
+ (ch->ch_stopc == _POSIX_VDISABLE))
+ cls_set_no_input_flow_control(ch);
+ else
+ cls_set_ixoff_flow_control(ch);
+ } else {
+ cls_set_no_input_flow_control(ch);
+ }
+
+ cls_assert_modem_signals(ch);
+
+ /* Get current status of the modem signals now */
+ cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
+}
+
+
+/*
+ * Our board poller function.
+ */
+static void cls_tasklet(unsigned long data)
+{
+ struct dgnc_board *bd = (struct dgnc_board *) data;
+ struct channel_t *ch;
+ ulong lock_flags;
+ int i;
+ int state = 0;
+ int ports = 0;
+
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+ APR(("poll_tasklet() - NULL or bad bd.\n"));
+ return;
+ }
+
+ /* Cache a couple board values */
+ DGNC_LOCK(bd->bd_lock, lock_flags);
+ state = bd->state;
+ ports = bd->nasync;
+ DGNC_UNLOCK(bd->bd_lock, lock_flags);
+
+ /*
+ * Do NOT allow the interrupt routine to read the intr registers
+ * Until we release this lock.
+ */
+ DGNC_LOCK(bd->bd_intr_lock, lock_flags);
+
+ /*
+ * If board is ready, parse deeper to see if there is anything to do.
+ */
+ if ((state == BOARD_READY) && (ports > 0)) {
+
+ /* Loop on each port */
+ for (i = 0; i < ports; i++) {
+ ch = bd->channels[i];
+ if (!ch)
+ continue;
+
+ /*
+ * NOTE: Remember you CANNOT hold any channel
+ * locks when calling input.
+ * During input processing, its possible we
+ * will call ld, which might do callbacks back
+ * into us.
+ */
+ dgnc_input(ch);
+
+ /*
+ * Channel lock is grabbed and then released
+ * inside this routine.
+ */
+ cls_copy_data_from_queue_to_uart(ch);
+ dgnc_wakeup_writes(ch);
+
+ /*
+ * Check carrier function.
+ */
+ dgnc_carrier(ch);
+
+ /*
+ * The timing check of turning off the break is done
+ * inside clear_break()
+ */
+ if (ch->ch_stop_sending_break)
+ cls_clear_break(ch, 0);
+ }
+ }
+
+ DGNC_UNLOCK(bd->bd_intr_lock, lock_flags);
+
+}
+
+
+/*
+ * cls_intr()
+ *
+ * Classic specific interrupt handler.
+ */
+static irqreturn_t cls_intr(int irq, void *voidbrd)
+{
+ struct dgnc_board *brd = (struct dgnc_board *) voidbrd;
+ uint i = 0;
+ uchar poll_reg;
+ unsigned long lock_flags;
+
+ if (!brd) {
+ APR(("Received interrupt (%d) with null board associated\n",
+ irq));
+ return IRQ_NONE;
+ }
+
+ /*
+ * Check to make sure its for us.
+ */
+ if (brd->magic != DGNC_BOARD_MAGIC) {
+ APR(("Received interrupt (%d) with a board pointer that wasn't ours!\n",
+ irq));
+ return IRQ_NONE;
+ }
+
+ DGNC_LOCK(brd->bd_intr_lock, lock_flags);
+
+ brd->intr_count++;
+
+ /*
+ * Check the board's global interrupt offset to see if we
+ * we actually do have an interrupt pending for us.
+ */
+ poll_reg = readb(brd->re_map_membase + UART_CLASSIC_POLL_ADDR_OFFSET);
+
+ /* If 0, no interrupts pending */
+ if (!poll_reg) {
+ DPR_INTR((
+ "Kernel interrupted to me, but no pending interrupts...\n"));
+ DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+ return IRQ_NONE;
+ }
+
+ DPR_INTR(("%s:%d poll_reg: %x\n", __FILE__, __LINE__, poll_reg));
+
+ /* Parse each port to find out what caused the interrupt */
+ for (i = 0; i < brd->nasync; i++)
+ cls_parse_isr(brd, i);
+
+ /*
+ * Schedule tasklet to more in-depth servicing at a better time.
+ */
+ tasklet_schedule(&brd->helper_tasklet);
+
+ DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+
+ DPR_INTR(("dgnc_intr finish.\n"));
+ return IRQ_HANDLED;
+}
+
+
+static void cls_disable_receiver(struct channel_t *ch)
+{
+ uchar tmp = readb(&ch->ch_cls_uart->ier);
+ tmp &= ~(UART_IER_RDI);
+ writeb(tmp, &ch->ch_cls_uart->ier);
+}
+
+
+static void cls_enable_receiver(struct channel_t *ch)
+{
+ uchar tmp = readb(&ch->ch_cls_uart->ier);
+ tmp |= (UART_IER_RDI);
+ writeb(tmp, &ch->ch_cls_uart->ier);
+}
+
+
+static void cls_copy_data_from_uart_to_queue(struct channel_t *ch)
+{
+ int qleft = 0;
+ uchar linestatus = 0;
+ uchar error_mask = 0;
+ ushort head;
+ ushort tail;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* cache head and tail of queue */
+ head = ch->ch_r_head;
+ tail = ch->ch_r_tail;
+
+ /* Store how much space we have left in the queue */
+ qleft = (tail - head - 1);
+ if (qleft < 0)
+ qleft += RQUEUEMASK + 1;
+
+ /*
+ * Create a mask to determine whether we should
+ * insert the character (if any) into our queue.
+ */
+ if (ch->ch_c_iflag & IGNBRK)
+ error_mask |= UART_LSR_BI;
+
+ while (1) {
+ linestatus = readb(&ch->ch_cls_uart->lsr);
+
+ if (!(linestatus & (UART_LSR_DR)))
+ break;
+
+ /*
+ * Discard character if we are ignoring the error mask.
+ */
+ if (linestatus & error_mask) {
+ uchar discard;
+ linestatus = 0;
+ discard = readb(&ch->ch_cls_uart->txrx);
+ continue;
+ }
+
+ /*
+ * If our queue is full, we have no choice but to drop some
+ * data. The assumption is that HWFLOW or SWFLOW should have
+ * stopped things way way before we got to this point.
+ *
+ * I decided that I wanted to ditch the oldest data first,
+ * I hope thats okay with everyone? Yes? Good.
+ */
+ while (qleft < 1) {
+ DPR_READ(("Queue full, dropping DATA:%x LSR:%x\n",
+ ch->ch_rqueue[tail], ch->ch_equeue[tail]));
+
+ ch->ch_r_tail = tail = (tail + 1) & RQUEUEMASK;
+ ch->ch_err_overrun++;
+ qleft++;
+ }
+
+ ch->ch_equeue[head] = linestatus & (UART_LSR_BI | UART_LSR_PE
+ | UART_LSR_FE);
+ ch->ch_rqueue[head] = readb(&ch->ch_cls_uart->txrx);
+ dgnc_sniff_nowait_nolock(ch, "UART READ",
+ ch->ch_rqueue + head, 1);
+
+ qleft--;
+
+ DPR_READ(("DATA/LSR pair: %x %x\n", ch->ch_rqueue[head],
+ ch->ch_equeue[head]));
+
+ if (ch->ch_equeue[head] & UART_LSR_PE)
+ ch->ch_err_parity++;
+ if (ch->ch_equeue[head] & UART_LSR_BI)
+ ch->ch_err_break++;
+ if (ch->ch_equeue[head] & UART_LSR_FE)
+ ch->ch_err_frame++;
+
+ /* Add to, and flip head if needed */
+ head = (head + 1) & RQUEUEMASK;
+ ch->ch_rxcount++;
+ }
+
+ /*
+ * Write new final heads to channel structure.
+ */
+ ch->ch_r_head = head & RQUEUEMASK;
+ ch->ch_e_head = head & EQUEUEMASK;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * This function basically goes to sleep for secs, or until
+ * it gets signalled that the port has fully drained.
+ */
+static int cls_drain(struct tty_struct *tty, uint seconds)
+{
+ ulong lock_flags;
+ struct channel_t *ch;
+ struct un_t *un;
+ int rc = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -ENXIO;
+
+ un = (struct un_t *) tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -ENXIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENXIO;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ un->un_flags |= UN_EMPTY;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * NOTE: Do something with time passed in.
+ */
+ rc = wait_event_interruptible(un->un_flags_wait,
+ ((un->un_flags & UN_EMPTY) == 0));
+
+ /* If ret is non-zero, user ctrl-c'ed us */
+ if (rc)
+ DPR_IOCTL(("%d Drain - User ctrl c'ed\n", __LINE__));
+
+ return rc;
+}
+
+
+/* Channel lock MUST be held before calling this function! */
+static void cls_flush_uart_write(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT),
+ &ch->ch_cls_uart->isr_fcr);
+ udelay(10);
+
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+}
+
+
+/* Channel lock MUST be held before calling this function! */
+static void cls_flush_uart_read(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * For complete POSIX compatibility, we should be purging the
+ * read FIFO in the UART here.
+ *
+ * However, doing the statement below also incorrectly flushes
+ * write data as well as just basically trashing the FIFO.
+ *
+ * I believe this is a BUG in this UART.
+ * So for now, we will leave the code #ifdef'ed out...
+ */
+#if 0
+ writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR),
+ &ch->ch_cls_uart->isr_fcr);
+#endif
+ udelay(10);
+}
+
+
+static void cls_copy_data_from_queue_to_uart(struct channel_t *ch)
+{
+ ushort head;
+ ushort tail;
+ int n;
+ int qlen;
+ uint len_written = 0;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* No data to write to the UART */
+ if (ch->ch_w_tail == ch->ch_w_head) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* If port is "stopped", don't send any data to the UART */
+ if ((ch->ch_flags & CH_FORCED_STOP) ||
+ (ch->ch_flags & CH_BREAK_SENDING)) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ n = 32;
+
+ /* cache head and tail of queue */
+ head = ch->ch_w_head & WQUEUEMASK;
+ tail = ch->ch_w_tail & WQUEUEMASK;
+ qlen = (head - tail) & WQUEUEMASK;
+
+ /* Find minimum of the FIFO space, versus queue length */
+ n = min(n, qlen);
+
+ while (n > 0) {
+
+ /*
+ * If RTS Toggle mode is on, turn on RTS now if not already set,
+ * and make sure we get an event when the data transfer has
+ * completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_RTS)) {
+ ch->ch_mostat |= (UART_MCR_RTS);
+ cls_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+
+ /*
+ * If DTR Toggle mode is on, turn on DTR now if not already set,
+ * and make sure we get an event when the data transfer has
+ * completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_DTR)) {
+ ch->ch_mostat |= (UART_MCR_DTR);
+ cls_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+ writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_cls_uart->txrx);
+ dgnc_sniff_nowait_nolock(ch, "UART WRITE",
+ ch->ch_wqueue + ch->ch_w_tail, 1);
+ DPR_WRITE(("Tx data: %x\n", ch->ch_wqueue[ch->ch_w_tail]));
+ ch->ch_w_tail++;
+ ch->ch_w_tail &= WQUEUEMASK;
+ ch->ch_txcount++;
+ len_written++;
+ n--;
+ }
+
+ if (len_written > 0)
+ ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ return;
+}
+
+
+static void cls_parse_modem(struct channel_t *ch, uchar signals)
+{
+ uchar msignals = signals;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DPR_MSIGS(("cls_parse_modem: port: %d signals: %d\n",
+ ch->ch_portnum, msignals));
+
+ /*
+ * Do altpin switching. Altpin switches DCD and DSR.
+ * This prolly breaks DSRPACE, so we should be more clever here.
+ */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ if (ch->ch_digi.digi_flags & DIGI_ALTPIN) {
+ uchar mswap = signals;
+ if (mswap & UART_MSR_DDCD) {
+ msignals &= ~UART_MSR_DDCD;
+ msignals |= UART_MSR_DDSR;
+ }
+ if (mswap & UART_MSR_DDSR) {
+ msignals &= ~UART_MSR_DDSR;
+ msignals |= UART_MSR_DDCD;
+ }
+ if (mswap & UART_MSR_DCD) {
+ msignals &= ~UART_MSR_DCD;
+ msignals |= UART_MSR_DSR;
+ }
+ if (mswap & UART_MSR_DSR) {
+ msignals &= ~UART_MSR_DSR;
+ msignals |= UART_MSR_DCD;
+ }
+ }
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Scrub off lower bits. They signify delta's, which I don't
+ * care about
+ */
+ signals &= 0xf0;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ if (msignals & UART_MSR_DCD)
+ ch->ch_mistat |= UART_MSR_DCD;
+ else
+ ch->ch_mistat &= ~UART_MSR_DCD;
+
+ if (msignals & UART_MSR_DSR)
+ ch->ch_mistat |= UART_MSR_DSR;
+ else
+ ch->ch_mistat &= ~UART_MSR_DSR;
+
+ if (msignals & UART_MSR_RI)
+ ch->ch_mistat |= UART_MSR_RI;
+ else
+ ch->ch_mistat &= ~UART_MSR_RI;
+
+ if (msignals & UART_MSR_CTS)
+ ch->ch_mistat |= UART_MSR_CTS;
+ else
+ ch->ch_mistat &= ~UART_MSR_CTS;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+
+ DPR_MSIGS((
+ "Port: %d DTR: %d RTS: %d CTS: %d DSR: %d " "RI: %d CD: %d\n",
+ ch->ch_portnum,
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_DTR),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_RTS),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_CTS),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DSR),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_RI),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DCD)));
+}
+
+
+/* Make the UART raise any of the output signals we want up */
+static void cls_assert_modem_signals(struct channel_t *ch)
+{
+ uchar out;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ out = ch->ch_mostat;
+
+ if (ch->ch_flags & CH_LOOPBACK)
+ out |= UART_MCR_LOOP;
+
+ writeb(out, &ch->ch_cls_uart->mcr);
+
+ /* Give time for the UART to actually drop the signals */
+ udelay(10);
+}
+
+
+static void cls_send_start_character(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ if (ch->ch_startc != _POSIX_VDISABLE) {
+ ch->ch_xon_sends++;
+ writeb(ch->ch_startc, &ch->ch_cls_uart->txrx);
+ }
+}
+
+
+static void cls_send_stop_character(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ if (ch->ch_stopc != _POSIX_VDISABLE) {
+ ch->ch_xoff_sends++;
+ writeb(ch->ch_stopc, &ch->ch_cls_uart->txrx);
+ }
+}
+
+
+/* Inits UART */
+static void cls_uart_init(struct channel_t *ch)
+{
+ uchar lcrb = readb(&ch->ch_cls_uart->lcr);
+ uchar isr_fcr = 0;
+
+ writeb(0, &ch->ch_cls_uart->ier);
+
+ /*
+ * The Enhanced Register Set may only be accessed when
+ * the Line Control Register is set to 0xBFh.
+ */
+ writeb(UART_EXAR654_ENHANCED_REGISTER_SET, &ch->ch_cls_uart->lcr);
+
+ isr_fcr = readb(&ch->ch_cls_uart->isr_fcr);
+
+ /* Turn on Enhanced/Extended controls */
+ isr_fcr |= (UART_EXAR654_EFR_ECB);
+
+ writeb(isr_fcr, &ch->ch_cls_uart->isr_fcr);
+
+ /* Write old LCR value back out, which turns enhanced access off */
+ writeb(lcrb, &ch->ch_cls_uart->lcr);
+
+ /* Clear out UART and FIFO */
+ readb(&ch->ch_cls_uart->txrx);
+
+ writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT),
+ &ch->ch_cls_uart->isr_fcr);
+ udelay(10);
+
+ ch->ch_flags |= (CH_FIFO_ENABLED | CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+
+ readb(&ch->ch_cls_uart->lsr);
+ readb(&ch->ch_cls_uart->msr);
+}
+
+
+/*
+ * Turns off UART.
+ */
+static void cls_uart_off(struct channel_t *ch)
+{
+ writeb(0, &ch->ch_cls_uart->ier);
+}
+
+
+/*
+ * cls_get_uarts_bytes_left.
+ * Returns 0 is nothing left in the FIFO, returns 1 otherwise.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static uint cls_get_uart_bytes_left(struct channel_t *ch)
+{
+ uchar left = 0;
+ uchar lsr = 0;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+
+ lsr = readb(&ch->ch_cls_uart->lsr);
+
+ /* Determine whether the Transmitter is empty or not */
+ if (!(lsr & UART_LSR_TEMT)) {
+ if (ch->ch_flags & CH_TX_FIFO_EMPTY)
+ tasklet_schedule(&ch->ch_bd->helper_tasklet);
+ left = 1;
+ } else {
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ left = 0;
+ }
+
+ return left;
+}
+
+
+/*
+ * cls_send_break.
+ * Starts sending a break thru the UART.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void cls_send_break(struct channel_t *ch, int msecs)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ /*
+ * If we receive a time of 0, this means turn off the break.
+ */
+ if (msecs == 0) {
+ /* Turn break off, and unset some variables */
+ if (ch->ch_flags & CH_BREAK_SENDING) {
+ uchar temp = readb(&ch->ch_cls_uart->lcr);
+ writeb((temp & ~UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+ ch->ch_flags &= ~(CH_BREAK_SENDING);
+ ch->ch_stop_sending_break = 0;
+ DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n",
+ jiffies));
+ }
+ return;
+ }
+
+ /*
+ * Set the time we should stop sending the break.
+ * If we are already sending a break, toss away the existing
+ * time to stop, and use this new value instead.
+ */
+ ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs);
+
+ /* Tell the UART to start sending the break */
+ if (!(ch->ch_flags & CH_BREAK_SENDING)) {
+ uchar temp = readb(&ch->ch_cls_uart->lcr);
+ writeb((temp | UART_LCR_SBC), &ch->ch_cls_uart->lcr);
+ ch->ch_flags |= (CH_BREAK_SENDING);
+ DPR_IOCTL((
+ "Port %d. Starting UART_LCR_SBC! start: %lx should end: %lx\n",
+ ch->ch_portnum, jiffies, ch->ch_stop_sending_break));
+ }
+}
+
+
+/*
+ * cls_send_immediate_char.
+ * Sends a specific character as soon as possible to the UART,
+ * jumping over any bytes that might be in the write queue.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void cls_send_immediate_char(struct channel_t *ch, unsigned char c)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ writeb(c, &ch->ch_cls_uart->txrx);
+}
+
+static void cls_vpd(struct dgnc_board *brd)
+{
+ ulong vpdbase; /* Start of io base of the card */
+ u8 __iomem *re_map_vpdbase;/* Remapped memory of the card */
+ int i = 0;
+
+
+ vpdbase = pci_resource_start(brd->pdev, 3);
+
+ /* No VPD */
+ if (!vpdbase)
+ return;
+
+ re_map_vpdbase = ioremap(vpdbase, 0x400);
+
+ if (!re_map_vpdbase)
+ return;
+
+ /* Store the VPD into our buffer */
+ for (i = 0; i < 0x40; i++) {
+ brd->vpd[i] = readb(re_map_vpdbase + i);
+ pr_info("%x ", brd->vpd[i]);
+ }
+ pr_info("\n");
+
+ if (re_map_vpdbase)
+ iounmap(re_map_vpdbase);
+}
+
diff --git a/drivers/staging/dgnc/dgnc_cls.h b/drivers/staging/dgnc/dgnc_cls.h
new file mode 100644
index 00000000000..465d79a6f75
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_cls.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ */
+
+#ifndef __DGNC_CLS_H
+#define __DGNC_CLS_H
+
+#include "dgnc_types.h"
+
+
+/************************************************************************
+ * Per channel/port Classic UART structure *
+ ************************************************************************
+ * Base Structure Entries Usage Meanings to Host *
+ * *
+ * W = read write R = read only *
+ * U = Unused. *
+ ************************************************************************/
+
+struct cls_uart_struct {
+ u8 txrx; /* WR RHR/THR - Holding Reg */
+ u8 ier; /* WR IER - Interrupt Enable Reg */
+ u8 isr_fcr; /* WR ISR/FCR - Interrupt Status Reg/Fifo Control Reg */
+ u8 lcr; /* WR LCR - Line Control Reg */
+ u8 mcr; /* WR MCR - Modem Control Reg */
+ u8 lsr; /* WR LSR - Line Status Reg */
+ u8 msr; /* WR MSR - Modem Status Reg */
+ u8 spr; /* WR SPR - Scratch Pad Reg */
+};
+
+/* Where to read the interrupt register (8bits) */
+#define UART_CLASSIC_POLL_ADDR_OFFSET 0x40
+
+#define UART_EXAR654_ENHANCED_REGISTER_SET 0xBF
+
+#define UART_16654_FCR_TXTRIGGER_8 0x0
+#define UART_16654_FCR_TXTRIGGER_16 0x10
+#define UART_16654_FCR_TXTRIGGER_32 0x20
+#define UART_16654_FCR_TXTRIGGER_56 0x30
+
+#define UART_16654_FCR_RXTRIGGER_8 0x0
+#define UART_16654_FCR_RXTRIGGER_16 0x40
+#define UART_16654_FCR_RXTRIGGER_56 0x80
+#define UART_16654_FCR_RXTRIGGER_60 0xC0
+
+#define UART_IIR_CTSRTS 0x20 /* Received CTS/RTS change of state */
+#define UART_IIR_RDI_TIMEOUT 0x0C /* Receiver data TIMEOUT */
+
+/*
+ * These are the EXTENDED definitions for the Exar 654's Interrupt
+ * Enable Register.
+ */
+#define UART_EXAR654_EFR_ECB 0x10 /* Enhanced control bit */
+#define UART_EXAR654_EFR_IXON 0x2 /* Receiver compares Xon1/Xoff1 */
+#define UART_EXAR654_EFR_IXOFF 0x8 /* Transmit Xon1/Xoff1 */
+#define UART_EXAR654_EFR_RTSDTR 0x40 /* Auto RTS/DTR Flow Control Enable */
+#define UART_EXAR654_EFR_CTSDSR 0x80 /* Auto CTS/DSR Flow COntrol Enable */
+
+#define UART_EXAR654_XOFF_DETECT 0x1 /* Indicates whether chip saw an incoming XOFF char */
+#define UART_EXAR654_XON_DETECT 0x2 /* Indicates whether chip saw an incoming XON char */
+
+#define UART_EXAR654_IER_XOFF 0x20 /* Xoff Interrupt Enable */
+#define UART_EXAR654_IER_RTSDTR 0x40 /* Output Interrupt Enable */
+#define UART_EXAR654_IER_CTSDSR 0x80 /* Input Interrupt Enable */
+
+/*
+ * Our Global Variables
+ */
+extern struct board_ops dgnc_cls_ops;
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_driver.c b/drivers/staging/dgnc/dgnc_driver.c
new file mode 100644
index 00000000000..5af8300dfb0
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_driver.c
@@ -0,0 +1,934 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include "dgnc_driver.h"
+#include "dgnc_pci.h"
+#include "dpacompat.h"
+#include "dgnc_mgmt.h"
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+#include "dgnc_cls.h"
+#include "dgnc_neo.h"
+#include "dgnc_sysfs.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Digi International, http://www.digi.com");
+MODULE_DESCRIPTION("Driver for the Digi International Neo and Classic PCI based product line");
+MODULE_SUPPORTED_DEVICE("dgnc");
+
+/*
+ * insmod command line overrideable parameters
+ *
+ * NOTE: we use a set of macros to create the variables, which allows
+ * us to specify the variable type, name, initial value, and description.
+ */
+PARM_INT(debug, 0x00, 0644, "Driver debugging level");
+PARM_INT(rawreadok, 1, 0644, "Bypass flip buffers on input");
+PARM_INT(trcbuf_size, 0x100000, 0644, "Debugging trace buffer size.");
+
+/**************************************************************************
+ *
+ * protos for this file
+ *
+ */
+static int dgnc_start(void);
+static int dgnc_finalize_board_init(struct dgnc_board *brd);
+static void dgnc_init_globals(void);
+static int dgnc_found_board(struct pci_dev *pdev, int id);
+static void dgnc_cleanup_board(struct dgnc_board *brd);
+static void dgnc_poll_handler(ulong dummy);
+static int dgnc_init_pci(void);
+static int dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
+static void dgnc_remove_one(struct pci_dev *dev);
+static int dgnc_probe1(struct pci_dev *pdev, int card_type);
+static void dgnc_do_remap(struct dgnc_board *brd);
+
+/* Driver load/unload functions */
+int dgnc_init_module(void);
+void dgnc_cleanup_module(void);
+
+module_init(dgnc_init_module);
+module_exit(dgnc_cleanup_module);
+
+
+/*
+ * File operations permitted on Control/Management major.
+ */
+static const struct file_operations dgnc_BoardFops =
+{
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = dgnc_mgmt_ioctl,
+ .open = dgnc_mgmt_open,
+ .release = dgnc_mgmt_close
+};
+
+
+/*
+ * Globals
+ */
+uint dgnc_NumBoards;
+struct dgnc_board *dgnc_Board[MAXBOARDS];
+DEFINE_SPINLOCK(dgnc_global_lock);
+int dgnc_driver_state = DRIVER_INITIALIZED;
+ulong dgnc_poll_counter;
+uint dgnc_Major;
+int dgnc_poll_tick = 20; /* Poll interval - 20 ms */
+
+/*
+ * Static vars.
+ */
+static uint dgnc_Major_Control_Registered = FALSE;
+static uint dgnc_driver_start = FALSE;
+
+static struct class *dgnc_class;
+
+/*
+ * Poller stuff
+ */
+static DEFINE_SPINLOCK(dgnc_poll_lock); /* Poll scheduling lock */
+static ulong dgnc_poll_time; /* Time of next poll */
+static uint dgnc_poll_stop; /* Used to tell poller to stop */
+static struct timer_list dgnc_poll_timer;
+
+
+static struct pci_device_id dgnc_pci_tbl[] = {
+ { DIGI_VID, PCI_DEVICE_CLASSIC_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { DIGI_VID, PCI_DEVICE_CLASSIC_4_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 },
+ { DIGI_VID, PCI_DEVICE_CLASSIC_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2 },
+ { DIGI_VID, PCI_DEVICE_CLASSIC_8_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3 },
+ { DIGI_VID, PCI_DEVICE_NEO_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 },
+ { DIGI_VID, PCI_DEVICE_NEO_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 },
+ { DIGI_VID, PCI_DEVICE_NEO_2DB9_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6 },
+ { DIGI_VID, PCI_DEVICE_NEO_2DB9PRI_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 7 },
+ { DIGI_VID, PCI_DEVICE_NEO_2RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 8 },
+ { DIGI_VID, PCI_DEVICE_NEO_2RJ45PRI_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 9 },
+ { DIGI_VID, PCI_DEVICE_NEO_1_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 10 },
+ { DIGI_VID, PCI_DEVICE_NEO_1_422_485_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 11 },
+ { DIGI_VID, PCI_DEVICE_NEO_2_422_485_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 12 },
+ { DIGI_VID, PCI_DEVICE_NEO_EXPRESS_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 13 },
+ { DIGI_VID, PCI_DEVICE_NEO_EXPRESS_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 14 },
+ { DIGI_VID, PCI_DEVICE_NEO_EXPRESS_4RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 15 },
+ { DIGI_VID, PCI_DEVICE_NEO_EXPRESS_8RJ45_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 16 },
+ {0,} /* 0 terminated list. */
+};
+MODULE_DEVICE_TABLE(pci, dgnc_pci_tbl);
+
+struct board_id {
+ uchar *name;
+ uint maxports;
+ unsigned int is_pci_express;
+};
+
+static struct board_id dgnc_Ids[] = {
+ { PCI_DEVICE_CLASSIC_4_PCI_NAME, 4, 0 },
+ { PCI_DEVICE_CLASSIC_4_422_PCI_NAME, 4, 0 },
+ { PCI_DEVICE_CLASSIC_8_PCI_NAME, 8, 0 },
+ { PCI_DEVICE_CLASSIC_8_422_PCI_NAME, 8, 0 },
+ { PCI_DEVICE_NEO_4_PCI_NAME, 4, 0 },
+ { PCI_DEVICE_NEO_8_PCI_NAME, 8, 0 },
+ { PCI_DEVICE_NEO_2DB9_PCI_NAME, 2, 0 },
+ { PCI_DEVICE_NEO_2DB9PRI_PCI_NAME, 2, 0 },
+ { PCI_DEVICE_NEO_2RJ45_PCI_NAME, 2, 0 },
+ { PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME, 2, 0 },
+ { PCI_DEVICE_NEO_1_422_PCI_NAME, 1, 0 },
+ { PCI_DEVICE_NEO_1_422_485_PCI_NAME, 1, 0 },
+ { PCI_DEVICE_NEO_2_422_485_PCI_NAME, 2, 0 },
+ { PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME, 8, 1 },
+ { PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME, 4, 1 },
+ { PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME, 4, 1 },
+ { PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME, 8, 1 },
+ { NULL, 0, 0 }
+};
+
+static struct pci_driver dgnc_driver = {
+ .name = "dgnc",
+ .probe = dgnc_init_one,
+ .id_table = dgnc_pci_tbl,
+ .remove = dgnc_remove_one,
+};
+
+
+char *dgnc_state_text[] = {
+ "Board Failed",
+ "Board Found",
+ "Board READY",
+};
+
+char *dgnc_driver_state_text[] = {
+ "Driver Initialized",
+ "Driver Ready."
+};
+
+
+
+/************************************************************************
+ *
+ * Driver load/unload functions
+ *
+ ************************************************************************/
+
+
+/*
+ * init_module()
+ *
+ * Module load. This is where it all starts.
+ */
+int dgnc_init_module(void)
+{
+ int rc = 0;
+
+ APR(("%s, Digi International Part Number %s\n", DG_NAME, DG_PART));
+
+ /*
+ * Initialize global stuff
+ */
+ rc = dgnc_start();
+
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Find and configure all the cards
+ */
+ rc = dgnc_init_pci();
+
+ /*
+ * If something went wrong in the scan, bail out of driver.
+ */
+ if (rc < 0) {
+ /* Only unregister the pci driver if it was actually registered. */
+ if (dgnc_NumBoards)
+ pci_unregister_driver(&dgnc_driver);
+ else
+ pr_warn("WARNING: dgnc driver load failed. No Digi Neo or Classic boards found.\n");
+
+ dgnc_cleanup_module();
+ } else {
+ dgnc_create_driver_sysfiles(&dgnc_driver);
+ }
+
+ DPR_INIT(("Finished init_module. Returning %d\n", rc));
+ return rc;
+}
+
+
+/*
+ * Start of driver.
+ */
+static int dgnc_start(void)
+{
+ int rc = 0;
+ unsigned long flags;
+
+ if (dgnc_driver_start == FALSE) {
+
+ dgnc_driver_start = TRUE;
+
+ /* make sure that the globals are init'd before we do anything else */
+ dgnc_init_globals();
+
+ dgnc_NumBoards = 0;
+
+ APR(("For the tools package or updated drivers please visit http://www.digi.com\n"));
+
+ /*
+ * Register our base character device into the kernel.
+ * This allows the download daemon to connect to the downld device
+ * before any of the boards are init'ed.
+ */
+ if (!dgnc_Major_Control_Registered) {
+ /*
+ * Register management/dpa devices
+ */
+ rc = register_chrdev(0, "dgnc", &dgnc_BoardFops);
+ if (rc <= 0) {
+ APR(("Can't register dgnc driver device (%d)\n", rc));
+ rc = -ENXIO;
+ return rc;
+ }
+ dgnc_Major = rc;
+
+ dgnc_class = class_create(THIS_MODULE, "dgnc_mgmt");
+ device_create(dgnc_class, NULL,
+ MKDEV(dgnc_Major, 0),
+ NULL, "dgnc_mgmt");
+ dgnc_Major_Control_Registered = TRUE;
+ }
+
+ /*
+ * Init any global tty stuff.
+ */
+ rc = dgnc_tty_preinit();
+
+ if (rc < 0) {
+ APR(("tty preinit - not enough memory (%d)\n", rc));
+ return rc;
+ }
+
+ /* Start the poller */
+ DGNC_LOCK(dgnc_poll_lock, flags);
+ init_timer(&dgnc_poll_timer);
+ dgnc_poll_timer.function = dgnc_poll_handler;
+ dgnc_poll_timer.data = 0;
+ dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick);
+ dgnc_poll_timer.expires = dgnc_poll_time;
+ DGNC_UNLOCK(dgnc_poll_lock, flags);
+
+ add_timer(&dgnc_poll_timer);
+
+ dgnc_driver_state = DRIVER_READY;
+ }
+
+ return rc;
+}
+
+/*
+ * Register pci driver, and return how many boards we have.
+ */
+static int dgnc_init_pci(void)
+{
+ return pci_register_driver(&dgnc_driver);
+}
+
+
+/* returns count (>= 0), or negative on error */
+static int dgnc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int rc;
+
+ /* wake up and enable device */
+ rc = pci_enable_device(pdev);
+
+ if (rc < 0) {
+ rc = -EIO;
+ } else {
+ rc = dgnc_probe1(pdev, ent->driver_data);
+ if (rc == 0) {
+ dgnc_NumBoards++;
+ DPR_INIT(("Incrementing numboards to %d\n", dgnc_NumBoards));
+ }
+ }
+ return rc;
+}
+
+static int dgnc_probe1(struct pci_dev *pdev, int card_type)
+{
+ return dgnc_found_board(pdev, card_type);
+}
+
+
+static void dgnc_remove_one(struct pci_dev *dev)
+{
+ /* Do Nothing */
+}
+
+/*
+ * dgnc_cleanup_module()
+ *
+ * Module unload. This is where it all ends.
+ */
+void dgnc_cleanup_module(void)
+{
+ int i;
+ ulong lock_flags;
+
+ DGNC_LOCK(dgnc_poll_lock, lock_flags);
+ dgnc_poll_stop = 1;
+ DGNC_UNLOCK(dgnc_poll_lock, lock_flags);
+
+ /* Turn off poller right away. */
+ del_timer_sync(&dgnc_poll_timer);
+
+ dgnc_remove_driver_sysfiles(&dgnc_driver);
+
+ if (dgnc_Major_Control_Registered) {
+ device_destroy(dgnc_class, MKDEV(dgnc_Major, 0));
+ class_destroy(dgnc_class);
+ unregister_chrdev(dgnc_Major, "dgnc");
+ }
+
+ for (i = 0; i < dgnc_NumBoards; ++i) {
+ dgnc_remove_ports_sysfiles(dgnc_Board[i]);
+ dgnc_tty_uninit(dgnc_Board[i]);
+ dgnc_cleanup_board(dgnc_Board[i]);
+ }
+
+ dgnc_tty_post_uninit();
+
+#if defined(DGNC_TRACER)
+ /* last thing, make sure we release the tracebuffer */
+ dgnc_tracer_free();
+#endif
+ if (dgnc_NumBoards)
+ pci_unregister_driver(&dgnc_driver);
+}
+
+
+/*
+ * dgnc_cleanup_board()
+ *
+ * Free all the memory associated with a board
+ */
+static void dgnc_cleanup_board(struct dgnc_board *brd)
+{
+ int i = 0;
+
+ if(!brd || brd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ switch (brd->device) {
+ case PCI_DEVICE_CLASSIC_4_DID:
+ case PCI_DEVICE_CLASSIC_8_DID:
+ case PCI_DEVICE_CLASSIC_4_422_DID:
+ case PCI_DEVICE_CLASSIC_8_422_DID:
+
+ /* Tell card not to interrupt anymore. */
+ outb(0, brd->iobase + 0x4c);
+ break;
+
+ default:
+ break;
+ }
+
+ if (brd->irq)
+ free_irq(brd->irq, brd);
+
+ tasklet_kill(&brd->helper_tasklet);
+
+ if (brd->re_map_membase) {
+ iounmap(brd->re_map_membase);
+ brd->re_map_membase = NULL;
+ }
+
+ if (brd->msgbuf_head) {
+ unsigned long flags;
+
+ DGNC_LOCK(dgnc_global_lock, flags);
+ brd->msgbuf = NULL;
+ printk("%s", brd->msgbuf_head);
+ kfree(brd->msgbuf_head);
+ brd->msgbuf_head = NULL;
+ DGNC_UNLOCK(dgnc_global_lock, flags);
+ }
+
+ /* Free all allocated channels structs */
+ for (i = 0; i < MAXPORTS ; i++) {
+ if (brd->channels[i]) {
+ kfree(brd->channels[i]->ch_rqueue);
+ kfree(brd->channels[i]->ch_equeue);
+ kfree(brd->channels[i]->ch_wqueue);
+ kfree(brd->channels[i]);
+ brd->channels[i] = NULL;
+ }
+ }
+
+ kfree(brd->flipbuf);
+
+ dgnc_Board[brd->boardnum] = NULL;
+
+ kfree(brd);
+}
+
+
+/*
+ * dgnc_found_board()
+ *
+ * A board has been found, init it.
+ */
+static int dgnc_found_board(struct pci_dev *pdev, int id)
+{
+ struct dgnc_board *brd;
+ unsigned int pci_irq;
+ int i = 0;
+ int rc = 0;
+ unsigned long flags;
+
+ /* get the board structure and prep it */
+ brd = dgnc_Board[dgnc_NumBoards] =
+ kzalloc(sizeof(*brd), GFP_KERNEL);
+ if (!brd)
+ return -ENOMEM;
+
+ /* make a temporary message buffer for the boot messages */
+ brd->msgbuf = brd->msgbuf_head =
+ kzalloc(sizeof(u8) * 8192, GFP_KERNEL);
+ if (!brd->msgbuf) {
+ kfree(brd);
+ return -ENOMEM;
+ }
+
+ /* store the info for the board we've found */
+ brd->magic = DGNC_BOARD_MAGIC;
+ brd->boardnum = dgnc_NumBoards;
+ brd->vendor = dgnc_pci_tbl[id].vendor;
+ brd->device = dgnc_pci_tbl[id].device;
+ brd->pdev = pdev;
+ brd->pci_bus = pdev->bus->number;
+ brd->pci_slot = PCI_SLOT(pdev->devfn);
+ brd->name = dgnc_Ids[id].name;
+ brd->maxports = dgnc_Ids[id].maxports;
+ if (dgnc_Ids[i].is_pci_express)
+ brd->bd_flags |= BD_IS_PCI_EXPRESS;
+ brd->dpastatus = BD_NOFEP;
+ init_waitqueue_head(&brd->state_wait);
+
+ DGNC_SPINLOCK_INIT(brd->bd_lock);
+ DGNC_SPINLOCK_INIT(brd->bd_intr_lock);
+
+ brd->state = BOARD_FOUND;
+
+ for (i = 0; i < MAXPORTS; i++)
+ brd->channels[i] = NULL;
+
+ /* store which card & revision we have */
+ pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor);
+ pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice);
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev);
+
+ pci_irq = pdev->irq;
+ brd->irq = pci_irq;
+
+
+ switch(brd->device) {
+
+ case PCI_DEVICE_CLASSIC_4_DID:
+ case PCI_DEVICE_CLASSIC_8_DID:
+ case PCI_DEVICE_CLASSIC_4_422_DID:
+ case PCI_DEVICE_CLASSIC_8_422_DID:
+
+ brd->dpatype = T_CLASSIC | T_PCIBUS;
+
+ DPR_INIT(("dgnc_found_board - Classic.\n"));
+
+ /*
+ * For PCI ClassicBoards
+ * PCI Local Address (i.e. "resource" number) space
+ * 0 PLX Memory Mapped Config
+ * 1 PLX I/O Mapped Config
+ * 2 I/O Mapped UARTs and Status
+ * 3 Memory Mapped VPD
+ * 4 Memory Mapped UARTs and Status
+ */
+
+
+ /* get the PCI Base Address Registers */
+ brd->membase = pci_resource_start(pdev, 4);
+
+ if (!brd->membase) {
+ APR(("card has no PCI IO resources, failing board.\n"));
+ return -ENODEV;
+ }
+
+ brd->membase_end = pci_resource_end(pdev, 4);
+
+ if (brd->membase & 1)
+ brd->membase &= ~3;
+ else
+ brd->membase &= ~15;
+
+ brd->iobase = pci_resource_start(pdev, 1);
+ brd->iobase_end = pci_resource_end(pdev, 1);
+ brd->iobase = ((unsigned int) (brd->iobase)) & 0xFFFE;
+
+ /* Assign the board_ops struct */
+ brd->bd_ops = &dgnc_cls_ops;
+
+ brd->bd_uart_offset = 0x8;
+ brd->bd_dividend = 921600;
+
+ dgnc_do_remap(brd);
+
+ /* Get and store the board VPD, if it exists */
+ brd->bd_ops->vpd(brd);
+
+ /*
+ * Enable Local Interrupt 1 (0x1),
+ * Local Interrupt 1 Polarity Active high (0x2),
+ * Enable PCI interrupt (0x40)
+ */
+ outb(0x43, brd->iobase + 0x4c);
+
+ break;
+
+
+ case PCI_DEVICE_NEO_4_DID:
+ case PCI_DEVICE_NEO_8_DID:
+ case PCI_DEVICE_NEO_2DB9_DID:
+ case PCI_DEVICE_NEO_2DB9PRI_DID:
+ case PCI_DEVICE_NEO_2RJ45_DID:
+ case PCI_DEVICE_NEO_2RJ45PRI_DID:
+ case PCI_DEVICE_NEO_1_422_DID:
+ case PCI_DEVICE_NEO_1_422_485_DID:
+ case PCI_DEVICE_NEO_2_422_485_DID:
+ case PCI_DEVICE_NEO_EXPRESS_8_DID:
+ case PCI_DEVICE_NEO_EXPRESS_4_DID:
+ case PCI_DEVICE_NEO_EXPRESS_4RJ45_DID:
+ case PCI_DEVICE_NEO_EXPRESS_8RJ45_DID:
+
+ /*
+ * This chip is set up 100% when we get to it.
+ * No need to enable global interrupts or anything.
+ */
+ if (brd->bd_flags & BD_IS_PCI_EXPRESS)
+ brd->dpatype = T_NEO_EXPRESS | T_PCIBUS;
+ else
+ brd->dpatype = T_NEO | T_PCIBUS;
+
+ DPR_INIT(("dgnc_found_board - NEO.\n"));
+
+ /* get the PCI Base Address Registers */
+ brd->membase = pci_resource_start(pdev, 0);
+ brd->membase_end = pci_resource_end(pdev, 0);
+
+ if (brd->membase & 1)
+ brd->membase &= ~3;
+ else
+ brd->membase &= ~15;
+
+ /* Assign the board_ops struct */
+ brd->bd_ops = &dgnc_neo_ops;
+
+ brd->bd_uart_offset = 0x200;
+ brd->bd_dividend = 921600;
+
+ dgnc_do_remap(brd);
+
+ if (brd->re_map_membase) {
+
+ /* After remap is complete, we need to read and store the dvid */
+ brd->dvid = readb(brd->re_map_membase + 0x8D);
+
+ /* Get and store the board VPD, if it exists */
+ brd->bd_ops->vpd(brd);
+ }
+ break;
+
+ default:
+ APR(("Did not find any compatible Neo or Classic PCI boards in system.\n"));
+ return -ENXIO;
+
+ }
+
+ /*
+ * Do tty device initialization.
+ */
+
+ rc = dgnc_tty_register(brd);
+ if (rc < 0) {
+ dgnc_tty_uninit(brd);
+ APR(("Can't register tty devices (%d)\n", rc));
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+ goto failed;
+ }
+
+ rc = dgnc_finalize_board_init(brd);
+ if (rc < 0) {
+ APR(("Can't finalize board init (%d)\n", rc));
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+
+ goto failed;
+ }
+
+ rc = dgnc_tty_init(brd);
+ if (rc < 0) {
+ dgnc_tty_uninit(brd);
+ APR(("Can't init tty devices (%d)\n", rc));
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+
+ goto failed;
+ }
+
+ brd->state = BOARD_READY;
+ brd->dpastatus = BD_RUNNING;
+
+ dgnc_create_ports_sysfiles(brd);
+
+ /* init our poll helper tasklet */
+ tasklet_init(&brd->helper_tasklet, brd->bd_ops->tasklet, (unsigned long) brd);
+
+ DPR_INIT(("dgnc_scan(%d) - printing out the msgbuf\n", i));
+ DGNC_LOCK(dgnc_global_lock, flags);
+ brd->msgbuf = NULL;
+ printk("%s", brd->msgbuf_head);
+ kfree(brd->msgbuf_head);
+ brd->msgbuf_head = NULL;
+ DGNC_UNLOCK(dgnc_global_lock, flags);
+
+ /*
+ * allocate flip buffer for board.
+ *
+ * Okay to malloc with GFP_KERNEL, we are not at interrupt
+ * context, and there are no locks held.
+ */
+ brd->flipbuf = kzalloc(MYFLIPLEN, GFP_KERNEL);
+
+ wake_up_interruptible(&brd->state_wait);
+
+ return 0;
+
+failed:
+
+ return -ENXIO;
+
+}
+
+
+static int dgnc_finalize_board_init(struct dgnc_board *brd) {
+ int rc = 0;
+
+ DPR_INIT(("dgnc_finalize_board_init() - start\n"));
+
+ if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+ return -ENODEV;
+
+ DPR_INIT(("dgnc_finalize_board_init() - start #2\n"));
+
+ if (brd->irq) {
+ rc = request_irq(brd->irq, brd->bd_ops->intr,
+ IRQF_SHARED, "DGNC", brd);
+
+ if (rc) {
+ dev_err(&brd->pdev->dev,
+ "Failed to hook IRQ %d\n", brd->irq);
+ brd->state = BOARD_FAILED;
+ brd->dpastatus = BD_NOFEP;
+ rc = -ENODEV;
+ } else {
+ DPR_INIT(("Requested and received usage of IRQ %d\n",
+ brd->irq));
+ }
+ }
+ return rc;
+}
+
+/*
+ * Remap PCI memory.
+ */
+static void dgnc_do_remap(struct dgnc_board *brd)
+{
+
+ if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ brd->re_map_membase = ioremap(brd->membase, 0x1000);
+
+ DPR_INIT(("remapped mem: 0x%p\n", brd->re_map_membase));
+}
+
+
+/*****************************************************************************
+*
+* Function:
+*
+* dgnc_poll_handler
+*
+* Author:
+*
+* Scott H Kilau
+*
+* Parameters:
+*
+* dummy -- ignored
+*
+* Return Values:
+*
+* none
+*
+* Description:
+*
+* As each timer expires, it determines (a) whether the "transmit"
+* waiter needs to be woken up, and (b) whether the poller needs to
+* be rescheduled.
+*
+******************************************************************************/
+
+static void dgnc_poll_handler(ulong dummy)
+{
+ struct dgnc_board *brd;
+ unsigned long lock_flags;
+ int i;
+ unsigned long new_time;
+
+ dgnc_poll_counter++;
+
+ /*
+ * Do not start the board state machine until
+ * driver tells us its up and running, and has
+ * everything it needs.
+ */
+ if (dgnc_driver_state != DRIVER_READY)
+ goto schedule_poller;
+
+ /* Go thru each board, kicking off a tasklet for each if needed */
+ for (i = 0; i < dgnc_NumBoards; i++) {
+ brd = dgnc_Board[i];
+
+ DGNC_LOCK(brd->bd_lock, lock_flags);
+
+ /* If board is in a failed state, don't bother scheduling a tasklet */
+ if (brd->state == BOARD_FAILED) {
+ DGNC_UNLOCK(brd->bd_lock, lock_flags);
+ continue;
+ }
+
+ /* Schedule a poll helper task */
+ tasklet_schedule(&brd->helper_tasklet);
+
+ DGNC_UNLOCK(brd->bd_lock, lock_flags);
+ }
+
+schedule_poller:
+
+ /*
+ * Schedule ourself back at the nominal wakeup interval.
+ */
+ DGNC_LOCK(dgnc_poll_lock, lock_flags);
+ dgnc_poll_time += dgnc_jiffies_from_ms(dgnc_poll_tick);
+
+ new_time = dgnc_poll_time - jiffies;
+
+ if ((ulong) new_time >= 2 * dgnc_poll_tick)
+ dgnc_poll_time = jiffies + dgnc_jiffies_from_ms(dgnc_poll_tick);
+
+ init_timer(&dgnc_poll_timer);
+ dgnc_poll_timer.function = dgnc_poll_handler;
+ dgnc_poll_timer.data = 0;
+ dgnc_poll_timer.expires = dgnc_poll_time;
+ DGNC_UNLOCK(dgnc_poll_lock, lock_flags);
+
+ if (!dgnc_poll_stop)
+ add_timer(&dgnc_poll_timer);
+}
+
+/*
+ * dgnc_init_globals()
+ *
+ * This is where we initialize the globals from the static insmod
+ * configuration variables. These are declared near the head of
+ * this file.
+ */
+static void dgnc_init_globals(void)
+{
+ int i = 0;
+
+ dgnc_rawreadok = rawreadok;
+ dgnc_trcbuf_size = trcbuf_size;
+ dgnc_debug = debug;
+
+ for (i = 0; i < MAXBOARDS; i++)
+ dgnc_Board[i] = NULL;
+
+ init_timer(&dgnc_poll_timer);
+}
+
+
+/************************************************************************
+ *
+ * Utility functions
+ *
+ ************************************************************************/
+
+/*
+ * dgnc_ms_sleep()
+ *
+ * Put the driver to sleep for x ms's
+ *
+ * Returns 0 if timed out, !0 (showing signal) if interrupted by a signal.
+ */
+int dgnc_ms_sleep(ulong ms)
+{
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout((ms * HZ) / 1000);
+ return signal_pending(current);
+}
+
+
+
+/*
+ * dgnc_ioctl_name() : Returns a text version of each ioctl value.
+ */
+char *dgnc_ioctl_name(int cmd)
+{
+ switch(cmd) {
+
+ case TCGETA: return "TCGETA";
+ case TCGETS: return "TCGETS";
+ case TCSETA: return "TCSETA";
+ case TCSETS: return "TCSETS";
+ case TCSETAW: return "TCSETAW";
+ case TCSETSW: return "TCSETSW";
+ case TCSETAF: return "TCSETAF";
+ case TCSETSF: return "TCSETSF";
+ case TCSBRK: return "TCSBRK";
+ case TCXONC: return "TCXONC";
+ case TCFLSH: return "TCFLSH";
+ case TIOCGSID: return "TIOCGSID";
+
+ case TIOCGETD: return "TIOCGETD";
+ case TIOCSETD: return "TIOCSETD";
+ case TIOCGWINSZ: return "TIOCGWINSZ";
+ case TIOCSWINSZ: return "TIOCSWINSZ";
+
+ case TIOCMGET: return "TIOCMGET";
+ case TIOCMSET: return "TIOCMSET";
+ case TIOCMBIS: return "TIOCMBIS";
+ case TIOCMBIC: return "TIOCMBIC";
+
+ /* from digi.h */
+ case DIGI_SETA: return "DIGI_SETA";
+ case DIGI_SETAW: return "DIGI_SETAW";
+ case DIGI_SETAF: return "DIGI_SETAF";
+ case DIGI_SETFLOW: return "DIGI_SETFLOW";
+ case DIGI_SETAFLOW: return "DIGI_SETAFLOW";
+ case DIGI_GETFLOW: return "DIGI_GETFLOW";
+ case DIGI_GETAFLOW: return "DIGI_GETAFLOW";
+ case DIGI_GETA: return "DIGI_GETA";
+ case DIGI_GEDELAY: return "DIGI_GEDELAY";
+ case DIGI_SEDELAY: return "DIGI_SEDELAY";
+ case DIGI_GETCUSTOMBAUD: return "DIGI_GETCUSTOMBAUD";
+ case DIGI_SETCUSTOMBAUD: return "DIGI_SETCUSTOMBAUD";
+ case TIOCMODG: return "TIOCMODG";
+ case TIOCMODS: return "TIOCMODS";
+ case TIOCSDTR: return "TIOCSDTR";
+ case TIOCCDTR: return "TIOCCDTR";
+
+ default: return "unknown";
+ }
+}
diff --git a/drivers/staging/dgnc/dgnc_driver.h b/drivers/staging/dgnc/dgnc_driver.h
new file mode 100644
index 00000000000..3519b803e75
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_driver.h
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *************************************************************************
+ *
+ * Driver includes
+ *
+ *************************************************************************/
+
+#ifndef __DGNC_DRIVER_H
+#define __DGNC_DRIVER_H
+
+#include <linux/types.h> /* To pick up the varions Linux types */
+#include <linux/tty.h> /* To pick up the various tty structs/defines */
+#include <linux/interrupt.h> /* For irqreturn_t type */
+
+#include "dgnc_types.h" /* Additional types needed by the Digi header files */
+#include "digi.h" /* Digi specific ioctl header */
+#include "dgnc_kcompat.h" /* Kernel 2.4/2.6 compat includes */
+#include "dgnc_sysfs.h" /* Support for SYSFS */
+
+/*************************************************************************
+ *
+ * Driver defines
+ *
+ *************************************************************************/
+
+/*
+ * Driver identification, error and debugging statments
+ *
+ * In theory, you can change all occurrences of "digi" in the next
+ * three lines, and the driver printk's will all automagically change.
+ *
+ * APR((fmt, args, ...)); Always prints message
+ * DPR((fmt, args, ...)); Only prints if DGNC_TRACER is defined at
+ * compile time and dgnc_debug!=0
+ */
+#define PROCSTR "dgnc" /* /proc entries */
+#define DEVSTR "/dev/dg/dgnc" /* /dev entries */
+#define DRVSTR "dgnc" /* Driver name string
+ * displayed by APR */
+#define APR(args) do { PRINTF_TO_KMEM(args); printk(DRVSTR": "); printk args; \
+ } while (0)
+#define RAPR(args) do { PRINTF_TO_KMEM(args); printk args; } while (0)
+
+#define TRC_TO_CONSOLE 1
+
+/*
+ * Debugging levels can be set using debug insmod variable
+ * They can also be compiled out completely.
+ */
+
+#define DBG_INIT (dgnc_debug & 0x01)
+#define DBG_BASIC (dgnc_debug & 0x02)
+#define DBG_CORE (dgnc_debug & 0x04)
+
+#define DBG_OPEN (dgnc_debug & 0x08)
+#define DBG_CLOSE (dgnc_debug & 0x10)
+#define DBG_READ (dgnc_debug & 0x20)
+#define DBG_WRITE (dgnc_debug & 0x40)
+
+#define DBG_IOCTL (dgnc_debug & 0x80)
+
+#define DBG_PROC (dgnc_debug & 0x100)
+#define DBG_PARAM (dgnc_debug & 0x200)
+#define DBG_PSCAN (dgnc_debug & 0x400)
+#define DBG_EVENT (dgnc_debug & 0x800)
+
+#define DBG_DRAIN (dgnc_debug & 0x1000)
+#define DBG_MSIGS (dgnc_debug & 0x2000)
+
+#define DBG_MGMT (dgnc_debug & 0x4000)
+#define DBG_INTR (dgnc_debug & 0x8000)
+
+#define DBG_CARR (dgnc_debug & 0x10000)
+
+
+#if defined(DGNC_TRACER)
+
+# if defined(TRC_TO_KMEM)
+/* Choose one: */
+# define TRC_ON_OVERFLOW_WRAP_AROUND
+# undef TRC_ON_OVERFLOW_SHIFT_BUFFER
+# endif //TRC_TO_KMEM
+
+# define TRC_MAXMSG 1024
+# define TRC_OVERFLOW "(OVERFLOW)"
+# define TRC_DTRC "/usr/bin/dtrc"
+
+#if defined TRC_TO_CONSOLE
+#define PRINTF_TO_CONSOLE(args) { printk(DRVSTR": "); printk args; }
+#else //!defined TRACE_TO_CONSOLE
+#define PRINTF_TO_CONSOLE(args)
+#endif
+
+#if defined TRC_TO_KMEM
+#define PRINTF_TO_KMEM(args) dgnc_tracef args
+#else //!defined TRC_TO_KMEM
+#define PRINTF_TO_KMEM(args)
+#endif
+
+#define TRC(args) { PRINTF_TO_KMEM(args); PRINTF_TO_CONSOLE(args) }
+
+# define DPR_INIT(ARGS) if (DBG_INIT) TRC(ARGS)
+# define DPR_BASIC(ARGS) if (DBG_BASIC) TRC(ARGS)
+# define DPR_CORE(ARGS) if (DBG_CORE) TRC(ARGS)
+# define DPR_OPEN(ARGS) if (DBG_OPEN) TRC(ARGS)
+# define DPR_CLOSE(ARGS) if (DBG_CLOSE) TRC(ARGS)
+# define DPR_READ(ARGS) if (DBG_READ) TRC(ARGS)
+# define DPR_WRITE(ARGS) if (DBG_WRITE) TRC(ARGS)
+# define DPR_IOCTL(ARGS) if (DBG_IOCTL) TRC(ARGS)
+# define DPR_PROC(ARGS) if (DBG_PROC) TRC(ARGS)
+# define DPR_PARAM(ARGS) if (DBG_PARAM) TRC(ARGS)
+# define DPR_PSCAN(ARGS) if (DBG_PSCAN) TRC(ARGS)
+# define DPR_EVENT(ARGS) if (DBG_EVENT) TRC(ARGS)
+# define DPR_DRAIN(ARGS) if (DBG_DRAIN) TRC(ARGS)
+# define DPR_CARR(ARGS) if (DBG_CARR) TRC(ARGS)
+# define DPR_MGMT(ARGS) if (DBG_MGMT) TRC(ARGS)
+# define DPR_INTR(ARGS) if (DBG_INTR) TRC(ARGS)
+# define DPR_MSIGS(ARGS) if (DBG_MSIGS) TRC(ARGS)
+
+# define DPR(ARGS) if (dgnc_debug) TRC(ARGS)
+# define P(X) dgnc_tracef(#X "=%p\n", X)
+# define X(X) dgnc_tracef(#X "=%x\n", X)
+
+#else//!defined DGNC_TRACER
+
+#define PRINTF_TO_KMEM(args)
+# define TRC(ARGS)
+# define DPR_INIT(ARGS)
+# define DPR_BASIC(ARGS)
+# define DPR_CORE(ARGS)
+# define DPR_OPEN(ARGS)
+# define DPR_CLOSE(ARGS)
+# define DPR_READ(ARGS)
+# define DPR_WRITE(ARGS)
+# define DPR_IOCTL(ARGS)
+# define DPR_PROC(ARGS)
+# define DPR_PARAM(ARGS)
+# define DPR_PSCAN(ARGS)
+# define DPR_EVENT(ARGS)
+# define DPR_DRAIN(ARGS)
+# define DPR_CARR(ARGS)
+# define DPR_MGMT(ARGS)
+# define DPR_INTR(ARGS)
+# define DPR_MSIGS(ARGS)
+
+# define DPR(args)
+
+#endif//DGNC_TRACER
+
+/* Number of boards we support at once. */
+#define MAXBOARDS 20
+#define MAXPORTS 8
+#define MAXTTYNAMELEN 200
+
+/* Our 3 magic numbers for our board, channel and unit structs */
+#define DGNC_BOARD_MAGIC 0x5c6df104
+#define DGNC_CHANNEL_MAGIC 0x6c6df104
+#define DGNC_UNIT_MAGIC 0x7c6df104
+
+/* Serial port types */
+#define DGNC_SERIAL 0
+#define DGNC_PRINT 1
+
+#define SERIAL_TYPE_NORMAL 1
+
+#define PORT_NUM(dev) ((dev) & 0x7f)
+#define IS_PRINT(dev) (((dev) & 0xff) >= 0x80)
+
+/* MAX number of stop characters we will send when our read queue is getting full */
+#define MAX_STOPS_SENT 5
+
+/* 4 extra for alignment play space */
+#define WRITEBUFLEN ((4096) + 4)
+#define MYFLIPLEN N_TTY_BUF_SIZE
+
+#define dgnc_jiffies_from_ms(a) (((a) * HZ) / 1000)
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially. This is the same structure that is defined
+ * as the default in tty_io.c with the same settings overriden as in serial.c
+ *
+ * In short, this should match the internal serial ports' defaults.
+ */
+#define DEFAULT_IFLAGS (ICRNL | IXON)
+#define DEFAULT_OFLAGS (OPOST | ONLCR)
+#define DEFAULT_CFLAGS (B9600 | CS8 | CREAD | HUPCL | CLOCAL)
+#define DEFAULT_LFLAGS (ISIG | ICANON | ECHO | ECHOE | ECHOK | \
+ ECHOCTL | ECHOKE | IEXTEN)
+
+#ifndef _POSIX_VDISABLE
+#define _POSIX_VDISABLE '\0'
+#endif
+
+#define SNIFF_MAX 65536 /* Sniff buffer size (2^n) */
+#define SNIFF_MASK (SNIFF_MAX - 1) /* Sniff wrap mask */
+
+/*
+ * Lock function/defines.
+ * Makes spotting lock/unlock locations easier.
+ */
+# define DGNC_SPINLOCK_INIT(x) spin_lock_init(&(x))
+# define DGNC_LOCK(x,y) spin_lock_irqsave(&(x), y)
+# define DGNC_UNLOCK(x,y) spin_unlock_irqrestore(&(x), y)
+
+/*
+ * All the possible states the driver can be while being loaded.
+ */
+enum {
+ DRIVER_INITIALIZED = 0,
+ DRIVER_READY
+};
+
+/*
+ * All the possible states the board can be while booting up.
+ */
+enum {
+ BOARD_FAILED = 0,
+ BOARD_FOUND,
+ BOARD_READY
+};
+
+
+/*************************************************************************
+ *
+ * Structures and closely related defines.
+ *
+ *************************************************************************/
+
+struct dgnc_board;
+struct channel_t;
+
+/************************************************************************
+ * Per board operations structure *
+ ************************************************************************/
+struct board_ops {
+ void (*tasklet) (unsigned long data);
+ irqreturn_t (*intr) (int irq, void *voidbrd);
+ void (*uart_init) (struct channel_t *ch);
+ void (*uart_off) (struct channel_t *ch);
+ int (*drain) (struct tty_struct *tty, uint seconds);
+ void (*param) (struct tty_struct *tty);
+ void (*vpd) (struct dgnc_board *brd);
+ void (*assert_modem_signals) (struct channel_t *ch);
+ void (*flush_uart_write) (struct channel_t *ch);
+ void (*flush_uart_read) (struct channel_t *ch);
+ void (*disable_receiver) (struct channel_t *ch);
+ void (*enable_receiver) (struct channel_t *ch);
+ void (*send_break) (struct channel_t *ch, int);
+ void (*send_start_character) (struct channel_t *ch);
+ void (*send_stop_character) (struct channel_t *ch);
+ void (*copy_data_from_queue_to_uart) (struct channel_t *ch);
+ uint (*get_uart_bytes_left) (struct channel_t *ch);
+ void (*send_immediate_char) (struct channel_t *ch, unsigned char);
+};
+
+/************************************************************************
+ * Device flag definitions for bd_flags.
+ ************************************************************************/
+#define BD_IS_PCI_EXPRESS 0x0001 /* Is a PCI Express board */
+
+
+/*
+ * Per-board information
+ */
+struct dgnc_board {
+ int magic; /* Board Magic number. */
+ int boardnum; /* Board number: 0-32 */
+
+ int type; /* Type of board */
+ char *name; /* Product Name */
+ struct pci_dev *pdev; /* Pointer to the pci_dev struct */
+ unsigned long bd_flags; /* Board flags */
+ u16 vendor; /* PCI vendor ID */
+ u16 device; /* PCI device ID */
+ u16 subvendor; /* PCI subsystem vendor ID */
+ u16 subdevice; /* PCI subsystem device ID */
+ uchar rev; /* PCI revision ID */
+ uint pci_bus; /* PCI bus value */
+ uint pci_slot; /* PCI slot value */
+ uint maxports; /* MAX ports this board can handle */
+ uchar dvid; /* Board specific device id */
+ uchar vpd[128]; /* VPD of board, if found */
+ uchar serial_num[20]; /* Serial number of board, if found in VPD */
+
+ spinlock_t bd_lock; /* Used to protect board */
+
+ spinlock_t bd_intr_lock; /* Used to protect the poller tasklet and
+ * the interrupt routine from each other.
+ */
+
+ uint state; /* State of card. */
+ wait_queue_head_t state_wait; /* Place to sleep on for state change */
+
+ struct tasklet_struct helper_tasklet; /* Poll helper tasklet */
+
+ uint nasync; /* Number of ports on card */
+
+ uint irq; /* Interrupt request number */
+ ulong intr_count; /* Count of interrupts */
+ ulong intr_modem; /* Count of interrupts */
+ ulong intr_tx; /* Count of interrupts */
+ ulong intr_rx; /* Count of interrupts */
+
+ ulong membase; /* Start of base memory of the card */
+ ulong membase_end; /* End of base memory of the card */
+
+ u8 __iomem *re_map_membase;/* Remapped memory of the card */
+
+ ulong iobase; /* Start of io base of the card */
+ ulong iobase_end; /* End of io base of the card */
+
+ uint bd_uart_offset; /* Space between each UART */
+
+ struct channel_t *channels[MAXPORTS]; /* array of pointers to our channels. */
+
+ struct tty_driver SerialDriver;
+ char SerialName[200];
+ struct tty_driver PrintDriver;
+ char PrintName[200];
+
+ uint dgnc_Major_Serial_Registered;
+ uint dgnc_Major_TransparentPrint_Registered;
+
+ uint dgnc_Serial_Major;
+ uint dgnc_TransparentPrint_Major;
+
+ uint TtyRefCnt;
+
+ char *flipbuf; /* Our flip buffer, alloced if board is found */
+
+ u16 dpatype; /* The board "type", as defined by DPA */
+ u16 dpastatus; /* The board "status", as defined by DPA */
+
+ /*
+ * Mgmt data.
+ */
+ char *msgbuf_head;
+ char *msgbuf;
+
+ uint bd_dividend; /* Board/UARTs specific dividend */
+
+ struct board_ops *bd_ops;
+
+ /* /proc/<board> entries */
+ struct proc_dir_entry *proc_entry_pointer;
+ struct dgnc_proc_entry *dgnc_board_table;
+
+};
+
+
+/************************************************************************
+ * Unit flag definitions for un_flags.
+ ************************************************************************/
+#define UN_ISOPEN 0x0001 /* Device is open */
+#define UN_CLOSING 0x0002 /* Line is being closed */
+#define UN_IMM 0x0004 /* Service immediately */
+#define UN_BUSY 0x0008 /* Some work this channel */
+#define UN_BREAKI 0x0010 /* Input break received */
+#define UN_PWAIT 0x0020 /* Printer waiting for terminal */
+#define UN_TIME 0x0040 /* Waiting on time */
+#define UN_EMPTY 0x0080 /* Waiting output queue empty */
+#define UN_LOW 0x0100 /* Waiting output low water mark*/
+#define UN_EXCL_OPEN 0x0200 /* Open for exclusive use */
+#define UN_WOPEN 0x0400 /* Device waiting for open */
+#define UN_WIOCTL 0x0800 /* Device waiting for open */
+#define UN_HANGUP 0x8000 /* Carrier lost */
+
+struct device;
+
+/************************************************************************
+ * Structure for terminal or printer unit.
+ ************************************************************************/
+struct un_t {
+ int magic; /* Unit Magic Number. */
+ struct channel_t *un_ch;
+ ulong un_time;
+ uint un_type;
+ uint un_open_count; /* Counter of opens to port */
+ struct tty_struct *un_tty;/* Pointer to unit tty structure */
+ uint un_flags; /* Unit flags */
+ wait_queue_head_t un_flags_wait; /* Place to sleep to wait on unit */
+ uint un_dev; /* Minor device number */
+ struct device *un_sysfs;
+};
+
+
+/************************************************************************
+ * Device flag definitions for ch_flags.
+ ************************************************************************/
+#define CH_PRON 0x0001 /* Printer on string */
+#define CH_STOP 0x0002 /* Output is stopped */
+#define CH_STOPI 0x0004 /* Input is stopped */
+#define CH_CD 0x0008 /* Carrier is present */
+#define CH_FCAR 0x0010 /* Carrier forced on */
+#define CH_HANGUP 0x0020 /* Hangup received */
+
+#define CH_RECEIVER_OFF 0x0040 /* Receiver is off */
+#define CH_OPENING 0x0080 /* Port in fragile open state */
+#define CH_CLOSING 0x0100 /* Port in fragile close state */
+#define CH_FIFO_ENABLED 0x0200 /* Port has FIFOs enabled */
+#define CH_TX_FIFO_EMPTY 0x0400 /* TX Fifo is completely empty */
+#define CH_TX_FIFO_LWM 0x0800 /* TX Fifo is below Low Water */
+#define CH_BREAK_SENDING 0x1000 /* Break is being sent */
+#define CH_LOOPBACK 0x2000 /* Channel is in lookback mode */
+#define CH_FLIPBUF_IN_USE 0x4000 /* Channel's flipbuf is in use */
+#define CH_BAUD0 0x08000 /* Used for checking B0 transitions */
+#define CH_FORCED_STOP 0x20000 /* Output is forcibly stopped */
+#define CH_FORCED_STOPI 0x40000 /* Input is forcibly stopped */
+
+/*
+ * Definitions for ch_sniff_flags
+ */
+#define SNIFF_OPEN 0x1
+#define SNIFF_WAIT_DATA 0x2
+#define SNIFF_WAIT_SPACE 0x4
+
+
+/* Our Read/Error/Write queue sizes */
+#define RQUEUEMASK 0x1FFF /* 8 K - 1 */
+#define EQUEUEMASK 0x1FFF /* 8 K - 1 */
+#define WQUEUEMASK 0x0FFF /* 4 K - 1 */
+#define RQUEUESIZE (RQUEUEMASK + 1)
+#define EQUEUESIZE RQUEUESIZE
+#define WQUEUESIZE (WQUEUEMASK + 1)
+
+
+/************************************************************************
+ * Channel information structure.
+ ************************************************************************/
+struct channel_t {
+ int magic; /* Channel Magic Number */
+ struct dgnc_board *ch_bd; /* Board structure pointer */
+ struct digi_t ch_digi; /* Transparent Print structure */
+ struct un_t ch_tun; /* Terminal unit info */
+ struct un_t ch_pun; /* Printer unit info */
+
+ spinlock_t ch_lock; /* provide for serialization */
+ wait_queue_head_t ch_flags_wait;
+
+ uint ch_portnum; /* Port number, 0 offset. */
+ uint ch_open_count; /* open count */
+ uint ch_flags; /* Channel flags */
+
+ ulong ch_close_delay; /* How long we should drop RTS/DTR for */
+
+ ulong ch_cpstime; /* Time for CPS calculations */
+
+ tcflag_t ch_c_iflag; /* channel iflags */
+ tcflag_t ch_c_cflag; /* channel cflags */
+ tcflag_t ch_c_oflag; /* channel oflags */
+ tcflag_t ch_c_lflag; /* channel lflags */
+ uchar ch_stopc; /* Stop character */
+ uchar ch_startc; /* Start character */
+
+ uint ch_old_baud; /* Cache of the current baud */
+ uint ch_custom_speed;/* Custom baud, if set */
+
+ uint ch_wopen; /* Waiting for open process cnt */
+
+ uchar ch_mostat; /* FEP output modem status */
+ uchar ch_mistat; /* FEP input modem status */
+
+ struct neo_uart_struct __iomem *ch_neo_uart; /* Pointer to the "mapped" UART struct */
+ struct cls_uart_struct __iomem *ch_cls_uart; /* Pointer to the "mapped" UART struct */
+
+ uchar ch_cached_lsr; /* Cached value of the LSR register */
+
+ uchar *ch_rqueue; /* Our read queue buffer - malloc'ed */
+ ushort ch_r_head; /* Head location of the read queue */
+ ushort ch_r_tail; /* Tail location of the read queue */
+
+ uchar *ch_equeue; /* Our error queue buffer - malloc'ed */
+ ushort ch_e_head; /* Head location of the error queue */
+ ushort ch_e_tail; /* Tail location of the error queue */
+
+ uchar *ch_wqueue; /* Our write queue buffer - malloc'ed */
+ ushort ch_w_head; /* Head location of the write queue */
+ ushort ch_w_tail; /* Tail location of the write queue */
+
+ ulong ch_rxcount; /* total of data received so far */
+ ulong ch_txcount; /* total of data transmitted so far */
+
+ uchar ch_r_tlevel; /* Receive Trigger level */
+ uchar ch_t_tlevel; /* Transmit Trigger level */
+
+ uchar ch_r_watermark; /* Receive Watermark */
+
+ ulong ch_stop_sending_break; /* Time we should STOP sending a break */
+
+ uint ch_stops_sent; /* How many times I have sent a stop character
+ * to try to stop the other guy sending.
+ */
+ ulong ch_err_parity; /* Count of parity errors on channel */
+ ulong ch_err_frame; /* Count of framing errors on channel */
+ ulong ch_err_break; /* Count of breaks on channel */
+ ulong ch_err_overrun; /* Count of overruns on channel */
+
+ ulong ch_xon_sends; /* Count of xons transmitted */
+ ulong ch_xoff_sends; /* Count of xoffs transmitted */
+
+ ulong ch_intr_modem; /* Count of interrupts */
+ ulong ch_intr_tx; /* Count of interrupts */
+ ulong ch_intr_rx; /* Count of interrupts */
+
+
+ /* /proc/<board>/<channel> entries */
+ struct proc_dir_entry *proc_entry_pointer;
+ struct dgnc_proc_entry *dgnc_channel_table;
+
+ uint ch_sniff_in;
+ uint ch_sniff_out;
+ char *ch_sniff_buf; /* Sniff buffer for proc */
+ ulong ch_sniff_flags; /* Channel flags */
+ wait_queue_head_t ch_sniff_wait;
+};
+
+
+/*************************************************************************
+ *
+ * Prototypes for non-static functions used in more than one module
+ *
+ *************************************************************************/
+
+extern int dgnc_ms_sleep(ulong ms);
+extern char *dgnc_ioctl_name(int cmd);
+
+/*
+ * Our Global Variables.
+ */
+extern int dgnc_driver_state; /* The state of the driver */
+extern uint dgnc_Major; /* Our driver/mgmt major */
+extern int dgnc_debug; /* Debug variable */
+extern int dgnc_rawreadok; /* Set if user wants rawreads */
+extern int dgnc_poll_tick; /* Poll interval - 20 ms */
+extern int dgnc_trcbuf_size; /* Size of the ringbuffer */
+extern spinlock_t dgnc_global_lock; /* Driver global spinlock */
+extern uint dgnc_NumBoards; /* Total number of boards */
+extern struct dgnc_board *dgnc_Board[MAXBOARDS]; /* Array of board structs */
+extern ulong dgnc_poll_counter; /* Times the poller has run */
+extern char *dgnc_state_text[]; /* Array of state text */
+extern char *dgnc_driver_state_text[];/* Array of driver state text */
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_kcompat.h b/drivers/staging/dgnc/dgnc_kcompat.h
new file mode 100644
index 00000000000..eaec7e6a28e
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_kcompat.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2004 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *************************************************************************
+ *
+ * This file is intended to contain all the kernel "differences" between the
+ * various kernels that we support.
+ *
+ *************************************************************************/
+
+#ifndef __DGNC_KCOMPAT_H
+#define __DGNC_KCOMPAT_H
+
+#if !defined(TTY_FLIPBUF_SIZE)
+# define TTY_FLIPBUF_SIZE 512
+#endif
+
+
+/* Sparse stuff */
+# ifndef __user
+# define __user
+# define __kernel
+# define __safe
+# define __force
+# define __chk_user_ptr(x) (void)0
+# endif
+
+
+# define PARM_STR(VAR, INIT, PERM, DESC) \
+ static char *VAR = INIT; \
+ char *dgnc_##VAR; \
+ module_param(VAR, charp, PERM); \
+ MODULE_PARM_DESC(VAR, DESC);
+
+# define PARM_INT(VAR, INIT, PERM, DESC) \
+ static int VAR = INIT; \
+ int dgnc_##VAR; \
+ module_param(VAR, int, PERM); \
+ MODULE_PARM_DESC(VAR, DESC);
+
+# define PARM_ULONG(VAR, INIT, PERM, DESC) \
+ static ulong VAR = INIT; \
+ ulong dgnc_##VAR; \
+ module_param(VAR, long, PERM); \
+ MODULE_PARM_DESC(VAR, DESC);
+
+#endif /* ! __DGNC_KCOMPAT_H */
diff --git a/drivers/staging/dgnc/dgnc_mgmt.c b/drivers/staging/dgnc/dgnc_mgmt.c
new file mode 100644
index 00000000000..c5b425bf669
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_mgmt.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+/************************************************************************
+ *
+ * This file implements the mgmt functionality for the
+ * Neo and ClassicBoard based product lines.
+ *
+ ************************************************************************
+ */
+#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/sched.h> /* For jiffies, task states */
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/serial_reg.h>
+#include <linux/termios.h>
+#include <linux/uaccess.h> /* For copy_from_user/copy_to_user */
+
+#include "dgnc_driver.h"
+#include "dgnc_pci.h"
+#include "dgnc_kcompat.h" /* Kernel 2.4/2.6 compat includes */
+#include "dgnc_mgmt.h"
+#include "dpacompat.h"
+
+
+/* Our "in use" variables, to enforce 1 open only */
+static int dgnc_mgmt_in_use[MAXMGMTDEVICES];
+
+
+/*
+ * dgnc_mgmt_open()
+ *
+ * Open the mgmt/downld/dpa device
+ */
+int dgnc_mgmt_open(struct inode *inode, struct file *file)
+{
+ unsigned long lock_flags;
+ unsigned int minor = iminor(inode);
+
+ DPR_MGMT(("dgnc_mgmt_open start.\n"));
+
+ DGNC_LOCK(dgnc_global_lock, lock_flags);
+
+ /* mgmt device */
+ if (minor < MAXMGMTDEVICES) {
+ /* Only allow 1 open at a time on mgmt device */
+ if (dgnc_mgmt_in_use[minor]) {
+ DGNC_UNLOCK(dgnc_global_lock, lock_flags);
+ return -EBUSY;
+ }
+ dgnc_mgmt_in_use[minor]++;
+ } else {
+ DGNC_UNLOCK(dgnc_global_lock, lock_flags);
+ return -ENXIO;
+ }
+
+ DGNC_UNLOCK(dgnc_global_lock, lock_flags);
+
+ DPR_MGMT(("dgnc_mgmt_open finish.\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_mgmt_close()
+ *
+ * Open the mgmt/dpa device
+ */
+int dgnc_mgmt_close(struct inode *inode, struct file *file)
+{
+ unsigned long lock_flags;
+ unsigned int minor = iminor(inode);
+
+ DPR_MGMT(("dgnc_mgmt_close start.\n"));
+
+ DGNC_LOCK(dgnc_global_lock, lock_flags);
+
+ /* mgmt device */
+ if (minor < MAXMGMTDEVICES) {
+ if (dgnc_mgmt_in_use[minor])
+ dgnc_mgmt_in_use[minor] = 0;
+ }
+ DGNC_UNLOCK(dgnc_global_lock, lock_flags);
+
+ DPR_MGMT(("dgnc_mgmt_close finish.\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_mgmt_ioctl()
+ *
+ * ioctl the mgmt/dpa device
+ */
+
+long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ unsigned long lock_flags;
+ void __user *uarg = (void __user *) arg;
+
+ DPR_MGMT(("dgnc_mgmt_ioctl start.\n"));
+
+ switch (cmd) {
+
+ case DIGI_GETDD:
+ {
+ /*
+ * This returns the total number of boards
+ * in the system, as well as driver version
+ * and has space for a reserved entry
+ */
+ struct digi_dinfo ddi;
+
+ DGNC_LOCK(dgnc_global_lock, lock_flags);
+
+ ddi.dinfo_nboards = dgnc_NumBoards;
+ sprintf(ddi.dinfo_version, "%s", DG_PART);
+
+ DGNC_UNLOCK(dgnc_global_lock, lock_flags);
+
+ DPR_MGMT(("DIGI_GETDD returning numboards: %d version: %s\n",
+ ddi.dinfo_nboards, ddi.dinfo_version));
+
+ if (copy_to_user(uarg, &ddi, sizeof(ddi)))
+ return -EFAULT;
+
+ break;
+ }
+
+ case DIGI_GETBD:
+ {
+ int brd;
+
+ struct digi_info di;
+
+ if (copy_from_user(&brd, uarg, sizeof(int)))
+ return -EFAULT;
+
+ DPR_MGMT(("DIGI_GETBD asking about board: %d\n", brd));
+
+ if ((brd < 0) || (brd > dgnc_NumBoards) ||
+ (dgnc_NumBoards == 0))
+ return -ENODEV;
+
+ memset(&di, 0, sizeof(di));
+
+ di.info_bdnum = brd;
+
+ DGNC_LOCK(dgnc_Board[brd]->bd_lock, lock_flags);
+
+ di.info_bdtype = dgnc_Board[brd]->dpatype;
+ di.info_bdstate = dgnc_Board[brd]->dpastatus;
+ di.info_ioport = 0;
+ di.info_physaddr = (ulong) dgnc_Board[brd]->membase;
+ di.info_physsize = (ulong) dgnc_Board[brd]->membase - dgnc_Board[brd]->membase_end;
+ if (dgnc_Board[brd]->state != BOARD_FAILED)
+ di.info_nports = dgnc_Board[brd]->nasync;
+ else
+ di.info_nports = 0;
+
+ DGNC_UNLOCK(dgnc_Board[brd]->bd_lock, lock_flags);
+
+ DPR_MGMT(("DIGI_GETBD returning type: %x state: %x ports: %x size: %x\n",
+ di.info_bdtype, di.info_bdstate, di.info_nports, di.info_physsize));
+
+ if (copy_to_user(uarg, &di, sizeof(di)))
+ return -EFAULT;
+
+ break;
+ }
+
+ case DIGI_GET_NI_INFO:
+ {
+ struct channel_t *ch;
+ struct ni_info ni;
+ uchar mstat = 0;
+ uint board = 0;
+ uint channel = 0;
+
+ if (copy_from_user(&ni, uarg, sizeof(ni)))
+ return -EFAULT;
+
+ DPR_MGMT(("DIGI_GETBD asking about board: %d channel: %d\n",
+ ni.board, ni.channel));
+
+ board = ni.board;
+ channel = ni.channel;
+
+ /* Verify boundaries on board */
+ if ((board > dgnc_NumBoards) || (dgnc_NumBoards == 0))
+ return -ENODEV;
+
+ /* Verify boundaries on channel */
+ if ((channel < 0) || (channel > dgnc_Board[board]->nasync))
+ return -ENODEV;
+
+ ch = dgnc_Board[board]->channels[channel];
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENODEV;
+
+ memset(&ni, 0, sizeof(ni));
+ ni.board = board;
+ ni.channel = channel;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ mstat = (ch->ch_mostat | ch->ch_mistat);
+
+ if (mstat & UART_MCR_DTR) {
+ ni.mstat |= TIOCM_DTR;
+ ni.dtr = TIOCM_DTR;
+ }
+ if (mstat & UART_MCR_RTS) {
+ ni.mstat |= TIOCM_RTS;
+ ni.rts = TIOCM_RTS;
+ }
+ if (mstat & UART_MSR_CTS) {
+ ni.mstat |= TIOCM_CTS;
+ ni.cts = TIOCM_CTS;
+ }
+ if (mstat & UART_MSR_RI) {
+ ni.mstat |= TIOCM_RI;
+ ni.ri = TIOCM_RI;
+ }
+ if (mstat & UART_MSR_DCD) {
+ ni.mstat |= TIOCM_CD;
+ ni.dcd = TIOCM_CD;
+ }
+ if (mstat & UART_MSR_DSR)
+ ni.mstat |= TIOCM_DSR;
+
+ ni.iflag = ch->ch_c_iflag;
+ ni.oflag = ch->ch_c_oflag;
+ ni.cflag = ch->ch_c_cflag;
+ ni.lflag = ch->ch_c_lflag;
+
+ if (ch->ch_digi.digi_flags & CTSPACE ||
+ ch->ch_c_cflag & CRTSCTS)
+ ni.hflow = 1;
+ else
+ ni.hflow = 0;
+
+ if ((ch->ch_flags & CH_STOPI) ||
+ (ch->ch_flags & CH_FORCED_STOPI))
+ ni.recv_stopped = 1;
+ else
+ ni.recv_stopped = 0;
+
+ if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP))
+ ni.xmit_stopped = 1;
+ else
+ ni.xmit_stopped = 0;
+
+ ni.curtx = ch->ch_txcount;
+ ni.currx = ch->ch_rxcount;
+
+ ni.baud = ch->ch_old_baud;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (copy_to_user(uarg, &ni, sizeof(ni)))
+ return -EFAULT;
+
+ break;
+ }
+
+
+ }
+
+ DPR_MGMT(("dgnc_mgmt_ioctl finish.\n"));
+
+ return 0;
+}
diff --git a/drivers/staging/dgnc/dgnc_mgmt.h b/drivers/staging/dgnc/dgnc_mgmt.h
new file mode 100644
index 00000000000..567f687b18d
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_mgmt.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_MGMT_H
+#define __DGNC_MGMT_H
+
+#define MAXMGMTDEVICES 8
+
+int dgnc_mgmt_open(struct inode *inode, struct file *file);
+int dgnc_mgmt_close(struct inode *inode, struct file *file);
+long dgnc_mgmt_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+#endif
+
diff --git a/drivers/staging/dgnc/dgnc_neo.c b/drivers/staging/dgnc/dgnc_neo.c
new file mode 100644
index 00000000000..9de988cc892
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_neo.c
@@ -0,0 +1,1942 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/sched.h> /* For jiffies, task states */
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/delay.h> /* For udelay */
+#include <asm/io.h> /* For read[bwl]/write[bwl] */
+#include <linux/serial.h> /* For struct async_serial */
+#include <linux/serial_reg.h> /* For the various UART offsets */
+
+#include "dgnc_driver.h" /* Driver main header file */
+#include "dgnc_neo.h" /* Our header file */
+#include "dgnc_tty.h"
+#include "dgnc_trace.h"
+
+static inline void neo_parse_lsr(struct dgnc_board *brd, uint port);
+static inline void neo_parse_isr(struct dgnc_board *brd, uint port);
+static void neo_copy_data_from_uart_to_queue(struct channel_t *ch);
+static inline void neo_clear_break(struct channel_t *ch, int force);
+static inline void neo_set_cts_flow_control(struct channel_t *ch);
+static inline void neo_set_rts_flow_control(struct channel_t *ch);
+static inline void neo_set_ixon_flow_control(struct channel_t *ch);
+static inline void neo_set_ixoff_flow_control(struct channel_t *ch);
+static inline void neo_set_no_output_flow_control(struct channel_t *ch);
+static inline void neo_set_no_input_flow_control(struct channel_t *ch);
+static inline void neo_set_new_start_stop_chars(struct channel_t *ch);
+static void neo_parse_modem(struct channel_t *ch, uchar signals);
+static void neo_tasklet(unsigned long data);
+static void neo_vpd(struct dgnc_board *brd);
+static void neo_uart_init(struct channel_t *ch);
+static void neo_uart_off(struct channel_t *ch);
+static int neo_drain(struct tty_struct *tty, uint seconds);
+static void neo_param(struct tty_struct *tty);
+static void neo_assert_modem_signals(struct channel_t *ch);
+static void neo_flush_uart_write(struct channel_t *ch);
+static void neo_flush_uart_read(struct channel_t *ch);
+static void neo_disable_receiver(struct channel_t *ch);
+static void neo_enable_receiver(struct channel_t *ch);
+static void neo_send_break(struct channel_t *ch, int msecs);
+static void neo_send_start_character(struct channel_t *ch);
+static void neo_send_stop_character(struct channel_t *ch);
+static void neo_copy_data_from_queue_to_uart(struct channel_t *ch);
+static uint neo_get_uart_bytes_left(struct channel_t *ch);
+static void neo_send_immediate_char(struct channel_t *ch, unsigned char c);
+static irqreturn_t neo_intr(int irq, void *voidbrd);
+
+
+struct board_ops dgnc_neo_ops = {
+ .tasklet = neo_tasklet,
+ .intr = neo_intr,
+ .uart_init = neo_uart_init,
+ .uart_off = neo_uart_off,
+ .drain = neo_drain,
+ .param = neo_param,
+ .vpd = neo_vpd,
+ .assert_modem_signals = neo_assert_modem_signals,
+ .flush_uart_write = neo_flush_uart_write,
+ .flush_uart_read = neo_flush_uart_read,
+ .disable_receiver = neo_disable_receiver,
+ .enable_receiver = neo_enable_receiver,
+ .send_break = neo_send_break,
+ .send_start_character = neo_send_start_character,
+ .send_stop_character = neo_send_stop_character,
+ .copy_data_from_queue_to_uart = neo_copy_data_from_queue_to_uart,
+ .get_uart_bytes_left = neo_get_uart_bytes_left,
+ .send_immediate_char = neo_send_immediate_char
+};
+
+static uint dgnc_offset_table[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+
+
+/*
+ * This function allows calls to ensure that all outstanding
+ * PCI writes have been completed, by doing a PCI read against
+ * a non-destructive, read-only location on the Neo card.
+ *
+ * In this case, we are reading the DVID (Read-only Device Identification)
+ * value of the Neo card.
+ */
+static inline void neo_pci_posting_flush(struct dgnc_board *bd)
+{
+ readb(bd->re_map_membase + 0x8D);
+}
+
+static inline void neo_set_cts_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+
+ DPR_PARAM(("Setting CTSFLOW\n"));
+
+ /* Turn on auto CTS flow control */
+#if 1
+ ier |= (UART_17158_IER_CTSDSR);
+#else
+ ier &= ~(UART_17158_IER_CTSDSR);
+#endif
+
+ efr |= (UART_17158_EFR_ECB | UART_17158_EFR_CTSDSR);
+
+ /* Turn off auto Xon flow control */
+ efr &= ~(UART_17158_EFR_IXON);
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ /* Turn on table D, with 8 char hi/low watermarks */
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr);
+
+ /* Feed the UART our trigger levels */
+ writeb(8, &ch->ch_neo_uart->tfifo);
+ ch->ch_t_tlevel = 8;
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_rts_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+ DPR_PARAM(("Setting RTSFLOW\n"));
+
+ /* Turn on auto RTS flow control */
+#if 1
+ ier |= (UART_17158_IER_RTSDTR);
+#else
+ ier &= ~(UART_17158_IER_RTSDTR);
+#endif
+ efr |= (UART_17158_EFR_ECB | UART_17158_EFR_RTSDTR);
+
+ /* Turn off auto Xoff flow control */
+ ier &= ~(UART_17158_IER_XOFF);
+ efr &= ~(UART_17158_EFR_IXOFF);
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_4DELAY), &ch->ch_neo_uart->fctr);
+ ch->ch_r_watermark = 4;
+
+ writeb(32, &ch->ch_neo_uart->rfifo);
+ ch->ch_r_tlevel = 32;
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ /*
+ * From the Neo UART spec sheet:
+ * The auto RTS/DTR function must be started by asserting
+ * RTS/DTR# output pin (MCR bit-0 or 1 to logic 1 after
+ * it is enabled.
+ */
+ ch->ch_mostat |= (UART_MCR_RTS);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_ixon_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+ DPR_PARAM(("Setting IXON FLOW\n"));
+
+ /* Turn off auto CTS flow control */
+ ier &= ~(UART_17158_IER_CTSDSR);
+ efr &= ~(UART_17158_EFR_CTSDSR);
+
+ /* Turn on auto Xon flow control */
+ efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXON);
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+ ch->ch_r_watermark = 4;
+
+ writeb(32, &ch->ch_neo_uart->rfifo);
+ ch->ch_r_tlevel = 32;
+
+ /* Tell UART what start/stop chars it should be looking for */
+ writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+ writeb(0, &ch->ch_neo_uart->xonchar2);
+
+ writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+ writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_ixoff_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+ DPR_PARAM(("Setting IXOFF FLOW\n"));
+
+ /* Turn off auto RTS flow control */
+ ier &= ~(UART_17158_IER_RTSDTR);
+ efr &= ~(UART_17158_EFR_RTSDTR);
+
+ /* Turn on auto Xoff flow control */
+ ier |= (UART_17158_IER_XOFF);
+ efr |= (UART_17158_EFR_ECB | UART_17158_EFR_IXOFF);
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ /* Turn on table D, with 8 char hi/low watermarks */
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+ writeb(8, &ch->ch_neo_uart->tfifo);
+ ch->ch_t_tlevel = 8;
+
+ /* Tell UART what start/stop chars it should be looking for */
+ writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+ writeb(0, &ch->ch_neo_uart->xonchar2);
+
+ writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+ writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_no_input_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+ DPR_PARAM(("Unsetting Input FLOW\n"));
+
+ /* Turn off auto RTS flow control */
+ ier &= ~(UART_17158_IER_RTSDTR);
+ efr &= ~(UART_17158_EFR_RTSDTR);
+
+ /* Turn off auto Xoff flow control */
+ ier &= ~(UART_17158_IER_XOFF);
+ if (ch->ch_c_iflag & IXON)
+ efr &= ~(UART_17158_EFR_IXOFF);
+ else
+ efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXOFF);
+
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ /* Turn on table D, with 8 char hi/low watermarks */
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+ ch->ch_r_watermark = 0;
+
+ writeb(16, &ch->ch_neo_uart->tfifo);
+ ch->ch_t_tlevel = 16;
+
+ writeb(16, &ch->ch_neo_uart->rfifo);
+ ch->ch_r_tlevel = 16;
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static inline void neo_set_no_output_flow_control(struct channel_t *ch)
+{
+ uchar ier = readb(&ch->ch_neo_uart->ier);
+ uchar efr = readb(&ch->ch_neo_uart->efr);
+
+ DPR_PARAM(("Unsetting Output FLOW\n"));
+
+ /* Turn off auto CTS flow control */
+ ier &= ~(UART_17158_IER_CTSDSR);
+ efr &= ~(UART_17158_EFR_CTSDSR);
+
+ /* Turn off auto Xon flow control */
+ if (ch->ch_c_iflag & IXOFF)
+ efr &= ~(UART_17158_EFR_IXON);
+ else
+ efr &= ~(UART_17158_EFR_ECB | UART_17158_EFR_IXON);
+
+ /* Why? Becuz Exar's spec says we have to zero it out before setting it */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Turn on UART enhanced bits */
+ writeb(efr, &ch->ch_neo_uart->efr);
+
+ /* Turn on table D, with 8 char hi/low watermarks */
+ writeb((UART_17158_FCTR_TRGD | UART_17158_FCTR_RTS_8DELAY), &ch->ch_neo_uart->fctr);
+
+ ch->ch_r_watermark = 0;
+
+ writeb(16, &ch->ch_neo_uart->tfifo);
+ ch->ch_t_tlevel = 16;
+
+ writeb(16, &ch->ch_neo_uart->rfifo);
+ ch->ch_r_tlevel = 16;
+
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/* change UARTs start/stop chars */
+static inline void neo_set_new_start_stop_chars(struct channel_t *ch)
+{
+
+ /* if hardware flow control is set, then skip this whole thing */
+ if (ch->ch_digi.digi_flags & (CTSPACE | RTSPACE) || ch->ch_c_cflag & CRTSCTS)
+ return;
+
+ DPR_PARAM(("In new start stop chars\n"));
+
+ /* Tell UART what start/stop chars it should be looking for */
+ writeb(ch->ch_startc, &ch->ch_neo_uart->xonchar1);
+ writeb(0, &ch->ch_neo_uart->xonchar2);
+
+ writeb(ch->ch_stopc, &ch->ch_neo_uart->xoffchar1);
+ writeb(0, &ch->ch_neo_uart->xoffchar2);
+
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * No locks are assumed to be held when calling this function.
+ */
+static inline void neo_clear_break(struct channel_t *ch, int force)
+{
+ ulong lock_flags;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Bail if we aren't currently sending a break. */
+ if (!ch->ch_stop_sending_break) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* Turn break off, and unset some variables */
+ if (ch->ch_flags & CH_BREAK_SENDING) {
+ if (time_after_eq(jiffies, ch->ch_stop_sending_break)
+ || force) {
+ uchar temp = readb(&ch->ch_neo_uart->lcr);
+ writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+ neo_pci_posting_flush(ch->ch_bd);
+ ch->ch_flags &= ~(CH_BREAK_SENDING);
+ ch->ch_stop_sending_break = 0;
+ DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+ }
+ }
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * Parse the ISR register.
+ */
+static inline void neo_parse_isr(struct dgnc_board *brd, uint port)
+{
+ struct channel_t *ch;
+ uchar isr;
+ uchar cause;
+ ulong lock_flags;
+
+ if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ if (port > brd->maxports)
+ return;
+
+ ch = brd->channels[port];
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ /* Here we try to figure out what caused the interrupt to happen */
+ while (1) {
+
+ isr = readb(&ch->ch_neo_uart->isr_fcr);
+
+ /* Bail if no pending interrupt */
+ if (isr & UART_IIR_NO_INT)
+ break;
+
+ /*
+ * Yank off the upper 2 bits, which just show that the FIFO's are enabled.
+ */
+ isr &= ~(UART_17158_IIR_FIFO_ENABLED);
+
+ DPR_INTR(("%s:%d isr: %x\n", __FILE__, __LINE__, isr));
+
+ if (isr & (UART_17158_IIR_RDI_TIMEOUT | UART_IIR_RDI)) {
+ /* Read data from uart -> queue */
+ brd->intr_rx++;
+ ch->ch_intr_rx++;
+ neo_copy_data_from_uart_to_queue(ch);
+
+ /* Call our tty layer to enforce queue flow control if needed. */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ dgnc_check_queue_flow_control(ch);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+
+ if (isr & UART_IIR_THRI) {
+ brd->intr_tx++;
+ ch->ch_intr_tx++;
+ /* Transfer data (if any) from Write Queue -> UART. */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ neo_copy_data_from_queue_to_uart(ch);
+ }
+
+ if (isr & UART_17158_IIR_XONXOFF) {
+ cause = readb(&ch->ch_neo_uart->xoffchar1);
+
+ DPR_INTR(("Port %d. Got ISR_XONXOFF: cause:%x\n", port, cause));
+
+ /*
+ * Since the UART detected either an XON or
+ * XOFF match, we need to figure out which
+ * one it was, so we can suspend or resume data flow.
+ */
+ if (cause == UART_17158_XON_DETECT) {
+ /* Is output stopped right now, if so, resume it */
+ if (brd->channels[port]->ch_flags & CH_STOP) {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags &= ~(CH_STOP);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+ DPR_INTR(("Port %d. XON detected in incoming data\n", port));
+ } else if (cause == UART_17158_XOFF_DETECT) {
+ if (!(brd->channels[port]->ch_flags & CH_STOP)) {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags |= CH_STOP;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ DPR_INTR(("Setting CH_STOP\n"));
+ }
+ DPR_INTR(("Port: %d. XOFF detected in incoming data\n", port));
+ }
+ }
+
+ if (isr & UART_17158_IIR_HWFLOW_STATE_CHANGE) {
+ /*
+ * If we get here, this means the hardware is doing auto flow control.
+ * Check to see whether RTS/DTR or CTS/DSR caused this interrupt.
+ */
+ brd->intr_modem++;
+ ch->ch_intr_modem++;
+ cause = readb(&ch->ch_neo_uart->mcr);
+ /* Which pin is doing auto flow? RTS or DTR? */
+ if ((cause & 0x4) == 0) {
+ if (cause & UART_MCR_RTS) {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_mostat |= UART_MCR_RTS;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ } else {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+ } else {
+ if (cause & UART_MCR_DTR) {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_mostat |= UART_MCR_DTR;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ } else {
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+ }
+ }
+
+ /* Parse any modem signal changes */
+ DPR_INTR(("MOD_STAT: sending to parse_modem_sigs\n"));
+ neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr));
+ }
+}
+
+
+static inline void neo_parse_lsr(struct dgnc_board *brd, uint port)
+{
+ struct channel_t *ch;
+ int linestatus;
+ ulong lock_flags;
+
+ if (!brd)
+ return;
+
+ if (brd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ if (port > brd->maxports)
+ return;
+
+ ch = brd->channels[port];
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ linestatus = readb(&ch->ch_neo_uart->lsr);
+
+ DPR_INTR(("%s:%d port: %d linestatus: %x\n", __FILE__, __LINE__, port, linestatus));
+
+ ch->ch_cached_lsr |= linestatus;
+
+ if (ch->ch_cached_lsr & UART_LSR_DR) {
+ brd->intr_rx++;
+ ch->ch_intr_rx++;
+ /* Read data from uart -> queue */
+ neo_copy_data_from_uart_to_queue(ch);
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ dgnc_check_queue_flow_control(ch);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+
+ /*
+ * This is a special flag. It indicates that at least 1
+ * RX error (parity, framing, or break) has happened.
+ * Mark this in our struct, which will tell me that I have
+ *to do the special RX+LSR read for this FIFO load.
+ */
+ if (linestatus & UART_17158_RX_FIFO_DATA_ERROR) {
+ DPR_INTR(("%s:%d Port: %d Got an RX error, need to parse LSR\n",
+ __FILE__, __LINE__, port));
+ }
+
+ /*
+ * The next 3 tests should *NOT* happen, as the above test
+ * should encapsulate all 3... At least, thats what Exar says.
+ */
+
+ if (linestatus & UART_LSR_PE) {
+ ch->ch_err_parity++;
+ DPR_INTR(("%s:%d Port: %d. PAR ERR!\n", __FILE__, __LINE__, port));
+ }
+
+ if (linestatus & UART_LSR_FE) {
+ ch->ch_err_frame++;
+ DPR_INTR(("%s:%d Port: %d. FRM ERR!\n", __FILE__, __LINE__, port));
+ }
+
+ if (linestatus & UART_LSR_BI) {
+ ch->ch_err_break++;
+ DPR_INTR(("%s:%d Port: %d. BRK INTR!\n", __FILE__, __LINE__, port));
+ }
+
+ if (linestatus & UART_LSR_OE) {
+ /*
+ * Rx Oruns. Exar says that an orun will NOT corrupt
+ * the FIFO. It will just replace the holding register
+ * with this new data byte. So basically just ignore this.
+ * Probably we should eventually have an orun stat in our driver...
+ */
+ ch->ch_err_overrun++;
+ DPR_INTR(("%s:%d Port: %d. Rx Overrun!\n", __FILE__, __LINE__, port));
+ }
+
+ if (linestatus & UART_LSR_THRE) {
+ brd->intr_tx++;
+ ch->ch_intr_tx++;
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /* Transfer data (if any) from Write Queue -> UART. */
+ neo_copy_data_from_queue_to_uart(ch);
+ } else if (linestatus & UART_17158_TX_AND_FIFO_CLR) {
+ brd->intr_tx++;
+ ch->ch_intr_tx++;
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /* Transfer data (if any) from Write Queue -> UART. */
+ neo_copy_data_from_queue_to_uart(ch);
+ }
+}
+
+
+/*
+ * neo_param()
+ * Send any/all changes to the line to the UART.
+ */
+static void neo_param(struct tty_struct *tty)
+{
+ uchar lcr = 0;
+ uchar uart_lcr = 0;
+ uchar ier = 0;
+ uchar uart_ier = 0;
+ uint baud = 9600;
+ int quot = 0;
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = (struct un_t *) tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_PARAM(("param start: tdev: %x cflags: %x oflags: %x iflags: %x\n",
+ ch->ch_tun.un_dev, ch->ch_c_cflag, ch->ch_c_oflag, ch->ch_c_iflag));
+
+ /*
+ * If baud rate is zero, flush queues, and set mval to drop DTR.
+ */
+ if ((ch->ch_c_cflag & (CBAUD)) == 0) {
+ ch->ch_r_head = ch->ch_r_tail = 0;
+ ch->ch_e_head = ch->ch_e_tail = 0;
+ ch->ch_w_head = ch->ch_w_tail = 0;
+
+ neo_flush_uart_write(ch);
+ neo_flush_uart_read(ch);
+
+ /* The baudrate is B0 so all modem lines are to be dropped. */
+ ch->ch_flags |= (CH_BAUD0);
+ ch->ch_mostat &= ~(UART_MCR_RTS | UART_MCR_DTR);
+ neo_assert_modem_signals(ch);
+ ch->ch_old_baud = 0;
+ return;
+
+ } else if (ch->ch_custom_speed) {
+
+ baud = ch->ch_custom_speed;
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+
+ /*
+ * Bring back up RTS and DTR...
+ * Also handle RTS or DTR toggle if set.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+ }
+ } else {
+ int iindex = 0;
+ int jindex = 0;
+
+ ulong bauds[4][16] = {
+ { /* slowbaud */
+ 0, 50, 75, 110,
+ 134, 150, 200, 300,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* slowbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud */
+ 0, 57600, 76800, 115200,
+ 131657, 153600, 230400, 460800,
+ 921600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 }
+ };
+
+ /* Only use the TXPrint baud rate if the terminal unit is NOT open */
+ if (!(ch->ch_tun.un_flags & UN_ISOPEN) && (un->un_type == DGNC_PRINT))
+ baud = C_BAUD(ch->ch_pun.un_tty) & 0xff;
+ else
+ baud = C_BAUD(ch->ch_tun.un_tty) & 0xff;
+
+ if (ch->ch_c_cflag & CBAUDEX)
+ iindex = 1;
+
+ if (ch->ch_digi.digi_flags & DIGI_FAST)
+ iindex += 2;
+
+ jindex = baud;
+
+ if ((iindex >= 0) && (iindex < 4) && (jindex >= 0) && (jindex < 16)) {
+ baud = bauds[iindex][jindex];
+ } else {
+ DPR_IOCTL(("baud indices were out of range (%d)(%d)",
+ iindex, jindex));
+ baud = 0;
+ }
+
+ if (baud == 0)
+ baud = 9600;
+
+ /* Handle transition from B0 */
+ if (ch->ch_flags & CH_BAUD0) {
+ ch->ch_flags &= ~(CH_BAUD0);
+
+ /*
+ * Bring back up RTS and DTR...
+ * Also handle RTS or DTR toggle if set.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+ }
+ }
+
+ if (ch->ch_c_cflag & PARENB)
+ lcr |= UART_LCR_PARITY;
+
+ if (!(ch->ch_c_cflag & PARODD))
+ lcr |= UART_LCR_EPAR;
+
+ /*
+ * Not all platforms support mark/space parity,
+ * so this will hide behind an ifdef.
+ */
+#ifdef CMSPAR
+ if (ch->ch_c_cflag & CMSPAR)
+ lcr |= UART_LCR_SPAR;
+#endif
+
+ if (ch->ch_c_cflag & CSTOPB)
+ lcr |= UART_LCR_STOP;
+
+ switch (ch->ch_c_cflag & CSIZE) {
+ case CS5:
+ lcr |= UART_LCR_WLEN5;
+ break;
+ case CS6:
+ lcr |= UART_LCR_WLEN6;
+ break;
+ case CS7:
+ lcr |= UART_LCR_WLEN7;
+ break;
+ case CS8:
+ default:
+ lcr |= UART_LCR_WLEN8;
+ break;
+ }
+
+ ier = uart_ier = readb(&ch->ch_neo_uart->ier);
+ uart_lcr = readb(&ch->ch_neo_uart->lcr);
+
+ if (baud == 0)
+ baud = 9600;
+
+ quot = ch->ch_bd->bd_dividend / baud;
+
+ if (quot != 0 && ch->ch_old_baud != baud) {
+ ch->ch_old_baud = baud;
+ writeb(UART_LCR_DLAB, &ch->ch_neo_uart->lcr);
+ writeb((quot & 0xff), &ch->ch_neo_uart->txrx);
+ writeb((quot >> 8), &ch->ch_neo_uart->ier);
+ writeb(lcr, &ch->ch_neo_uart->lcr);
+ }
+
+ if (uart_lcr != lcr)
+ writeb(lcr, &ch->ch_neo_uart->lcr);
+
+ if (ch->ch_c_cflag & CREAD)
+ ier |= (UART_IER_RDI | UART_IER_RLSI);
+ else
+ ier &= ~(UART_IER_RDI | UART_IER_RLSI);
+
+ /*
+ * Have the UART interrupt on modem signal changes ONLY when
+ * we are in hardware flow control mode, or CLOCAL/FORCEDCD is not set.
+ */
+ if ((ch->ch_digi.digi_flags & CTSPACE) ||
+ (ch->ch_digi.digi_flags & RTSPACE) ||
+ (ch->ch_c_cflag & CRTSCTS) ||
+ !(ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+ !(ch->ch_c_cflag & CLOCAL))
+ ier |= UART_IER_MSI;
+ else
+ ier &= ~UART_IER_MSI;
+
+ ier |= UART_IER_THRI;
+
+ if (ier != uart_ier)
+ writeb(ier, &ch->ch_neo_uart->ier);
+
+ /* Set new start/stop chars */
+ neo_set_new_start_stop_chars(ch);
+
+ if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ neo_set_cts_flow_control(ch);
+ } else if (ch->ch_c_iflag & IXON) {
+ /* If start/stop is set to disable, then we should disable flow control */
+ if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+ neo_set_no_output_flow_control(ch);
+ else
+ neo_set_ixon_flow_control(ch);
+ } else {
+ neo_set_no_output_flow_control(ch);
+ }
+
+ if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ neo_set_rts_flow_control(ch);
+ } else if (ch->ch_c_iflag & IXOFF) {
+ /* If start/stop is set to disable, then we should disable flow control */
+ if ((ch->ch_startc == _POSIX_VDISABLE) || (ch->ch_stopc == _POSIX_VDISABLE))
+ neo_set_no_input_flow_control(ch);
+ else
+ neo_set_ixoff_flow_control(ch);
+ } else {
+ neo_set_no_input_flow_control(ch);
+ }
+
+ /*
+ * Adjust the RX FIFO Trigger level if baud is less than 9600.
+ * Not exactly elegant, but this is needed because of the Exar chip's
+ * delay on firing off the RX FIFO interrupt on slower baud rates.
+ */
+ if (baud < 9600) {
+ writeb(1, &ch->ch_neo_uart->rfifo);
+ ch->ch_r_tlevel = 1;
+ }
+
+ neo_assert_modem_signals(ch);
+
+ /* Get current status of the modem signals now */
+ neo_parse_modem(ch, readb(&ch->ch_neo_uart->msr));
+}
+
+
+/*
+ * Our board poller function.
+ */
+static void neo_tasklet(unsigned long data)
+{
+ struct dgnc_board *bd = (struct dgnc_board *) data;
+ struct channel_t *ch;
+ ulong lock_flags;
+ int i;
+ int state = 0;
+ int ports = 0;
+
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC) {
+ APR(("poll_tasklet() - NULL or bad bd.\n"));
+ return;
+ }
+
+ /* Cache a couple board values */
+ DGNC_LOCK(bd->bd_lock, lock_flags);
+ state = bd->state;
+ ports = bd->nasync;
+ DGNC_UNLOCK(bd->bd_lock, lock_flags);
+
+ /*
+ * Do NOT allow the interrupt routine to read the intr registers
+ * Until we release this lock.
+ */
+ DGNC_LOCK(bd->bd_intr_lock, lock_flags);
+
+ /*
+ * If board is ready, parse deeper to see if there is anything to do.
+ */
+ if ((state == BOARD_READY) && (ports > 0)) {
+ /* Loop on each port */
+ for (i = 0; i < ports; i++) {
+ ch = bd->channels[i];
+
+ /* Just being careful... */
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ continue;
+
+ /*
+ * NOTE: Remember you CANNOT hold any channel
+ * locks when calling the input routine.
+ *
+ * During input processing, its possible we
+ * will call the Linux ld, which might in turn,
+ * do a callback right back into us, resulting
+ * in us trying to grab the channel lock twice!
+ */
+ dgnc_input(ch);
+
+ /*
+ * Channel lock is grabbed and then released
+ * inside both of these routines, but neither
+ * call anything else that could call back into us.
+ */
+ neo_copy_data_from_queue_to_uart(ch);
+ dgnc_wakeup_writes(ch);
+
+ /*
+ * Call carrier carrier function, in case something
+ * has changed.
+ */
+ dgnc_carrier(ch);
+
+ /*
+ * Check to see if we need to turn off a sending break.
+ * The timing check is done inside clear_break()
+ */
+ if (ch->ch_stop_sending_break)
+ neo_clear_break(ch, 0);
+ }
+ }
+
+ /* Allow interrupt routine to access the interrupt register again */
+ DGNC_UNLOCK(bd->bd_intr_lock, lock_flags);
+
+}
+
+
+/*
+ * dgnc_neo_intr()
+ *
+ * Neo specific interrupt handler.
+ */
+static irqreturn_t neo_intr(int irq, void *voidbrd)
+{
+ struct dgnc_board *brd = (struct dgnc_board *) voidbrd;
+ struct channel_t *ch;
+ int port = 0;
+ int type = 0;
+ int current_port;
+ u32 tmp;
+ u32 uart_poll;
+ unsigned long lock_flags;
+ unsigned long lock_flags2;
+
+ if (!brd) {
+ APR(("Received interrupt (%d) with null board associated\n", irq));
+ return IRQ_NONE;
+ }
+
+ /*
+ * Check to make sure its for us.
+ */
+ if (brd->magic != DGNC_BOARD_MAGIC) {
+ APR(("Received interrupt (%d) with a board pointer that wasn't ours!\n", irq));
+ return IRQ_NONE;
+ }
+
+ brd->intr_count++;
+
+ /* Lock out the slow poller from running on this board. */
+ DGNC_LOCK(brd->bd_intr_lock, lock_flags);
+
+ /*
+ * Read in "extended" IRQ information from the 32bit Neo register.
+ * Bits 0-7: What port triggered the interrupt.
+ * Bits 8-31: Each 3bits indicate what type of interrupt occurred.
+ */
+ uart_poll = readl(brd->re_map_membase + UART_17158_POLL_ADDR_OFFSET);
+
+ DPR_INTR(("%s:%d uart_poll: %x\n", __FILE__, __LINE__, uart_poll));
+
+ /*
+ * If 0, no interrupts pending.
+ * This can happen if the IRQ is shared among a couple Neo/Classic boards.
+ */
+ if (!uart_poll) {
+ DPR_INTR(("Kernel interrupted to me, but no pending interrupts...\n"));
+ DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+ return IRQ_NONE;
+ }
+
+ /* At this point, we have at least SOMETHING to service, dig further... */
+
+ current_port = 0;
+
+ /* Loop on each port */
+ while ((uart_poll & 0xff) != 0) {
+
+ tmp = uart_poll;
+
+ /* Check current port to see if it has interrupt pending */
+ if ((tmp & dgnc_offset_table[current_port]) != 0) {
+ port = current_port;
+ type = tmp >> (8 + (port * 3));
+ type &= 0x7;
+ } else {
+ current_port++;
+ continue;
+ }
+
+ DPR_INTR(("%s:%d port: %x type: %x\n", __FILE__, __LINE__, port, type));
+
+ /* Remove this port + type from uart_poll */
+ uart_poll &= ~(dgnc_offset_table[port]);
+
+ if (!type) {
+ /* If no type, just ignore it, and move onto next port */
+ DPR_INTR(("Interrupt with no type! port: %d\n", port));
+ continue;
+ }
+
+ /* Switch on type of interrupt we have */
+ switch (type) {
+
+ case UART_17158_RXRDY_TIMEOUT:
+ /*
+ * RXRDY Time-out is cleared by reading data in the
+ * RX FIFO until it falls below the trigger level.
+ */
+
+ /* Verify the port is in range. */
+ if (port > brd->nasync)
+ continue;
+
+ ch = brd->channels[port];
+ neo_copy_data_from_uart_to_queue(ch);
+
+ /* Call our tty layer to enforce queue flow control if needed. */
+ DGNC_LOCK(ch->ch_lock, lock_flags2);
+ dgnc_check_queue_flow_control(ch);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags2);
+
+ continue;
+
+ case UART_17158_RX_LINE_STATUS:
+ /*
+ * RXRDY and RX LINE Status (logic OR of LSR[4:1])
+ */
+ neo_parse_lsr(brd, port);
+ continue;
+
+ case UART_17158_TXRDY:
+ /*
+ * TXRDY interrupt clears after reading ISR register for the UART channel.
+ */
+
+ /*
+ * Yes, this is odd...
+ * Why would I check EVERY possibility of type of
+ * interrupt, when we know its TXRDY???
+ * Becuz for some reason, even tho we got triggered for TXRDY,
+ * it seems to be occasionally wrong. Instead of TX, which
+ * it should be, I was getting things like RXDY too. Weird.
+ */
+ neo_parse_isr(brd, port);
+ continue;
+
+ case UART_17158_MSR:
+ /*
+ * MSR or flow control was seen.
+ */
+ neo_parse_isr(brd, port);
+ continue;
+
+ default:
+ /*
+ * The UART triggered us with a bogus interrupt type.
+ * It appears the Exar chip, when REALLY bogged down, will throw
+ * these once and awhile.
+ * Its harmless, just ignore it and move on.
+ */
+ DPR_INTR(("%s:%d Unknown Interrupt type: %x\n", __FILE__, __LINE__, type));
+ continue;
+ }
+ }
+
+ /*
+ * Schedule tasklet to more in-depth servicing at a better time.
+ */
+ tasklet_schedule(&brd->helper_tasklet);
+
+ DGNC_UNLOCK(brd->bd_intr_lock, lock_flags);
+
+ DPR_INTR(("dgnc_intr finish.\n"));
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * Neo specific way of turning off the receiver.
+ * Used as a way to enforce queue flow control when in
+ * hardware flow control mode.
+ */
+static void neo_disable_receiver(struct channel_t *ch)
+{
+ uchar tmp = readb(&ch->ch_neo_uart->ier);
+ tmp &= ~(UART_IER_RDI);
+ writeb(tmp, &ch->ch_neo_uart->ier);
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * Neo specific way of turning on the receiver.
+ * Used as a way to un-enforce queue flow control when in
+ * hardware flow control mode.
+ */
+static void neo_enable_receiver(struct channel_t *ch)
+{
+ uchar tmp = readb(&ch->ch_neo_uart->ier);
+ tmp |= (UART_IER_RDI);
+ writeb(tmp, &ch->ch_neo_uart->ier);
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static void neo_copy_data_from_uart_to_queue(struct channel_t *ch)
+{
+ int qleft = 0;
+ uchar linestatus = 0;
+ uchar error_mask = 0;
+ int n = 0;
+ int total = 0;
+ ushort head;
+ ushort tail;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* cache head and tail of queue */
+ head = ch->ch_r_head & RQUEUEMASK;
+ tail = ch->ch_r_tail & RQUEUEMASK;
+
+ /* Get our cached LSR */
+ linestatus = ch->ch_cached_lsr;
+ ch->ch_cached_lsr = 0;
+
+ /* Store how much space we have left in the queue */
+ qleft = tail - head - 1;
+ if (qleft < 0)
+ qleft += RQUEUEMASK + 1;
+
+ /*
+ * If the UART is not in FIFO mode, force the FIFO copy to
+ * NOT be run, by setting total to 0.
+ *
+ * On the other hand, if the UART IS in FIFO mode, then ask
+ * the UART to give us an approximation of data it has RX'ed.
+ */
+ if (!(ch->ch_flags & CH_FIFO_ENABLED))
+ total = 0;
+ else {
+ total = readb(&ch->ch_neo_uart->rfifo);
+
+ /*
+ * EXAR chip bug - RX FIFO COUNT - Fudge factor.
+ *
+ * This resolves a problem/bug with the Exar chip that sometimes
+ * returns a bogus value in the rfifo register.
+ * The count can be any where from 0-3 bytes "off".
+ * Bizarre, but true.
+ */
+ if ((ch->ch_bd->dvid & 0xf0) >= UART_XR17E158_DVID)
+ total -= 1;
+ else
+ total -= 3;
+ }
+
+
+ /*
+ * Finally, bound the copy to make sure we don't overflow
+ * our own queue...
+ * The byte by byte copy loop below this loop this will
+ * deal with the queue overflow possibility.
+ */
+ total = min(total, qleft);
+
+ while (total > 0) {
+
+ /*
+ * Grab the linestatus register, we need to check
+ * to see if there are any errors in the FIFO.
+ */
+ linestatus = readb(&ch->ch_neo_uart->lsr);
+
+ /*
+ * Break out if there is a FIFO error somewhere.
+ * This will allow us to go byte by byte down below,
+ * finding the exact location of the error.
+ */
+ if (linestatus & UART_17158_RX_FIFO_DATA_ERROR)
+ break;
+
+ /* Make sure we don't go over the end of our queue */
+ n = min(((uint) total), (RQUEUESIZE - (uint) head));
+
+ /*
+ * Cut down n even further if needed, this is to fix
+ * a problem with memcpy_fromio() with the Neo on the
+ * IBM pSeries platform.
+ * 15 bytes max appears to be the magic number.
+ */
+ n = min((uint) n, (uint) 12);
+
+ /*
+ * Since we are grabbing the linestatus register, which
+ * will reset some bits after our read, we need to ensure
+ * we don't miss our TX FIFO emptys.
+ */
+ if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR))
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+
+ linestatus = 0;
+
+ /* Copy data from uart to the queue */
+ memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, n);
+ dgnc_sniff_nowait_nolock(ch, "UART READ", ch->ch_rqueue + head, n);
+
+ /*
+ * Since RX_FIFO_DATA_ERROR was 0, we are guarenteed
+ * that all the data currently in the FIFO is free of
+ * breaks and parity/frame/orun errors.
+ */
+ memset(ch->ch_equeue + head, 0, n);
+
+ /* Add to and flip head if needed */
+ head = (head + n) & RQUEUEMASK;
+ total -= n;
+ qleft -= n;
+ ch->ch_rxcount += n;
+ }
+
+ /*
+ * Create a mask to determine whether we should
+ * insert the character (if any) into our queue.
+ */
+ if (ch->ch_c_iflag & IGNBRK)
+ error_mask |= UART_LSR_BI;
+
+ /*
+ * Now cleanup any leftover bytes still in the UART.
+ * Also deal with any possible queue overflow here as well.
+ */
+ while (1) {
+
+ /*
+ * Its possible we have a linestatus from the loop above
+ * this, so we "OR" on any extra bits.
+ */
+ linestatus |= readb(&ch->ch_neo_uart->lsr);
+
+ /*
+ * If the chip tells us there is no more data pending to
+ * be read, we can then leave.
+ * But before we do, cache the linestatus, just in case.
+ */
+ if (!(linestatus & UART_LSR_DR)) {
+ ch->ch_cached_lsr = linestatus;
+ break;
+ }
+
+ /* No need to store this bit */
+ linestatus &= ~UART_LSR_DR;
+
+ /*
+ * Since we are grabbing the linestatus register, which
+ * will reset some bits after our read, we need to ensure
+ * we don't miss our TX FIFO emptys.
+ */
+ if (linestatus & (UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR)) {
+ linestatus &= ~(UART_LSR_THRE | UART_17158_TX_AND_FIFO_CLR);
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ }
+
+ /*
+ * Discard character if we are ignoring the error mask.
+ */
+ if (linestatus & error_mask) {
+ uchar discard;
+ linestatus = 0;
+ memcpy_fromio(&discard, &ch->ch_neo_uart->txrxburst, 1);
+ continue;
+ }
+
+ /*
+ * If our queue is full, we have no choice but to drop some data.
+ * The assumption is that HWFLOW or SWFLOW should have stopped
+ * things way way before we got to this point.
+ *
+ * I decided that I wanted to ditch the oldest data first,
+ * I hope thats okay with everyone? Yes? Good.
+ */
+ while (qleft < 1) {
+ DPR_READ(("Queue full, dropping DATA:%x LSR:%x\n",
+ ch->ch_rqueue[tail], ch->ch_equeue[tail]));
+
+ ch->ch_r_tail = tail = (tail + 1) & RQUEUEMASK;
+ ch->ch_err_overrun++;
+ qleft++;
+ }
+
+ memcpy_fromio(ch->ch_rqueue + head, &ch->ch_neo_uart->txrxburst, 1);
+ ch->ch_equeue[head] = (uchar) linestatus;
+ dgnc_sniff_nowait_nolock(ch, "UART READ", ch->ch_rqueue + head, 1);
+
+ DPR_READ(("DATA/LSR pair: %x %x\n", ch->ch_rqueue[head], ch->ch_equeue[head]));
+
+ /* Ditch any remaining linestatus value. */
+ linestatus = 0;
+
+ /* Add to and flip head if needed */
+ head = (head + 1) & RQUEUEMASK;
+
+ qleft--;
+ ch->ch_rxcount++;
+ }
+
+ /*
+ * Write new final heads to channel structure.
+ */
+ ch->ch_r_head = head & RQUEUEMASK;
+ ch->ch_e_head = head & EQUEUEMASK;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+/*
+ * This function basically goes to sleep for secs, or until
+ * it gets signalled that the port has fully drained.
+ */
+static int neo_drain(struct tty_struct *tty, uint seconds)
+{
+ ulong lock_flags;
+ struct channel_t *ch;
+ struct un_t *un;
+ int rc = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -ENXIO;
+
+ un = (struct un_t *) tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -ENXIO;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENXIO;
+
+ DPR_IOCTL(("%d Drain wait started.\n", __LINE__));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ un->un_flags |= UN_EMPTY;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Go to sleep waiting for the tty layer to wake me back up when
+ * the empty flag goes away.
+ *
+ * NOTE: TODO: Do something with time passed in.
+ */
+ rc = wait_event_interruptible(un->un_flags_wait, ((un->un_flags & UN_EMPTY) == 0));
+
+ /* If ret is non-zero, user ctrl-c'ed us */
+ if (rc)
+ DPR_IOCTL(("%d Drain - User ctrl c'ed\n", __LINE__));
+ else
+ DPR_IOCTL(("%d Drain wait finished.\n", __LINE__));
+
+ return rc;
+}
+
+
+/*
+ * Flush the WRITE FIFO on the Neo.
+ *
+ * NOTE: Channel lock MUST be held before calling this function!
+ */
+static void neo_flush_uart_write(struct channel_t *ch)
+{
+ uchar tmp = 0;
+ int i = 0;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr);
+ neo_pci_posting_flush(ch->ch_bd);
+
+ for (i = 0; i < 10; i++) {
+
+ /* Check to see if the UART feels it completely flushed the FIFO. */
+ tmp = readb(&ch->ch_neo_uart->isr_fcr);
+ if (tmp & 4) {
+ DPR_IOCTL(("Still flushing TX UART... i: %d\n", i));
+ udelay(10);
+ } else
+ break;
+ }
+
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+}
+
+
+/*
+ * Flush the READ FIFO on the Neo.
+ *
+ * NOTE: Channel lock MUST be held before calling this function!
+ */
+static void neo_flush_uart_read(struct channel_t *ch)
+{
+ uchar tmp = 0;
+ int i = 0;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ writeb((UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR), &ch->ch_neo_uart->isr_fcr);
+ neo_pci_posting_flush(ch->ch_bd);
+
+ for (i = 0; i < 10; i++) {
+
+ /* Check to see if the UART feels it completely flushed the FIFO. */
+ tmp = readb(&ch->ch_neo_uart->isr_fcr);
+ if (tmp & 2) {
+ DPR_IOCTL(("Still flushing RX UART... i: %d\n", i));
+ udelay(10);
+ } else
+ break;
+ }
+}
+
+
+static void neo_copy_data_from_queue_to_uart(struct channel_t *ch)
+{
+ ushort head;
+ ushort tail;
+ int n;
+ int s;
+ int qlen;
+ uint len_written = 0;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* No data to write to the UART */
+ if (ch->ch_w_tail == ch->ch_w_head) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* If port is "stopped", don't send any data to the UART */
+ if ((ch->ch_flags & CH_FORCED_STOP) || (ch->ch_flags & CH_BREAK_SENDING)) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * If FIFOs are disabled. Send data directly to txrx register
+ */
+ if (!(ch->ch_flags & CH_FIFO_ENABLED)) {
+ uchar lsrbits = readb(&ch->ch_neo_uart->lsr);
+
+ /* Cache the LSR bits for later parsing */
+ ch->ch_cached_lsr |= lsrbits;
+ if (ch->ch_cached_lsr & UART_LSR_THRE) {
+ ch->ch_cached_lsr &= ~(UART_LSR_THRE);
+
+ /*
+ * If RTS Toggle mode is on, turn on RTS now if not already set,
+ * and make sure we get an event when the data transfer has completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_RTS)) {
+ ch->ch_mostat |= (UART_MCR_RTS);
+ neo_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+ /*
+ * If DTR Toggle mode is on, turn on DTR now if not already set,
+ * and make sure we get an event when the data transfer has completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_DTR)) {
+ ch->ch_mostat |= (UART_MCR_DTR);
+ neo_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+
+ writeb(ch->ch_wqueue[ch->ch_w_tail], &ch->ch_neo_uart->txrx);
+ DPR_WRITE(("Tx data: %x\n", ch->ch_wqueue[ch->ch_w_head]));
+ ch->ch_w_tail++;
+ ch->ch_w_tail &= WQUEUEMASK;
+ ch->ch_txcount++;
+ }
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * We have to do it this way, because of the EXAR TXFIFO count bug.
+ */
+ if ((ch->ch_bd->dvid & 0xf0) < UART_XR17E158_DVID) {
+ if (!(ch->ch_flags & (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM))) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ len_written = 0;
+
+ n = readb(&ch->ch_neo_uart->tfifo);
+
+ if ((unsigned int) n > ch->ch_t_tlevel) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ n = UART_17158_TX_FIFOSIZE - ch->ch_t_tlevel;
+ } else {
+ n = UART_17158_TX_FIFOSIZE - readb(&ch->ch_neo_uart->tfifo);
+ }
+
+ /* cache head and tail of queue */
+ head = ch->ch_w_head & WQUEUEMASK;
+ tail = ch->ch_w_tail & WQUEUEMASK;
+ qlen = (head - tail) & WQUEUEMASK;
+
+ /* Find minimum of the FIFO space, versus queue length */
+ n = min(n, qlen);
+
+ while (n > 0) {
+
+ s = ((head >= tail) ? head : WQUEUESIZE) - tail;
+ s = min(s, n);
+
+ if (s <= 0)
+ break;
+
+ /*
+ * If RTS Toggle mode is on, turn on RTS now if not already set,
+ * and make sure we get an event when the data transfer has completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_RTS)) {
+ ch->ch_mostat |= (UART_MCR_RTS);
+ neo_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+
+ /*
+ * If DTR Toggle mode is on, turn on DTR now if not already set,
+ * and make sure we get an event when the data transfer has completed.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+ if (!(ch->ch_mostat & UART_MCR_DTR)) {
+ ch->ch_mostat |= (UART_MCR_DTR);
+ neo_assert_modem_signals(ch);
+ }
+ ch->ch_tun.un_flags |= (UN_EMPTY);
+ }
+
+ memcpy_toio(&ch->ch_neo_uart->txrxburst, ch->ch_wqueue + tail, s);
+ dgnc_sniff_nowait_nolock(ch, "UART WRITE", ch->ch_wqueue + tail, s);
+
+ /* Add and flip queue if needed */
+ tail = (tail + s) & WQUEUEMASK;
+ n -= s;
+ ch->ch_txcount += s;
+ len_written += s;
+ }
+
+ /* Update the final tail */
+ ch->ch_w_tail = tail & WQUEUEMASK;
+
+ if (len_written > 0) {
+ neo_pci_posting_flush(ch->ch_bd);
+ ch->ch_flags &= ~(CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+static void neo_parse_modem(struct channel_t *ch, uchar signals)
+{
+ volatile uchar msignals = signals;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DPR_MSIGS(("neo_parse_modem: port: %d msignals: %x\n", ch->ch_portnum, msignals));
+
+ /*
+ * Do altpin switching. Altpin switches DCD and DSR.
+ * This prolly breaks DSRPACE, so we should be more clever here.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_ALTPIN) {
+ uchar mswap = msignals;
+
+ if (mswap & UART_MSR_DDCD) {
+ msignals &= ~UART_MSR_DDCD;
+ msignals |= UART_MSR_DDSR;
+ }
+ if (mswap & UART_MSR_DDSR) {
+ msignals &= ~UART_MSR_DDSR;
+ msignals |= UART_MSR_DDCD;
+ }
+ if (mswap & UART_MSR_DCD) {
+ msignals &= ~UART_MSR_DCD;
+ msignals |= UART_MSR_DSR;
+ }
+ if (mswap & UART_MSR_DSR) {
+ msignals &= ~UART_MSR_DSR;
+ msignals |= UART_MSR_DCD;
+ }
+ }
+
+ /* Scrub off lower bits. They signify delta's, which I don't care about */
+ msignals &= 0xf0;
+
+ if (msignals & UART_MSR_DCD)
+ ch->ch_mistat |= UART_MSR_DCD;
+ else
+ ch->ch_mistat &= ~UART_MSR_DCD;
+
+ if (msignals & UART_MSR_DSR)
+ ch->ch_mistat |= UART_MSR_DSR;
+ else
+ ch->ch_mistat &= ~UART_MSR_DSR;
+
+ if (msignals & UART_MSR_RI)
+ ch->ch_mistat |= UART_MSR_RI;
+ else
+ ch->ch_mistat &= ~UART_MSR_RI;
+
+ if (msignals & UART_MSR_CTS)
+ ch->ch_mistat |= UART_MSR_CTS;
+ else
+ ch->ch_mistat &= ~UART_MSR_CTS;
+
+ DPR_MSIGS(("Port: %d DTR: %d RTS: %d CTS: %d DSR: %d " "RI: %d CD: %d\n",
+ ch->ch_portnum,
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_DTR),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MCR_RTS),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_CTS),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DSR),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_RI),
+ !!((ch->ch_mistat | ch->ch_mostat) & UART_MSR_DCD)));
+}
+
+
+/* Make the UART raise any of the output signals we want up */
+static void neo_assert_modem_signals(struct channel_t *ch)
+{
+ uchar out;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ out = ch->ch_mostat;
+
+ if (ch->ch_flags & CH_LOOPBACK)
+ out |= UART_MCR_LOOP;
+
+ writeb(out, &ch->ch_neo_uart->mcr);
+ neo_pci_posting_flush(ch->ch_bd);
+
+ /* Give time for the UART to actually raise/drop the signals */
+ udelay(10);
+}
+
+
+static void neo_send_start_character(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ if (ch->ch_startc != _POSIX_VDISABLE) {
+ ch->ch_xon_sends++;
+ writeb(ch->ch_startc, &ch->ch_neo_uart->txrx);
+ neo_pci_posting_flush(ch->ch_bd);
+ udelay(10);
+ }
+}
+
+
+static void neo_send_stop_character(struct channel_t *ch)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ if (ch->ch_stopc != _POSIX_VDISABLE) {
+ ch->ch_xoff_sends++;
+ writeb(ch->ch_stopc, &ch->ch_neo_uart->txrx);
+ neo_pci_posting_flush(ch->ch_bd);
+ udelay(10);
+ }
+}
+
+
+/*
+ * neo_uart_init
+ */
+static void neo_uart_init(struct channel_t *ch)
+{
+
+ writeb(0, &ch->ch_neo_uart->ier);
+ writeb(0, &ch->ch_neo_uart->efr);
+ writeb(UART_EFR_ECB, &ch->ch_neo_uart->efr);
+
+
+ /* Clear out UART and FIFO */
+ readb(&ch->ch_neo_uart->txrx);
+ writeb((UART_FCR_ENABLE_FIFO|UART_FCR_CLEAR_RCVR|UART_FCR_CLEAR_XMIT), &ch->ch_neo_uart->isr_fcr);
+ readb(&ch->ch_neo_uart->lsr);
+ readb(&ch->ch_neo_uart->msr);
+
+ ch->ch_flags |= CH_FIFO_ENABLED;
+
+ /* Assert any signals we want up */
+ writeb(ch->ch_mostat, &ch->ch_neo_uart->mcr);
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+/*
+ * Make the UART completely turn off.
+ */
+static void neo_uart_off(struct channel_t *ch)
+{
+ /* Turn off UART enhanced bits */
+ writeb(0, &ch->ch_neo_uart->efr);
+
+ /* Stop all interrupts from occurring. */
+ writeb(0, &ch->ch_neo_uart->ier);
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static uint neo_get_uart_bytes_left(struct channel_t *ch)
+{
+ uchar left = 0;
+ uchar lsr = readb(&ch->ch_neo_uart->lsr);
+
+ /* We must cache the LSR as some of the bits get reset once read... */
+ ch->ch_cached_lsr |= lsr;
+
+ /* Determine whether the Transmitter is empty or not */
+ if (!(lsr & UART_LSR_TEMT)) {
+ if (ch->ch_flags & CH_TX_FIFO_EMPTY) {
+ tasklet_schedule(&ch->ch_bd->helper_tasklet);
+ }
+ left = 1;
+ } else {
+ ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
+ left = 0;
+ }
+
+ return left;
+}
+
+
+/* Channel lock MUST be held by the calling function! */
+static void neo_send_break(struct channel_t *ch, int msecs)
+{
+ /*
+ * If we receive a time of 0, this means turn off the break.
+ */
+ if (msecs == 0) {
+ if (ch->ch_flags & CH_BREAK_SENDING) {
+ uchar temp = readb(&ch->ch_neo_uart->lcr);
+ writeb((temp & ~UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+ neo_pci_posting_flush(ch->ch_bd);
+ ch->ch_flags &= ~(CH_BREAK_SENDING);
+ ch->ch_stop_sending_break = 0;
+ DPR_IOCTL(("Finishing UART_LCR_SBC! finished: %lx\n", jiffies));
+ }
+ return;
+ }
+
+ /*
+ * Set the time we should stop sending the break.
+ * If we are already sending a break, toss away the existing
+ * time to stop, and use this new value instead.
+ */
+ ch->ch_stop_sending_break = jiffies + dgnc_jiffies_from_ms(msecs);
+
+ /* Tell the UART to start sending the break */
+ if (!(ch->ch_flags & CH_BREAK_SENDING)) {
+ uchar temp = readb(&ch->ch_neo_uart->lcr);
+ writeb((temp | UART_LCR_SBC), &ch->ch_neo_uart->lcr);
+ neo_pci_posting_flush(ch->ch_bd);
+ ch->ch_flags |= (CH_BREAK_SENDING);
+ DPR_IOCTL(("Port %d. Starting UART_LCR_SBC! start: %lx should end: %lx\n",
+ ch->ch_portnum, jiffies, ch->ch_stop_sending_break));
+ }
+}
+
+
+/*
+ * neo_send_immediate_char.
+ *
+ * Sends a specific character as soon as possible to the UART,
+ * jumping over any bytes that might be in the write queue.
+ *
+ * The channel lock MUST be held by the calling function.
+ */
+static void neo_send_immediate_char(struct channel_t *ch, unsigned char c)
+{
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ writeb(c, &ch->ch_neo_uart->txrx);
+ neo_pci_posting_flush(ch->ch_bd);
+}
+
+
+static unsigned int neo_read_eeprom(unsigned char __iomem *base, unsigned int address)
+{
+ unsigned int enable;
+ unsigned int bits;
+ unsigned int databit;
+ unsigned int val;
+
+ /* enable chip select */
+ writeb(NEO_EECS, base + NEO_EEREG);
+ /* READ */
+ enable = (address | 0x180);
+
+ for (bits = 9; bits--; ) {
+ databit = (enable & (1 << bits)) ? NEO_EEDI : 0;
+ /* Set read address */
+ writeb(databit | NEO_EECS, base + NEO_EEREG);
+ writeb(databit | NEO_EECS | NEO_EECK, base + NEO_EEREG);
+ }
+
+ val = 0;
+
+ for (bits = 17; bits--; ) {
+ /* clock to EEPROM */
+ writeb(NEO_EECS, base + NEO_EEREG);
+ writeb(NEO_EECS | NEO_EECK, base + NEO_EEREG);
+ val <<= 1;
+ /* read EEPROM */
+ if (readb(base + NEO_EEREG) & NEO_EEDO)
+ val |= 1;
+ }
+
+ /* clock falling edge */
+ writeb(NEO_EECS, base + NEO_EEREG);
+
+ /* drop chip select */
+ writeb(0x00, base + NEO_EEREG);
+
+ return val;
+}
+
+
+static void neo_vpd(struct dgnc_board *brd)
+{
+ unsigned int i = 0;
+ unsigned int a;
+
+ if (!brd || brd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ if (!brd->re_map_membase)
+ return;
+
+ /* Store the VPD into our buffer */
+ for (i = 0; i < NEO_VPD_IMAGESIZE; i++) {
+ a = neo_read_eeprom(brd->re_map_membase, i);
+ brd->vpd[i*2] = a & 0xff;
+ brd->vpd[(i*2)+1] = (a >> 8) & 0xff;
+ }
+
+ if (((brd->vpd[0x08] != 0x82) /* long resource name tag */
+ && (brd->vpd[0x10] != 0x82)) /* long resource name tag (PCI-66 files)*/
+ || (brd->vpd[0x7F] != 0x78)) /* small resource end tag */
+ {
+ memset(brd->vpd, '\0', NEO_VPD_IMAGESIZE);
+ } else {
+ /* Search for the serial number */
+ for (i = 0; i < NEO_VPD_IMAGEBYTES - 3; i++)
+ if (brd->vpd[i] == 'S' && brd->vpd[i + 1] == 'N')
+ strncpy(brd->serial_num, &(brd->vpd[i + 3]), 9);
+ }
+}
diff --git a/drivers/staging/dgnc/dgnc_neo.h b/drivers/staging/dgnc/dgnc_neo.h
new file mode 100644
index 00000000000..1a4abb12869
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_neo.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ */
+
+#ifndef __DGNC_NEO_H
+#define __DGNC_NEO_H
+
+#include "dgnc_types.h"
+#include "dgnc_driver.h"
+
+/************************************************************************
+ * Per channel/port NEO UART structure *
+ ************************************************************************
+ * Base Structure Entries Usage Meanings to Host *
+ * *
+ * W = read write R = read only *
+ * U = Unused. *
+ ************************************************************************/
+
+struct neo_uart_struct {
+ u8 txrx; /* WR RHR/THR - Holding Reg */
+ u8 ier; /* WR IER - Interrupt Enable Reg */
+ u8 isr_fcr; /* WR ISR/FCR - Interrupt Status Reg/Fifo Control Reg */
+ u8 lcr; /* WR LCR - Line Control Reg */
+ u8 mcr; /* WR MCR - Modem Control Reg */
+ u8 lsr; /* WR LSR - Line Status Reg */
+ u8 msr; /* WR MSR - Modem Status Reg */
+ u8 spr; /* WR SPR - Scratch Pad Reg */
+ u8 fctr; /* WR FCTR - Feature Control Reg */
+ u8 efr; /* WR EFR - Enhanced Function Reg */
+ u8 tfifo; /* WR TXCNT/TXTRG - Transmit FIFO Reg */
+ u8 rfifo; /* WR RXCNT/RXTRG - Receive FIFO Reg */
+ u8 xoffchar1; /* WR XOFF 1 - XOff Character 1 Reg */
+ u8 xoffchar2; /* WR XOFF 2 - XOff Character 2 Reg */
+ u8 xonchar1; /* WR XON 1 - Xon Character 1 Reg */
+ u8 xonchar2; /* WR XON 2 - XOn Character 2 Reg */
+
+ u8 reserved1[0x2ff - 0x200]; /* U Reserved by Exar */
+ u8 txrxburst[64]; /* RW 64 bytes of RX/TX FIFO Data */
+ u8 reserved2[0x37f - 0x340]; /* U Reserved by Exar */
+ u8 rxburst_with_errors[64]; /* R 64 bytes of RX FIFO Data + LSR */
+};
+
+/* Where to read the extended interrupt register (32bits instead of 8bits) */
+#define UART_17158_POLL_ADDR_OFFSET 0x80
+
+/* These are the current dvid's of the Neo boards */
+#define UART_XR17C158_DVID 0x20
+#define UART_XR17D158_DVID 0x20
+#define UART_XR17E158_DVID 0x40
+
+#define NEO_EECK 0x10 /* Clock */
+#define NEO_EECS 0x20 /* Chip Select */
+#define NEO_EEDI 0x40 /* Data In is an Output Pin */
+#define NEO_EEDO 0x80 /* Data Out is an Input Pin */
+#define NEO_EEREG 0x8E /* offset to EEPROM control reg */
+
+
+#define NEO_VPD_IMAGESIZE 0x40 /* size of image to read from EEPROM in words */
+#define NEO_VPD_IMAGEBYTES (NEO_VPD_IMAGESIZE * 2)
+
+/*
+ * These are the redefinitions for the FCTR on the XR17C158, since
+ * Exar made them different than their earlier design. (XR16C854)
+ */
+
+/* These are only applicable when table D is selected */
+#define UART_17158_FCTR_RTS_NODELAY 0x00
+#define UART_17158_FCTR_RTS_4DELAY 0x01
+#define UART_17158_FCTR_RTS_6DELAY 0x02
+#define UART_17158_FCTR_RTS_8DELAY 0x03
+#define UART_17158_FCTR_RTS_12DELAY 0x12
+#define UART_17158_FCTR_RTS_16DELAY 0x05
+#define UART_17158_FCTR_RTS_20DELAY 0x13
+#define UART_17158_FCTR_RTS_24DELAY 0x06
+#define UART_17158_FCTR_RTS_28DELAY 0x14
+#define UART_17158_FCTR_RTS_32DELAY 0x07
+#define UART_17158_FCTR_RTS_36DELAY 0x16
+#define UART_17158_FCTR_RTS_40DELAY 0x08
+#define UART_17158_FCTR_RTS_44DELAY 0x09
+#define UART_17158_FCTR_RTS_48DELAY 0x10
+#define UART_17158_FCTR_RTS_52DELAY 0x11
+
+#define UART_17158_FCTR_RTS_IRDA 0x10
+#define UART_17158_FCTR_RS485 0x20
+#define UART_17158_FCTR_TRGA 0x00
+#define UART_17158_FCTR_TRGB 0x40
+#define UART_17158_FCTR_TRGC 0x80
+#define UART_17158_FCTR_TRGD 0xC0
+
+/* 17158 trigger table selects.. */
+#define UART_17158_FCTR_BIT6 0x40
+#define UART_17158_FCTR_BIT7 0x80
+
+/* 17158 TX/RX memmapped buffer offsets */
+#define UART_17158_RX_FIFOSIZE 64
+#define UART_17158_TX_FIFOSIZE 64
+
+/* 17158 Extended IIR's */
+#define UART_17158_IIR_RDI_TIMEOUT 0x0C /* Receiver data TIMEOUT */
+#define UART_17158_IIR_XONXOFF 0x10 /* Received an XON/XOFF char */
+#define UART_17158_IIR_HWFLOW_STATE_CHANGE 0x20 /* CTS/DSR or RTS/DTR state change */
+#define UART_17158_IIR_FIFO_ENABLED 0xC0 /* 16550 FIFOs are Enabled */
+
+/*
+ * These are the extended interrupts that get sent
+ * back to us from the UART's 32bit interrupt register
+ */
+#define UART_17158_RX_LINE_STATUS 0x1 /* RX Ready */
+#define UART_17158_RXRDY_TIMEOUT 0x2 /* RX Ready Timeout */
+#define UART_17158_TXRDY 0x3 /* TX Ready */
+#define UART_17158_MSR 0x4 /* Modem State Change */
+#define UART_17158_TX_AND_FIFO_CLR 0x40 /* Transmitter Holding Reg Empty */
+#define UART_17158_RX_FIFO_DATA_ERROR 0x80 /* UART detected an RX FIFO Data error */
+
+/*
+ * These are the EXTENDED definitions for the 17C158's Interrupt
+ * Enable Register.
+ */
+#define UART_17158_EFR_ECB 0x10 /* Enhanced control bit */
+#define UART_17158_EFR_IXON 0x2 /* Receiver compares Xon1/Xoff1 */
+#define UART_17158_EFR_IXOFF 0x8 /* Transmit Xon1/Xoff1 */
+#define UART_17158_EFR_RTSDTR 0x40 /* Auto RTS/DTR Flow Control Enable */
+#define UART_17158_EFR_CTSDSR 0x80 /* Auto CTS/DSR Flow COntrol Enable */
+
+#define UART_17158_XOFF_DETECT 0x1 /* Indicates whether chip saw an incoming XOFF char */
+#define UART_17158_XON_DETECT 0x2 /* Indicates whether chip saw an incoming XON char */
+
+#define UART_17158_IER_RSVD1 0x10 /* Reserved by Exar */
+#define UART_17158_IER_XOFF 0x20 /* Xoff Interrupt Enable */
+#define UART_17158_IER_RTSDTR 0x40 /* Output Interrupt Enable */
+#define UART_17158_IER_CTSDSR 0x80 /* Input Interrupt Enable */
+
+/*
+ * Our Global Variables
+ */
+extern struct board_ops dgnc_neo_ops;
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_pci.h b/drivers/staging/dgnc/dgnc_pci.h
new file mode 100644
index 00000000000..5b6f76d98aa
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_pci.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_PCI_H
+#define __DGNC_PCI_H
+
+#define PCIMAX 32 /* maximum number of PCI boards */
+
+#define DIGI_VID 0x114F
+
+#define PCI_DEVICE_CLASSIC_4_DID 0x0028
+#define PCI_DEVICE_CLASSIC_8_DID 0x0029
+#define PCI_DEVICE_CLASSIC_4_422_DID 0x00D0
+#define PCI_DEVICE_CLASSIC_8_422_DID 0x00D1
+#define PCI_DEVICE_NEO_4_DID 0x00B0
+#define PCI_DEVICE_NEO_8_DID 0x00B1
+#define PCI_DEVICE_NEO_2DB9_DID 0x00C8
+#define PCI_DEVICE_NEO_2DB9PRI_DID 0x00C9
+#define PCI_DEVICE_NEO_2RJ45_DID 0x00CA
+#define PCI_DEVICE_NEO_2RJ45PRI_DID 0x00CB
+#define PCI_DEVICE_NEO_1_422_DID 0x00CC
+#define PCI_DEVICE_NEO_1_422_485_DID 0x00CD
+#define PCI_DEVICE_NEO_2_422_485_DID 0x00CE
+#define PCI_DEVICE_NEO_EXPRESS_8_DID 0x00F0
+#define PCI_DEVICE_NEO_EXPRESS_4_DID 0x00F1
+#define PCI_DEVICE_NEO_EXPRESS_4RJ45_DID 0x00F2
+#define PCI_DEVICE_NEO_EXPRESS_8RJ45_DID 0x00F3
+#define PCI_DEVICE_NEO_EXPRESS_4_IBM_DID 0x00F4
+
+#define PCI_DEVICE_CLASSIC_4_PCI_NAME "ClassicBoard 4 PCI"
+#define PCI_DEVICE_CLASSIC_8_PCI_NAME "ClassicBoard 8 PCI"
+#define PCI_DEVICE_CLASSIC_4_422_PCI_NAME "ClassicBoard 4 422 PCI"
+#define PCI_DEVICE_CLASSIC_8_422_PCI_NAME "ClassicBoard 8 422 PCI"
+#define PCI_DEVICE_NEO_4_PCI_NAME "Neo 4 PCI"
+#define PCI_DEVICE_NEO_8_PCI_NAME "Neo 8 PCI"
+#define PCI_DEVICE_NEO_2DB9_PCI_NAME "Neo 2 - DB9 Universal PCI"
+#define PCI_DEVICE_NEO_2DB9PRI_PCI_NAME "Neo 2 - DB9 Universal PCI - Powered Ring Indicator"
+#define PCI_DEVICE_NEO_2RJ45_PCI_NAME "Neo 2 - RJ45 Universal PCI"
+#define PCI_DEVICE_NEO_2RJ45PRI_PCI_NAME "Neo 2 - RJ45 Universal PCI - Powered Ring Indicator"
+#define PCI_DEVICE_NEO_1_422_PCI_NAME "Neo 1 422 PCI"
+#define PCI_DEVICE_NEO_1_422_485_PCI_NAME "Neo 1 422/485 PCI"
+#define PCI_DEVICE_NEO_2_422_485_PCI_NAME "Neo 2 422/485 PCI"
+
+#define PCI_DEVICE_NEO_EXPRESS_8_PCI_NAME "Neo 8 PCI Express"
+#define PCI_DEVICE_NEO_EXPRESS_4_PCI_NAME "Neo 4 PCI Express"
+#define PCI_DEVICE_NEO_EXPRESS_4RJ45_PCI_NAME "Neo 4 PCI Express RJ45"
+#define PCI_DEVICE_NEO_EXPRESS_8RJ45_PCI_NAME "Neo 8 PCI Express RJ45"
+#define PCI_DEVICE_NEO_EXPRESS_4_IBM_PCI_NAME "Neo 4 PCI Express IBM"
+
+
+/* Size of Memory and I/O for PCI (4 K) */
+#define PCI_RAM_SIZE 0x1000
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE 0x1000
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_sysfs.c b/drivers/staging/dgnc/dgnc_sysfs.c
new file mode 100644
index 00000000000..0f0e8fcb663
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_sysfs.c
@@ -0,0 +1,756 @@
+/*
+ * Copyright 2004 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/serial_reg.h>
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/kdev_t.h>
+
+#include "dgnc_driver.h"
+#include "dgnc_mgmt.h"
+
+
+static ssize_t dgnc_driver_version_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", DG_PART);
+}
+static DRIVER_ATTR(version, S_IRUSR, dgnc_driver_version_show, NULL);
+
+
+static ssize_t dgnc_driver_boards_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", dgnc_NumBoards);
+}
+static DRIVER_ATTR(boards, S_IRUSR, dgnc_driver_boards_show, NULL);
+
+
+static ssize_t dgnc_driver_maxboards_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", MAXBOARDS);
+}
+static DRIVER_ATTR(maxboards, S_IRUSR, dgnc_driver_maxboards_show, NULL);
+
+
+static ssize_t dgnc_driver_pollcounter_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%ld\n", dgnc_poll_counter);
+}
+static DRIVER_ATTR(pollcounter, S_IRUSR, dgnc_driver_pollcounter_show, NULL);
+
+
+static ssize_t dgnc_driver_state_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", dgnc_driver_state_text[dgnc_driver_state]);
+}
+static DRIVER_ATTR(state, S_IRUSR, dgnc_driver_state_show, NULL);
+
+
+static ssize_t dgnc_driver_debug_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%x\n", dgnc_debug);
+}
+
+static ssize_t dgnc_driver_debug_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+ sscanf(buf, "0x%x\n", &dgnc_debug);
+ return count;
+}
+static DRIVER_ATTR(debug, (S_IRUSR | S_IWUSR), dgnc_driver_debug_show, dgnc_driver_debug_store);
+
+
+static ssize_t dgnc_driver_rawreadok_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "0x%x\n", dgnc_rawreadok);
+}
+
+static ssize_t dgnc_driver_rawreadok_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+ sscanf(buf, "0x%x\n", &dgnc_rawreadok);
+ return count;
+}
+static DRIVER_ATTR(rawreadok, (S_IRUSR | S_IWUSR), dgnc_driver_rawreadok_show, dgnc_driver_rawreadok_store);
+
+
+static ssize_t dgnc_driver_pollrate_show(struct device_driver *ddp, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%dms\n", dgnc_poll_tick);
+}
+
+static ssize_t dgnc_driver_pollrate_store(struct device_driver *ddp, const char *buf, size_t count)
+{
+ sscanf(buf, "%d\n", &dgnc_poll_tick);
+ return count;
+}
+static DRIVER_ATTR(pollrate, (S_IRUSR | S_IWUSR), dgnc_driver_pollrate_show, dgnc_driver_pollrate_store);
+
+
+void dgnc_create_driver_sysfiles(struct pci_driver *dgnc_driver)
+{
+ int rc = 0;
+ struct device_driver *driverfs = &dgnc_driver->driver;
+
+ rc |= driver_create_file(driverfs, &driver_attr_version);
+ rc |= driver_create_file(driverfs, &driver_attr_boards);
+ rc |= driver_create_file(driverfs, &driver_attr_maxboards);
+ rc |= driver_create_file(driverfs, &driver_attr_debug);
+ rc |= driver_create_file(driverfs, &driver_attr_rawreadok);
+ rc |= driver_create_file(driverfs, &driver_attr_pollrate);
+ rc |= driver_create_file(driverfs, &driver_attr_pollcounter);
+ rc |= driver_create_file(driverfs, &driver_attr_state);
+ if (rc) {
+ printk(KERN_ERR "DGNC: sysfs driver_create_file failed!\n");
+ }
+}
+
+
+void dgnc_remove_driver_sysfiles(struct pci_driver *dgnc_driver)
+{
+ struct device_driver *driverfs = &dgnc_driver->driver;
+ driver_remove_file(driverfs, &driver_attr_version);
+ driver_remove_file(driverfs, &driver_attr_boards);
+ driver_remove_file(driverfs, &driver_attr_maxboards);
+ driver_remove_file(driverfs, &driver_attr_debug);
+ driver_remove_file(driverfs, &driver_attr_rawreadok);
+ driver_remove_file(driverfs, &driver_attr_pollrate);
+ driver_remove_file(driverfs, &driver_attr_pollcounter);
+ driver_remove_file(driverfs, &driver_attr_state);
+}
+
+
+#define DGNC_VERIFY_BOARD(p, bd) \
+ if (!p) \
+ return 0; \
+ \
+ bd = dev_get_drvdata(p); \
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC) \
+ return 0; \
+ if (bd->state != BOARD_READY) \
+ return 0; \
+
+
+
+static ssize_t dgnc_vpd_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ count += sprintf(buf + count, "\n 0 1 2 3 4 5 6 7 8 9 A B C D E F");
+ for (i = 0; i < 0x40 * 2; i++) {
+ if (!(i % 16))
+ count += sprintf(buf + count, "\n%04X ", i * 2);
+ count += sprintf(buf + count, "%02X ", bd->vpd[i]);
+ }
+ count += sprintf(buf + count, "\n");
+
+ return count;
+}
+static DEVICE_ATTR(vpd, S_IRUSR, dgnc_vpd_show, NULL);
+
+static ssize_t dgnc_serial_number_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ if (bd->serial_num[0] == '\0')
+ count += sprintf(buf + count, "<UNKNOWN>\n");
+ else
+ count += sprintf(buf + count, "%s\n", bd->serial_num);
+
+ return count;
+}
+static DEVICE_ATTR(serial_number, S_IRUSR, dgnc_serial_number_show, NULL);
+
+
+static ssize_t dgnc_ports_state_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %s\n", bd->channels[i]->ch_portnum,
+ bd->channels[i]->ch_open_count ? "Open" : "Closed");
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_state, S_IRUSR, dgnc_ports_state_show, NULL);
+
+
+static ssize_t dgnc_ports_baud_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %d\n", bd->channels[i]->ch_portnum, bd->channels[i]->ch_old_baud);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_baud, S_IRUSR, dgnc_ports_baud_show, NULL);
+
+
+static ssize_t dgnc_ports_msignals_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ if (bd->channels[i]->ch_open_count) {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %s %s %s %s %s %s\n", bd->channels[i]->ch_portnum,
+ (bd->channels[i]->ch_mostat & UART_MCR_RTS) ? "RTS" : "",
+ (bd->channels[i]->ch_mistat & UART_MSR_CTS) ? "CTS" : "",
+ (bd->channels[i]->ch_mostat & UART_MCR_DTR) ? "DTR" : "",
+ (bd->channels[i]->ch_mistat & UART_MSR_DSR) ? "DSR" : "",
+ (bd->channels[i]->ch_mistat & UART_MSR_DCD) ? "DCD" : "",
+ (bd->channels[i]->ch_mistat & UART_MSR_RI) ? "RI" : "");
+ } else {
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d\n", bd->channels[i]->ch_portnum);
+ }
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_msignals, S_IRUSR, dgnc_ports_msignals_show, NULL);
+
+
+static ssize_t dgnc_ports_iflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_iflag);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_iflag, S_IRUSR, dgnc_ports_iflag_show, NULL);
+
+
+static ssize_t dgnc_ports_cflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_cflag);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_cflag, S_IRUSR, dgnc_ports_cflag_show, NULL);
+
+
+static ssize_t dgnc_ports_oflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_oflag);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_oflag, S_IRUSR, dgnc_ports_oflag_show, NULL);
+
+
+static ssize_t dgnc_ports_lflag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_c_lflag);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_lflag, S_IRUSR, dgnc_ports_lflag_show, NULL);
+
+
+static ssize_t dgnc_ports_digi_flag_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %x\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_digi.digi_flags);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_digi_flag, S_IRUSR, dgnc_ports_digi_flag_show, NULL);
+
+
+static ssize_t dgnc_ports_rxcount_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_rxcount);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_rxcount, S_IRUSR, dgnc_ports_rxcount_show, NULL);
+
+
+static ssize_t dgnc_ports_txcount_show(struct device *p, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ int count = 0;
+ int i = 0;
+
+ DGNC_VERIFY_BOARD(p, bd);
+
+ for (i = 0; i < bd->nasync; i++) {
+ count += snprintf(buf + count, PAGE_SIZE - count, "%d %ld\n",
+ bd->channels[i]->ch_portnum, bd->channels[i]->ch_txcount);
+ }
+ return count;
+}
+static DEVICE_ATTR(ports_txcount, S_IRUSR, dgnc_ports_txcount_show, NULL);
+
+
+/* this function creates the sys files that will export each signal status
+ * to sysfs each value will be put in a separate filename
+ */
+void dgnc_create_ports_sysfiles(struct dgnc_board *bd)
+{
+ int rc = 0;
+
+ dev_set_drvdata(&bd->pdev->dev, bd);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_state);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_vpd);
+ rc |= device_create_file(&(bd->pdev->dev), &dev_attr_serial_number);
+ if (rc) {
+ printk(KERN_ERR "DGNC: sysfs device_create_file failed!\n");
+ }
+}
+
+
+/* removes all the sys files created for that port */
+void dgnc_remove_ports_sysfiles(struct dgnc_board *bd)
+{
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_state);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_baud);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_msignals);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_iflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_cflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_oflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_lflag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_digi_flag);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_rxcount);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_ports_txcount);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_vpd);
+ device_remove_file(&(bd->pdev->dev), &dev_attr_serial_number);
+}
+
+
+static ssize_t dgnc_tty_state_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%s", un->un_open_count ? "Open" : "Closed");
+}
+static DEVICE_ATTR(state, S_IRUSR, dgnc_tty_state_show, NULL);
+
+
+static ssize_t dgnc_tty_baud_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_old_baud);
+}
+static DEVICE_ATTR(baud, S_IRUSR, dgnc_tty_baud_show, NULL);
+
+
+static ssize_t dgnc_tty_msignals_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ if (ch->ch_open_count) {
+ return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n",
+ (ch->ch_mostat & UART_MCR_RTS) ? "RTS" : "",
+ (ch->ch_mistat & UART_MSR_CTS) ? "CTS" : "",
+ (ch->ch_mostat & UART_MCR_DTR) ? "DTR" : "",
+ (ch->ch_mistat & UART_MSR_DSR) ? "DSR" : "",
+ (ch->ch_mistat & UART_MSR_DCD) ? "DCD" : "",
+ (ch->ch_mistat & UART_MSR_RI) ? "RI" : "");
+ }
+ return 0;
+}
+static DEVICE_ATTR(msignals, S_IRUSR, dgnc_tty_msignals_show, NULL);
+
+
+static ssize_t dgnc_tty_iflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_iflag);
+}
+static DEVICE_ATTR(iflag, S_IRUSR, dgnc_tty_iflag_show, NULL);
+
+
+static ssize_t dgnc_tty_cflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_cflag);
+}
+static DEVICE_ATTR(cflag, S_IRUSR, dgnc_tty_cflag_show, NULL);
+
+
+static ssize_t dgnc_tty_oflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_oflag);
+}
+static DEVICE_ATTR(oflag, S_IRUSR, dgnc_tty_oflag_show, NULL);
+
+
+static ssize_t dgnc_tty_lflag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_c_lflag);
+}
+static DEVICE_ATTR(lflag, S_IRUSR, dgnc_tty_lflag_show, NULL);
+
+
+static ssize_t dgnc_tty_digi_flag_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags);
+}
+static DEVICE_ATTR(digi_flag, S_IRUSR, dgnc_tty_digi_flag_show, NULL);
+
+
+static ssize_t dgnc_tty_rxcount_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_rxcount);
+}
+static DEVICE_ATTR(rxcount, S_IRUSR, dgnc_tty_rxcount_show, NULL);
+
+
+static ssize_t dgnc_tty_txcount_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%ld\n", ch->ch_txcount);
+}
+static DEVICE_ATTR(txcount, S_IRUSR, dgnc_tty_txcount_show, NULL);
+
+
+static ssize_t dgnc_tty_name_show(struct device *d, struct device_attribute *attr, char *buf)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return 0;
+ if (bd->state != BOARD_READY)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%sn%d%c\n",
+ (un->un_type == DGNC_PRINT) ? "pr" : "tty",
+ bd->boardnum + 1, 'a' + ch->ch_portnum);
+}
+static DEVICE_ATTR(custom_name, S_IRUSR, dgnc_tty_name_show, NULL);
+
+
+static struct attribute *dgnc_sysfs_tty_entries[] = {
+ &dev_attr_state.attr,
+ &dev_attr_baud.attr,
+ &dev_attr_msignals.attr,
+ &dev_attr_iflag.attr,
+ &dev_attr_cflag.attr,
+ &dev_attr_oflag.attr,
+ &dev_attr_lflag.attr,
+ &dev_attr_digi_flag.attr,
+ &dev_attr_rxcount.attr,
+ &dev_attr_txcount.attr,
+ &dev_attr_custom_name.attr,
+ NULL
+};
+
+
+static struct attribute_group dgnc_tty_attribute_group = {
+ .name = NULL,
+ .attrs = dgnc_sysfs_tty_entries,
+};
+
+
+void dgnc_create_tty_sysfs(struct un_t *un, struct device *c)
+{
+ int ret;
+
+ ret = sysfs_create_group(&c->kobj, &dgnc_tty_attribute_group);
+ if (ret) {
+ dev_err(c, "dgnc: failed to create sysfs tty device attributes.\n");
+ sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group);
+ return;
+ }
+
+ dev_set_drvdata(c, un);
+
+}
+
+
+void dgnc_remove_tty_sysfs(struct device *c)
+{
+ sysfs_remove_group(&c->kobj, &dgnc_tty_attribute_group);
+}
+
diff --git a/drivers/staging/dgnc/dgnc_sysfs.h b/drivers/staging/dgnc/dgnc_sysfs.h
new file mode 100644
index 00000000000..68c0de5898a
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_sysfs.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_SYSFS_H
+#define __DGNC_SYSFS_H
+
+#include "dgnc_driver.h"
+
+#include <linux/device.h>
+
+struct dgnc_board;
+struct channel_t;
+struct un_t;
+struct pci_driver;
+struct class_device;
+
+extern void dgnc_create_ports_sysfiles(struct dgnc_board *bd);
+extern void dgnc_remove_ports_sysfiles(struct dgnc_board *bd);
+
+extern void dgnc_create_driver_sysfiles(struct pci_driver *);
+extern void dgnc_remove_driver_sysfiles(struct pci_driver *);
+
+extern int dgnc_tty_class_init(void);
+extern int dgnc_tty_class_destroy(void);
+
+extern void dgnc_create_tty_sysfs(struct un_t *un, struct device *c);
+extern void dgnc_remove_tty_sysfs(struct device *c);
+
+
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_trace.c b/drivers/staging/dgnc/dgnc_trace.c
new file mode 100644
index 00000000000..2f62f2a4354
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_trace.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h> /* For jiffies, task states */
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/vmalloc.h>
+
+#include "dgnc_driver.h"
+#include "dgnc_trace.h"
+
+#define TRC_TO_CONSOLE 1
+
+/* file level globals */
+static char *dgnc_trcbuf; /* the ringbuffer */
+
+#if defined(TRC_TO_KMEM)
+static int dgnc_trcbufi = 0; /* index of the tilde at the end of */
+#endif
+
+#if defined(TRC_TO_KMEM)
+static DEFINE_SPINLOCK(dgnc_tracef_lock);
+#endif
+
+
+#if 0
+
+#if !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE)
+
+void dgnc_tracef(const char *fmt, ...)
+{
+ return;
+}
+
+#else /* !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE) */
+
+void dgnc_tracef(const char *fmt, ...)
+{
+ va_list ap;
+ char buf[TRC_MAXMSG+1];
+ size_t lenbuf;
+ int i;
+ static int failed = FALSE;
+# if defined(TRC_TO_KMEM)
+ unsigned long flags;
+#endif
+
+ if (failed)
+ return;
+# if defined(TRC_TO_KMEM)
+ DGNC_LOCK(dgnc_tracef_lock, flags);
+#endif
+
+ /* Format buf using fmt and arguments contained in ap. */
+ va_start(ap, fmt);
+ i = vsprintf(buf, fmt, ap);
+ va_end(ap);
+ lenbuf = strlen(buf);
+
+# if defined(TRC_TO_KMEM)
+ {
+ static int initd = 0;
+
+ /*
+ * Now, in addition to (or instead of) printing this stuff out
+ * (which is a buffered operation), also tuck it away into a
+ * corner of memory which can be examined post-crash in kdb.
+ */
+ if (!initd) {
+ dgnc_trcbuf = (char *) vmalloc(dgnc_trcbuf_size);
+ if (!dgnc_trcbuf) {
+ failed = TRUE;
+ printk("dgnc: tracing init failed!\n");
+ return;
+ }
+
+ memset(dgnc_trcbuf, '\0', dgnc_trcbuf_size);
+ dgnc_trcbufi = 0;
+ initd++;
+
+ printk("dgnc: tracing enabled - " TRC_DTRC
+ " 0x%lx 0x%x\n",
+ (unsigned long)dgnc_trcbuf,
+ dgnc_trcbuf_size);
+ }
+
+# if defined(TRC_ON_OVERFLOW_WRAP_AROUND)
+ /*
+ * This is the less CPU-intensive way to do things. We simply
+ * wrap around before we fall off the end of the buffer. A
+ * tilde (~) demarcates the current end of the trace.
+ *
+ * This method should be used if you are concerned about race
+ * conditions as it is less likely to affect the timing of
+ * things.
+ */
+
+ if (dgnc_trcbufi + lenbuf >= dgnc_trcbuf_size) {
+ /* We are wrapping, so wipe out the last tilde. */
+ dgnc_trcbuf[dgnc_trcbufi] = '\0';
+ /* put the new string at the beginning of the buffer */
+ dgnc_trcbufi = 0;
+ }
+
+ strcpy(&dgnc_trcbuf[dgnc_trcbufi], buf);
+ dgnc_trcbufi += lenbuf;
+ dgnc_trcbuf[dgnc_trcbufi] = '~';
+
+# elif defined(TRC_ON_OVERFLOW_SHIFT_BUFFER)
+ /*
+ * This is the more CPU-intensive way to do things. If we
+ * venture into the last 1/8 of the buffer, we shift the
+ * last 7/8 of the buffer forward, wiping out the first 1/8.
+ * Advantage: No wrap-around, only truncation from the
+ * beginning.
+ *
+ * This method should not be used if you are concerned about
+ * timing changes affecting the behaviour of the driver (ie,
+ * race conditions).
+ */
+ strcpy(&dgnc_trcbuf[dgnc_trcbufi], buf);
+ dgnc_trcbufi += lenbuf;
+ dgnc_trcbuf[dgnc_trcbufi] = '~';
+ dgnc_trcbuf[dgnc_trcbufi+1] = '\0';
+
+ /* If we're near the end of the trace buffer... */
+ if (dgnc_trcbufi > (dgnc_trcbuf_size/8)*7) {
+ /* Wipe out the first eighth to make some more room. */
+ strcpy(dgnc_trcbuf, &dgnc_trcbuf[dgnc_trcbuf_size/8]);
+ dgnc_trcbufi = strlen(dgnc_trcbuf)-1;
+ /* Plop overflow message at the top of the buffer. */
+ bcopy(TRC_OVERFLOW, dgnc_trcbuf, strlen(TRC_OVERFLOW));
+ }
+# else
+# error "TRC_ON_OVERFLOW_WRAP_AROUND or TRC_ON_OVERFLOW_SHIFT_BUFFER?"
+# endif
+ }
+ DGNC_UNLOCK(dgnc_tracef_lock, flags);
+
+# endif /* defined(TRC_TO_KMEM) */
+}
+
+#endif /* !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE) */
+
+#endif
+
+
+/*
+ * dgnc_tracer_free()
+ *
+ *
+ */
+void dgnc_tracer_free(void)
+{
+ if (dgnc_trcbuf)
+ vfree(dgnc_trcbuf);
+}
diff --git a/drivers/staging/dgnc/dgnc_trace.h b/drivers/staging/dgnc/dgnc_trace.h
new file mode 100644
index 00000000000..efed88a627d
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_trace.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ *
+ *****************************************************************************
+ * Header file for dgnc_trace.c
+ *
+ */
+
+#ifndef __DGNC_TRACE_H
+#define __DGNC_TRACE_H
+
+#include "dgnc_driver.h"
+
+#if 0
+
+# if !defined(TRC_TO_KMEM) && !defined(TRC_TO_CONSOLE)
+ void dgnc_tracef(const char *fmt, ...);
+# else
+ void dgnc_tracef(const char *fmt, ...);
+# endif
+
+#endif
+
+void dgnc_tracer_free(void);
+
+#endif
+
diff --git a/drivers/staging/dgnc/dgnc_tty.c b/drivers/staging/dgnc/dgnc_tty.c
new file mode 100644
index 00000000000..4135cb0ed9f
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_tty.c
@@ -0,0 +1,3446 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ *
+ * NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
+ *
+ * This is shared code between Digi's CVS archive and the
+ * Linux Kernel sources.
+ * Changing the source just for reformatting needlessly breaks
+ * our CVS diff history.
+ *
+ * Send any bug fixes/changes to: Eng.Linux at digi dot com.
+ * Thank you.
+ */
+
+/************************************************************************
+ *
+ * This file implements the tty driver functionality for the
+ * Neo and ClassicBoard PCI based product lines.
+ *
+ ************************************************************************
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h> /* For jiffies, task states */
+#include <linux/interrupt.h> /* For tasklet and interrupt structs/defines */
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_reg.h>
+#include <linux/slab.h>
+#include <linux/delay.h> /* For udelay */
+#include <asm/uaccess.h> /* For copy_from_user/copy_to_user */
+#include <linux/pci.h>
+
+#include "dgnc_driver.h"
+#include "dgnc_tty.h"
+#include "dgnc_types.h"
+#include "dgnc_trace.h"
+#include "dgnc_neo.h"
+#include "dgnc_cls.h"
+#include "dpacompat.h"
+#include "dgnc_sysfs.h"
+
+#define init_MUTEX(sem) sema_init(sem, 1)
+#define DECLARE_MUTEX(name) \
+ struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
+
+/*
+ * internal variables
+ */
+static struct dgnc_board *dgnc_BoardsByMajor[256];
+static uchar *dgnc_TmpWriteBuf = NULL;
+static DECLARE_MUTEX(dgnc_TmpWriteSem);
+
+/*
+ * Default transparent print information.
+ */
+static struct digi_t dgnc_digi_init = {
+ .digi_flags = DIGI_COOK, /* Flags */
+ .digi_maxcps = 100, /* Max CPS */
+ .digi_maxchar = 50, /* Max chars in print queue */
+ .digi_bufsize = 100, /* Printer buffer size */
+ .digi_onlen = 4, /* size of printer on string */
+ .digi_offlen = 4, /* size of printer off string */
+ .digi_onstr = "\033[5i", /* ANSI printer on string ] */
+ .digi_offstr = "\033[4i", /* ANSI printer off string ] */
+ .digi_term = "ansi" /* default terminal type */
+};
+
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.
+ *
+ * This defines a raw port at 9600 baud, 8 data bits, no parity,
+ * 1 stop bit.
+ */
+static struct ktermios DgncDefaultTermios =
+{
+ .c_iflag = (DEFAULT_IFLAGS), /* iflags */
+ .c_oflag = (DEFAULT_OFLAGS), /* oflags */
+ .c_cflag = (DEFAULT_CFLAGS), /* cflags */
+ .c_lflag = (DEFAULT_LFLAGS), /* lflags */
+ .c_cc = INIT_C_CC,
+ .c_line = 0,
+};
+
+
+/* Our function prototypes */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file);
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file);
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch);
+static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg);
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo);
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info);
+static int dgnc_tty_write_room(struct tty_struct *tty);
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c);
+static int dgnc_tty_chars_in_buffer(struct tty_struct *tty);
+static void dgnc_tty_start(struct tty_struct *tty);
+static void dgnc_tty_stop(struct tty_struct *tty);
+static void dgnc_tty_throttle(struct tty_struct *tty);
+static void dgnc_tty_unthrottle(struct tty_struct *tty);
+static void dgnc_tty_flush_chars(struct tty_struct *tty);
+static void dgnc_tty_flush_buffer(struct tty_struct *tty);
+static void dgnc_tty_hangup(struct tty_struct *tty);
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value);
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value);
+static int dgnc_tty_tiocmget(struct tty_struct *tty);
+static int dgnc_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear);
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec);
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout);
+static int dgnc_tty_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios);
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char ch);
+
+
+static const struct tty_operations dgnc_tty_ops = {
+ .open = dgnc_tty_open,
+ .close = dgnc_tty_close,
+ .write = dgnc_tty_write,
+ .write_room = dgnc_tty_write_room,
+ .flush_buffer = dgnc_tty_flush_buffer,
+ .chars_in_buffer = dgnc_tty_chars_in_buffer,
+ .flush_chars = dgnc_tty_flush_chars,
+ .ioctl = dgnc_tty_ioctl,
+ .set_termios = dgnc_tty_set_termios,
+ .stop = dgnc_tty_stop,
+ .start = dgnc_tty_start,
+ .throttle = dgnc_tty_throttle,
+ .unthrottle = dgnc_tty_unthrottle,
+ .hangup = dgnc_tty_hangup,
+ .put_char = dgnc_tty_put_char,
+ .tiocmget = dgnc_tty_tiocmget,
+ .tiocmset = dgnc_tty_tiocmset,
+ .break_ctl = dgnc_tty_send_break,
+ .wait_until_sent = dgnc_tty_wait_until_sent,
+ .send_xchar = dgnc_tty_send_xchar
+};
+
+/************************************************************************
+ *
+ * TTY Initialization/Cleanup Functions
+ *
+ ************************************************************************/
+
+/*
+ * dgnc_tty_preinit()
+ *
+ * Initialize any global tty related data before we download any boards.
+ */
+int dgnc_tty_preinit(void)
+{
+ /*
+ * Allocate a buffer for doing the copy from user space to
+ * kernel space in dgnc_write(). We only use one buffer and
+ * control access to it with a semaphore. If we are paging, we
+ * are already in trouble so one buffer won't hurt much anyway.
+ *
+ * We are okay to sleep in the malloc, as this routine
+ * is only called during module load, (not in interrupt context),
+ * and with no locks held.
+ */
+ dgnc_TmpWriteBuf = kmalloc(WRITEBUFLEN, GFP_KERNEL);
+
+ if (!dgnc_TmpWriteBuf) {
+ DPR_INIT(("unable to allocate tmp write buf"));
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_register()
+ *
+ * Init the tty subsystem for this board.
+ */
+int dgnc_tty_register(struct dgnc_board *brd)
+{
+ int rc = 0;
+
+ DPR_INIT(("tty_register start\n"));
+
+ memset(&brd->SerialDriver, 0, sizeof(brd->SerialDriver));
+ memset(&brd->PrintDriver, 0, sizeof(brd->PrintDriver));
+
+ brd->SerialDriver.magic = TTY_DRIVER_MAGIC;
+
+ snprintf(brd->SerialName, MAXTTYNAMELEN, "tty_dgnc_%d_", brd->boardnum);
+
+ brd->SerialDriver.name = brd->SerialName;
+ brd->SerialDriver.name_base = 0;
+ brd->SerialDriver.major = 0;
+ brd->SerialDriver.minor_start = 0;
+ brd->SerialDriver.num = brd->maxports;
+ brd->SerialDriver.type = TTY_DRIVER_TYPE_SERIAL;
+ brd->SerialDriver.subtype = SERIAL_TYPE_NORMAL;
+ brd->SerialDriver.init_termios = DgncDefaultTermios;
+ brd->SerialDriver.driver_name = DRVSTR;
+ brd->SerialDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+ /*
+ * The kernel wants space to store pointers to
+ * tty_struct's and termios's.
+ */
+ brd->SerialDriver.ttys = kzalloc(brd->maxports * sizeof(*brd->SerialDriver.ttys), GFP_KERNEL);
+ if (!brd->SerialDriver.ttys)
+ return -ENOMEM;
+
+ kref_init(&brd->SerialDriver.kref);
+ brd->SerialDriver.termios = kzalloc(brd->maxports * sizeof(*brd->SerialDriver.termios), GFP_KERNEL);
+ if (!brd->SerialDriver.termios)
+ return -ENOMEM;
+
+ /*
+ * Entry points for driver. Called by the kernel from
+ * tty_io.c and n_tty.c.
+ */
+ tty_set_operations(&brd->SerialDriver, &dgnc_tty_ops);
+
+ if (!brd->dgnc_Major_Serial_Registered) {
+ /* Register tty devices */
+ rc = tty_register_driver(&brd->SerialDriver);
+ if (rc < 0) {
+ APR(("Can't register tty device (%d)\n", rc));
+ return rc;
+ }
+ brd->dgnc_Major_Serial_Registered = TRUE;
+ }
+
+ /*
+ * If we're doing transparent print, we have to do all of the above
+ * again, separately so we don't get the LD confused about what major
+ * we are when we get into the dgnc_tty_open() routine.
+ */
+ brd->PrintDriver.magic = TTY_DRIVER_MAGIC;
+ snprintf(brd->PrintName, MAXTTYNAMELEN, "pr_dgnc_%d_", brd->boardnum);
+
+ brd->PrintDriver.name = brd->PrintName;
+ brd->PrintDriver.name_base = 0;
+ brd->PrintDriver.major = brd->SerialDriver.major;
+ brd->PrintDriver.minor_start = 0x80;
+ brd->PrintDriver.num = brd->maxports;
+ brd->PrintDriver.type = TTY_DRIVER_TYPE_SERIAL;
+ brd->PrintDriver.subtype = SERIAL_TYPE_NORMAL;
+ brd->PrintDriver.init_termios = DgncDefaultTermios;
+ brd->PrintDriver.driver_name = DRVSTR;
+ brd->PrintDriver.flags = (TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
+
+ /*
+ * The kernel wants space to store pointers to
+ * tty_struct's and termios's. Must be separated from
+ * the Serial Driver so we don't get confused
+ */
+ brd->PrintDriver.ttys = kzalloc(brd->maxports * sizeof(*brd->PrintDriver.ttys), GFP_KERNEL);
+ if (!brd->PrintDriver.ttys)
+ return -ENOMEM;
+ kref_init(&brd->PrintDriver.kref);
+ brd->PrintDriver.termios = kzalloc(brd->maxports * sizeof(*brd->PrintDriver.termios), GFP_KERNEL);
+ if (!brd->PrintDriver.termios)
+ return -ENOMEM;
+
+ /*
+ * Entry points for driver. Called by the kernel from
+ * tty_io.c and n_tty.c.
+ */
+ tty_set_operations(&brd->PrintDriver, &dgnc_tty_ops);
+
+ if (!brd->dgnc_Major_TransparentPrint_Registered) {
+ /* Register Transparent Print devices */
+ rc = tty_register_driver(&brd->PrintDriver);
+ if (rc < 0) {
+ APR(("Can't register Transparent Print device (%d)\n", rc));
+ return rc;
+ }
+ brd->dgnc_Major_TransparentPrint_Registered = TRUE;
+ }
+
+ dgnc_BoardsByMajor[brd->SerialDriver.major] = brd;
+ brd->dgnc_Serial_Major = brd->SerialDriver.major;
+ brd->dgnc_TransparentPrint_Major = brd->PrintDriver.major;
+
+ DPR_INIT(("DGNC REGISTER TTY: MAJOR: %d\n", brd->SerialDriver.major));
+
+ return rc;
+}
+
+
+/*
+ * dgnc_tty_init()
+ *
+ * Init the tty subsystem. Called once per board after board has been
+ * downloaded and init'ed.
+ */
+int dgnc_tty_init(struct dgnc_board *brd)
+{
+ int i;
+ void __iomem *vaddr;
+ struct channel_t *ch;
+
+ if (!brd)
+ return -ENXIO;
+
+ DPR_INIT(("dgnc_tty_init start\n"));
+
+ /*
+ * Initialize board structure elements.
+ */
+
+ vaddr = brd->re_map_membase;
+
+ brd->nasync = brd->maxports;
+
+ /*
+ * Allocate channel memory that might not have been allocated
+ * when the driver was first loaded.
+ */
+ for (i = 0; i < brd->nasync; i++) {
+ if (!brd->channels[i]) {
+
+ /*
+ * Okay to malloc with GFP_KERNEL, we are not at
+ * interrupt context, and there are no locks held.
+ */
+ brd->channels[i] = kzalloc(sizeof(*brd->channels[i]), GFP_KERNEL);
+ if (!brd->channels[i]) {
+ DPR_CORE(("%s:%d Unable to allocate memory for channel struct\n",
+ __FILE__, __LINE__));
+ }
+ }
+ }
+
+ ch = brd->channels[0];
+ vaddr = brd->re_map_membase;
+
+ /* Set up channel variables */
+ for (i = 0; i < brd->nasync; i++, ch = brd->channels[i]) {
+
+ if (!brd->channels[i])
+ continue;
+
+ DGNC_SPINLOCK_INIT(ch->ch_lock);
+
+ /* Store all our magic numbers */
+ ch->magic = DGNC_CHANNEL_MAGIC;
+ ch->ch_tun.magic = DGNC_UNIT_MAGIC;
+ ch->ch_tun.un_ch = ch;
+ ch->ch_tun.un_type = DGNC_SERIAL;
+ ch->ch_tun.un_dev = i;
+
+ ch->ch_pun.magic = DGNC_UNIT_MAGIC;
+ ch->ch_pun.un_ch = ch;
+ ch->ch_pun.un_type = DGNC_PRINT;
+ ch->ch_pun.un_dev = i + 128;
+
+ if (brd->bd_uart_offset == 0x200)
+ ch->ch_neo_uart = vaddr + (brd->bd_uart_offset * i);
+ else
+ ch->ch_cls_uart = vaddr + (brd->bd_uart_offset * i);
+
+ ch->ch_bd = brd;
+ ch->ch_portnum = i;
+ ch->ch_digi = dgnc_digi_init;
+
+ /* .25 second delay */
+ ch->ch_close_delay = 250;
+
+ init_waitqueue_head(&ch->ch_flags_wait);
+ init_waitqueue_head(&ch->ch_tun.un_flags_wait);
+ init_waitqueue_head(&ch->ch_pun.un_flags_wait);
+ init_waitqueue_head(&ch->ch_sniff_wait);
+
+ {
+ struct device *classp;
+ classp = tty_register_device(&brd->SerialDriver, i,
+ &(ch->ch_bd->pdev->dev));
+ ch->ch_tun.un_sysfs = classp;
+ dgnc_create_tty_sysfs(&ch->ch_tun, classp);
+
+ classp = tty_register_device(&brd->PrintDriver, i,
+ &(ch->ch_bd->pdev->dev));
+ ch->ch_pun.un_sysfs = classp;
+ dgnc_create_tty_sysfs(&ch->ch_pun, classp);
+ }
+
+ }
+
+ DPR_INIT(("dgnc_tty_init finish\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_post_uninit()
+ *
+ * UnInitialize any global tty related data.
+ */
+void dgnc_tty_post_uninit(void)
+{
+ if (dgnc_TmpWriteBuf) {
+ kfree(dgnc_TmpWriteBuf);
+ dgnc_TmpWriteBuf = NULL;
+ }
+}
+
+
+/*
+ * dgnc_tty_uninit()
+ *
+ * Uninitialize the TTY portion of this driver. Free all memory and
+ * resources.
+ */
+void dgnc_tty_uninit(struct dgnc_board *brd)
+{
+ int i = 0;
+
+ if (brd->dgnc_Major_Serial_Registered) {
+ dgnc_BoardsByMajor[brd->SerialDriver.major] = NULL;
+ brd->dgnc_Serial_Major = 0;
+ for (i = 0; i < brd->nasync; i++) {
+ dgnc_remove_tty_sysfs(brd->channels[i]->ch_tun.un_sysfs);
+ tty_unregister_device(&brd->SerialDriver, i);
+ }
+ tty_unregister_driver(&brd->SerialDriver);
+ brd->dgnc_Major_Serial_Registered = FALSE;
+ }
+
+ if (brd->dgnc_Major_TransparentPrint_Registered) {
+ dgnc_BoardsByMajor[brd->PrintDriver.major] = NULL;
+ brd->dgnc_TransparentPrint_Major = 0;
+ for (i = 0; i < brd->nasync; i++) {
+ dgnc_remove_tty_sysfs(brd->channels[i]->ch_pun.un_sysfs);
+ tty_unregister_device(&brd->PrintDriver, i);
+ }
+ tty_unregister_driver(&brd->PrintDriver);
+ brd->dgnc_Major_TransparentPrint_Registered = FALSE;
+ }
+
+ if (brd->SerialDriver.ttys) {
+ kfree(brd->SerialDriver.ttys);
+ brd->SerialDriver.ttys = NULL;
+ }
+ if (brd->PrintDriver.ttys) {
+ kfree(brd->PrintDriver.ttys);
+ brd->PrintDriver.ttys = NULL;
+ }
+}
+
+
+#define TMPBUFLEN (1024)
+
+/*
+ * dgnc_sniff - Dump data out to the "sniff" buffer if the
+ * proc sniff file is opened...
+ */
+void dgnc_sniff_nowait_nolock(struct channel_t *ch, uchar *text, uchar *buf, int len)
+{
+ struct timeval tv;
+ int n;
+ int r;
+ int nbuf;
+ int i;
+ int tmpbuflen;
+ char tmpbuf[TMPBUFLEN];
+ char *p = tmpbuf;
+ int too_much_data;
+
+ /* Leave if sniff not open */
+ if (!(ch->ch_sniff_flags & SNIFF_OPEN))
+ return;
+
+ do_gettimeofday(&tv);
+
+ /* Create our header for data dump */
+ p += sprintf(p, "<%ld %ld><%s><", tv.tv_sec, tv.tv_usec, text);
+ tmpbuflen = p - tmpbuf;
+
+ do {
+ too_much_data = 0;
+
+ for (i = 0; i < len && tmpbuflen < (TMPBUFLEN - 4); i++) {
+ p += sprintf(p, "%02x ", *buf);
+ buf++;
+ tmpbuflen = p - tmpbuf;
+ }
+
+ if (tmpbuflen < (TMPBUFLEN - 4)) {
+ if (i > 0)
+ p += sprintf(p - 1, "%s\n", ">");
+ else
+ p += sprintf(p, "%s\n", ">");
+ } else {
+ too_much_data = 1;
+ len -= i;
+ }
+
+ nbuf = strlen(tmpbuf);
+ p = tmpbuf;
+
+ /*
+ * Loop while data remains.
+ */
+ while (nbuf > 0 && ch->ch_sniff_buf) {
+ /*
+ * Determine the amount of available space left in the
+ * buffer. If there's none, wait until some appears.
+ */
+ n = (ch->ch_sniff_out - ch->ch_sniff_in - 1) & SNIFF_MASK;
+
+ /*
+ * If there is no space left to write to in our sniff buffer,
+ * we have no choice but to drop the data.
+ * We *cannot* sleep here waiting for space, because this
+ * function was probably called by the interrupt/timer routines!
+ */
+ if (n == 0)
+ return;
+
+ /*
+ * Copy as much data as will fit.
+ */
+
+ if (n > nbuf)
+ n = nbuf;
+
+ r = SNIFF_MAX - ch->ch_sniff_in;
+
+ if (r <= n) {
+ memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, r);
+
+ n -= r;
+ ch->ch_sniff_in = 0;
+ p += r;
+ nbuf -= r;
+ }
+
+ memcpy(ch->ch_sniff_buf + ch->ch_sniff_in, p, n);
+
+ ch->ch_sniff_in += n;
+ p += n;
+ nbuf -= n;
+
+ /*
+ * Wakeup any thread waiting for data
+ */
+ if (ch->ch_sniff_flags & SNIFF_WAIT_DATA) {
+ ch->ch_sniff_flags &= ~SNIFF_WAIT_DATA;
+ wake_up_interruptible(&ch->ch_sniff_wait);
+ }
+ }
+
+ /*
+ * If the user sent us too much data to push into our tmpbuf,
+ * we need to keep looping around on all the data.
+ */
+ if (too_much_data) {
+ p = tmpbuf;
+ tmpbuflen = 0;
+ }
+
+ } while (too_much_data);
+}
+
+
+/*=======================================================================
+ *
+ * dgnc_wmove - Write data to transmit queue.
+ *
+ * ch - Pointer to channel structure.
+ * buf - Poiter to characters to be moved.
+ * n - Number of characters to move.
+ *
+ *=======================================================================*/
+static void dgnc_wmove(struct channel_t *ch, char *buf, uint n)
+{
+ int remain;
+ uint head;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ head = ch->ch_w_head & WQUEUEMASK;
+
+ /*
+ * If the write wraps over the top of the circular buffer,
+ * move the portion up to the wrap point, and reset the
+ * pointers to the bottom.
+ */
+ remain = WQUEUESIZE - head;
+
+ if (n >= remain) {
+ n -= remain;
+ memcpy(ch->ch_wqueue + head, buf, remain);
+ head = 0;
+ buf += remain;
+ }
+
+ if (n > 0) {
+ /*
+ * Move rest of data.
+ */
+ remain = n;
+ memcpy(ch->ch_wqueue + head, buf, remain);
+ head += remain;
+ }
+
+ head &= WQUEUEMASK;
+ ch->ch_w_head = head;
+}
+
+
+
+
+/*=======================================================================
+ *
+ * dgnc_input - Process received data.
+ *
+ * ch - Pointer to channel structure.
+ *
+ *=======================================================================*/
+void dgnc_input(struct channel_t *ch)
+{
+ struct dgnc_board *bd;
+ struct tty_struct *tp;
+ struct tty_ldisc *ld;
+ uint rmask;
+ ushort head;
+ ushort tail;
+ int data_len;
+ ulong lock_flags;
+ int flip_len;
+ int len = 0;
+ int n = 0;
+ int s = 0;
+ int i = 0;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ tp = ch->ch_tun.un_tty;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Figure the number of characters in the buffer.
+ * Exit immediately if none.
+ */
+ rmask = RQUEUEMASK;
+ head = ch->ch_r_head & rmask;
+ tail = ch->ch_r_tail & rmask;
+ data_len = (head - tail) & rmask;
+
+ if (data_len == 0) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ DPR_READ(("dgnc_input start\n"));
+
+ /*
+ * If the device is not open, or CREAD is off,
+ * flush input data and return immediately.
+ */
+ if (!tp || (tp->magic != TTY_MAGIC) || !(ch->ch_tun.un_flags & UN_ISOPEN) ||
+ !(tp->termios.c_cflag & CREAD) || (ch->ch_tun.un_flags & UN_CLOSING)) {
+
+ DPR_READ(("input. dropping %d bytes on port %d...\n", data_len, ch->ch_portnum));
+ DPR_READ(("input. tp: %p tp->magic: %x MAGIC:%x ch flags: %x\n",
+ tp, tp ? tp->magic : 0, TTY_MAGIC, ch->ch_tun.un_flags));
+
+ ch->ch_r_head = tail;
+
+ /* Force queue flow control to be released, if needed */
+ dgnc_check_queue_flow_control(ch);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * If we are throttled, simply don't read any data.
+ */
+ if (ch->ch_flags & CH_FORCED_STOPI) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ DPR_READ(("Port %d throttled, not reading any data. head: %x tail: %x\n",
+ ch->ch_portnum, head, tail));
+ return;
+ }
+
+ DPR_READ(("dgnc_input start 2\n"));
+
+ flip_len = TTY_FLIPBUF_SIZE;
+
+ /* Chop down the length, if needed */
+ len = min(data_len, flip_len);
+ len = min(len, (N_TTY_BUF_SIZE - 1));
+
+ ld = tty_ldisc_ref(tp);
+
+#ifdef TTY_DONT_FLIP
+ /*
+ * If the DONT_FLIP flag is on, don't flush our buffer, and act
+ * like the ld doesn't have any space to put the data right now.
+ */
+ if (test_bit(TTY_DONT_FLIP, &tp->flags))
+ len = 0;
+#endif
+
+ /*
+ * If we were unable to get a reference to the ld,
+ * don't flush our buffer, and act like the ld doesn't
+ * have any space to put the data right now.
+ */
+ if (!ld) {
+ len = 0;
+ } else {
+ /*
+ * If ld doesn't have a pointer to a receive_buf function,
+ * flush the data, then act like the ld doesn't have any
+ * space to put the data right now.
+ */
+ if (!ld->ops->receive_buf) {
+ ch->ch_r_head = ch->ch_r_tail;
+ len = 0;
+ }
+ }
+
+ if (len <= 0) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ if (ld)
+ tty_ldisc_deref(ld);
+ return;
+ }
+
+ /*
+ * The tty layer in the kernel has changed in 2.6.16+.
+ *
+ * The flip buffers in the tty structure are no longer exposed,
+ * and probably will be going away eventually.
+ *
+ * If we are completely raw, we don't need to go through a lot
+ * of the tty layers that exist.
+ * In this case, we take the shortest and fastest route we
+ * can to relay the data to the user.
+ *
+ * On the other hand, if we are not raw, we need to go through
+ * the new 2.6.16+ tty layer, which has its API more well defined.
+ */
+ len = tty_buffer_request_room(tp->port, len);
+ n = len;
+
+ /*
+ * n now contains the most amount of data we can copy,
+ * bounded either by how much the Linux tty layer can handle,
+ * or the amount of data the card actually has pending...
+ */
+ while (n) {
+ s = ((head >= tail) ? head : RQUEUESIZE) - tail;
+ s = min(s, n);
+
+ if (s <= 0)
+ break;
+
+ /*
+ * If conditions are such that ld needs to see all
+ * UART errors, we will have to walk each character
+ * and error byte and send them to the buffer one at
+ * a time.
+ */
+ if (I_PARMRK(tp) || I_BRKINT(tp) || I_INPCK(tp)) {
+ for (i = 0; i < s; i++) {
+ if (*(ch->ch_equeue + tail + i) & UART_LSR_BI)
+ tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_BREAK);
+ else if (*(ch->ch_equeue + tail + i) & UART_LSR_PE)
+ tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_PARITY);
+ else if (*(ch->ch_equeue + tail + i) & UART_LSR_FE)
+ tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_FRAME);
+ else
+ tty_insert_flip_char(tp->port, *(ch->ch_rqueue + tail + i), TTY_NORMAL);
+ }
+ } else {
+ tty_insert_flip_string(tp->port, ch->ch_rqueue + tail, s);
+ }
+
+ dgnc_sniff_nowait_nolock(ch, "USER READ", ch->ch_rqueue + tail, s);
+
+ tail += s;
+ n -= s;
+ /* Flip queue if needed */
+ tail &= rmask;
+ }
+
+ ch->ch_r_tail = tail & rmask;
+ ch->ch_e_tail = tail & rmask;
+ dgnc_check_queue_flow_control(ch);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /* Tell the tty layer its okay to "eat" the data now */
+ tty_flip_buffer_push(tp->port);
+
+ if (ld)
+ tty_ldisc_deref(ld);
+
+ DPR_READ(("dgnc_input - finish\n"));
+}
+
+
+/************************************************************************
+ * Determines when CARRIER changes state and takes appropriate
+ * action.
+ ************************************************************************/
+void dgnc_carrier(struct channel_t *ch)
+{
+ struct dgnc_board *bd;
+
+ int virt_carrier = 0;
+ int phys_carrier = 0;
+
+ DPR_CARR(("dgnc_carrier called...\n"));
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ if (ch->ch_mistat & UART_MSR_DCD) {
+ DPR_CARR(("mistat: %x D_CD: %x\n", ch->ch_mistat, ch->ch_mistat & UART_MSR_DCD));
+ phys_carrier = 1;
+ }
+
+ if (ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+ virt_carrier = 1;
+
+ if (ch->ch_c_cflag & CLOCAL)
+ virt_carrier = 1;
+
+ DPR_CARR(("DCD: physical: %d virt: %d\n", phys_carrier, virt_carrier));
+
+ /*
+ * Test for a VIRTUAL carrier transition to HIGH.
+ */
+ if (((ch->ch_flags & CH_FCAR) == 0) && (virt_carrier == 1)) {
+
+ /*
+ * When carrier rises, wake any threads waiting
+ * for carrier in the open routine.
+ */
+
+ DPR_CARR(("carrier: virt DCD rose\n"));
+
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+
+ /*
+ * Test for a PHYSICAL carrier transition to HIGH.
+ */
+ if (((ch->ch_flags & CH_CD) == 0) && (phys_carrier == 1)) {
+
+ /*
+ * When carrier rises, wake any threads waiting
+ * for carrier in the open routine.
+ */
+
+ DPR_CARR(("carrier: physical DCD rose\n"));
+
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+ }
+
+ /*
+ * Test for a PHYSICAL transition to low, so long as we aren't
+ * currently ignoring physical transitions (which is what "virtual
+ * carrier" indicates).
+ *
+ * The transition of the virtual carrier to low really doesn't
+ * matter... it really only means "ignore carrier state", not
+ * "make pretend that carrier is there".
+ */
+ if ((virt_carrier == 0) && ((ch->ch_flags & CH_CD) != 0) &&
+ (phys_carrier == 0))
+ {
+
+ /*
+ * When carrier drops:
+ *
+ * Drop carrier on all open units.
+ *
+ * Flush queues, waking up any task waiting in the
+ * line discipline.
+ *
+ * Send a hangup to the control terminal.
+ *
+ * Enable all select calls.
+ */
+ if (waitqueue_active(&(ch->ch_flags_wait)))
+ wake_up_interruptible(&ch->ch_flags_wait);
+
+ if (ch->ch_tun.un_open_count > 0) {
+ DPR_CARR(("Sending tty hangup\n"));
+ tty_hangup(ch->ch_tun.un_tty);
+ }
+
+ if (ch->ch_pun.un_open_count > 0) {
+ DPR_CARR(("Sending pr hangup\n"));
+ tty_hangup(ch->ch_pun.un_tty);
+ }
+ }
+
+ /*
+ * Make sure that our cached values reflect the current reality.
+ */
+ if (virt_carrier == 1)
+ ch->ch_flags |= CH_FCAR;
+ else
+ ch->ch_flags &= ~CH_FCAR;
+
+ if (phys_carrier == 1)
+ ch->ch_flags |= CH_CD;
+ else
+ ch->ch_flags &= ~CH_CD;
+}
+
+/*
+ * Assign the custom baud rate to the channel structure
+ */
+static void dgnc_set_custom_speed(struct channel_t *ch, uint newrate)
+{
+ int testdiv;
+ int testrate_high;
+ int testrate_low;
+ int deltahigh;
+ int deltalow;
+
+ if (newrate <= 0) {
+ ch->ch_custom_speed = 0;
+ return;
+ }
+
+ /*
+ * Since the divisor is stored in a 16-bit integer, we make sure
+ * we don't allow any rates smaller than a 16-bit integer would allow.
+ * And of course, rates above the dividend won't fly.
+ */
+ if (newrate && newrate < ((ch->ch_bd->bd_dividend / 0xFFFF) + 1))
+ newrate = ((ch->ch_bd->bd_dividend / 0xFFFF) + 1);
+
+ if (newrate && newrate > ch->ch_bd->bd_dividend)
+ newrate = ch->ch_bd->bd_dividend;
+
+ if (newrate > 0) {
+ testdiv = ch->ch_bd->bd_dividend / newrate;
+
+ /*
+ * If we try to figure out what rate the board would use
+ * with the test divisor, it will be either equal or higher
+ * than the requested baud rate. If we then determine the
+ * rate with a divisor one higher, we will get the next lower
+ * supported rate below the requested.
+ */
+ testrate_high = ch->ch_bd->bd_dividend / testdiv;
+ testrate_low = ch->ch_bd->bd_dividend / (testdiv + 1);
+
+ /*
+ * If the rate for the requested divisor is correct, just
+ * use it and be done.
+ */
+ if (testrate_high != newrate) {
+ /*
+ * Otherwise, pick the rate that is closer (i.e. whichever rate
+ * has a smaller delta).
+ */
+ deltahigh = testrate_high - newrate;
+ deltalow = newrate - testrate_low;
+
+ if (deltahigh < deltalow)
+ newrate = testrate_high;
+ else
+ newrate = testrate_low;
+ }
+ }
+
+ ch->ch_custom_speed = newrate;
+}
+
+
+void dgnc_check_queue_flow_control(struct channel_t *ch)
+{
+ int qleft = 0;
+
+ /* Store how much space we have left in the queue */
+ qleft = ch->ch_r_tail - ch->ch_r_head - 1;
+ if (qleft < 0)
+ qleft += RQUEUEMASK + 1;
+
+ /*
+ * Check to see if we should enforce flow control on our queue because
+ * the ld (or user) isn't reading data out of our queue fast enuf.
+ *
+ * NOTE: This is done based on what the current flow control of the
+ * port is set for.
+ *
+ * 1) HWFLOW (RTS) - Turn off the UART's Receive interrupt.
+ * This will cause the UART's FIFO to back up, and force
+ * the RTS signal to be dropped.
+ * 2) SWFLOW (IXOFF) - Keep trying to send a stop character to
+ * the other side, in hopes it will stop sending data to us.
+ * 3) NONE - Nothing we can do. We will simply drop any extra data
+ * that gets sent into us when the queue fills up.
+ */
+ if (qleft < 256) {
+ /* HWFLOW */
+ if (ch->ch_digi.digi_flags & CTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ if (!(ch->ch_flags & CH_RECEIVER_OFF)) {
+ ch->ch_bd->bd_ops->disable_receiver(ch);
+ ch->ch_flags |= (CH_RECEIVER_OFF);
+ DPR_READ(("Internal queue hit hilevel mark (%d)! Turning off interrupts.\n",
+ qleft));
+ }
+ }
+ /* SWFLOW */
+ else if (ch->ch_c_iflag & IXOFF) {
+ if (ch->ch_stops_sent <= MAX_STOPS_SENT) {
+ ch->ch_bd->bd_ops->send_stop_character(ch);
+ ch->ch_stops_sent++;
+ DPR_READ(("Sending stop char! Times sent: %x\n", ch->ch_stops_sent));
+ }
+ }
+ /* No FLOW */
+ else {
+ /* Empty... Can't do anything about the impending overflow... */
+ }
+ }
+
+ /*
+ * Check to see if we should unenforce flow control because
+ * ld (or user) finally read enuf data out of our queue.
+ *
+ * NOTE: This is done based on what the current flow control of the
+ * port is set for.
+ *
+ * 1) HWFLOW (RTS) - Turn back on the UART's Receive interrupt.
+ * This will cause the UART's FIFO to raise RTS back up,
+ * which will allow the other side to start sending data again.
+ * 2) SWFLOW (IXOFF) - Send a start character to
+ * the other side, so it will start sending data to us again.
+ * 3) NONE - Do nothing. Since we didn't do anything to turn off the
+ * other side, we don't need to do anything now.
+ */
+ if (qleft > (RQUEUESIZE / 2)) {
+ /* HWFLOW */
+ if (ch->ch_digi.digi_flags & RTSPACE || ch->ch_c_cflag & CRTSCTS) {
+ if (ch->ch_flags & CH_RECEIVER_OFF) {
+ ch->ch_bd->bd_ops->enable_receiver(ch);
+ ch->ch_flags &= ~(CH_RECEIVER_OFF);
+ DPR_READ(("Internal queue hit lowlevel mark (%d)! Turning on interrupts.\n",
+ qleft));
+ }
+ }
+ /* SWFLOW */
+ else if (ch->ch_c_iflag & IXOFF && ch->ch_stops_sent) {
+ ch->ch_stops_sent = 0;
+ ch->ch_bd->bd_ops->send_start_character(ch);
+ DPR_READ(("Sending start char!\n"));
+ }
+ /* No FLOW */
+ else {
+ /* Nothing needed. */
+ }
+ }
+}
+
+
+void dgnc_wakeup_writes(struct channel_t *ch)
+{
+ int qlen = 0;
+ ulong lock_flags;
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * If channel now has space, wake up anyone waiting on the condition.
+ */
+ qlen = ch->ch_w_head - ch->ch_w_tail;
+ if (qlen < 0)
+ qlen += WQUEUESIZE;
+
+ if (qlen >= (WQUEUESIZE - 256)) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ if (ch->ch_tun.un_flags & UN_ISOPEN) {
+ if ((ch->ch_tun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ ch->ch_tun.un_tty->ldisc->ops->write_wakeup)
+ {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ (ch->ch_tun.un_tty->ldisc->ops->write_wakeup)(ch->ch_tun.un_tty);
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ }
+
+ wake_up_interruptible(&ch->ch_tun.un_tty->write_wait);
+
+ /*
+ * If unit is set to wait until empty, check to make sure
+ * the queue AND FIFO are both empty.
+ */
+ if (ch->ch_tun.un_flags & UN_EMPTY) {
+ if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
+ ch->ch_tun.un_flags &= ~(UN_EMPTY);
+
+ /*
+ * If RTS Toggle mode is on, whenever
+ * the queue and UART is empty, keep RTS low.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) {
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+ ch->ch_bd->bd_ops->assert_modem_signals(ch);
+ }
+
+ /*
+ * If DTR Toggle mode is on, whenever
+ * the queue and UART is empty, keep DTR low.
+ */
+ if (ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) {
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+ ch->ch_bd->bd_ops->assert_modem_signals(ch);
+ }
+ }
+ }
+
+ wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+ }
+
+ if (ch->ch_pun.un_flags & UN_ISOPEN) {
+ if ((ch->ch_pun.un_tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ ch->ch_pun.un_tty->ldisc->ops->write_wakeup)
+ {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ (ch->ch_pun.un_tty->ldisc->ops->write_wakeup)(ch->ch_pun.un_tty);
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ }
+
+ wake_up_interruptible(&ch->ch_pun.un_tty->write_wait);
+
+ /*
+ * If unit is set to wait until empty, check to make sure
+ * the queue AND FIFO are both empty.
+ */
+ if (ch->ch_pun.un_flags & UN_EMPTY) {
+ if ((qlen == 0) && (ch->ch_bd->bd_ops->get_uart_bytes_left(ch) == 0)) {
+ ch->ch_pun.un_flags &= ~(UN_EMPTY);
+ }
+ }
+
+ wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+
+/************************************************************************
+ *
+ * TTY Entry points and helper functions
+ *
+ ************************************************************************/
+
+/*
+ * dgnc_tty_open()
+ *
+ */
+static int dgnc_tty_open(struct tty_struct *tty, struct file *file)
+{
+ struct dgnc_board *brd;
+ struct channel_t *ch;
+ struct un_t *un;
+ uint major = 0;
+ uint minor = 0;
+ int rc = 0;
+ ulong lock_flags;
+
+ rc = 0;
+
+ major = MAJOR(tty_devnum(tty));
+ minor = MINOR(tty_devnum(tty));
+
+ if (major > 255)
+ return -ENXIO;
+
+ /* Get board pointer from our array of majors we have allocated */
+ brd = dgnc_BoardsByMajor[major];
+ if (!brd)
+ return -ENXIO;
+
+ /*
+ * If board is not yet up to a state of READY, go to
+ * sleep waiting for it to happen or they cancel the open.
+ */
+ rc = wait_event_interruptible(brd->state_wait,
+ (brd->state & BOARD_READY));
+
+ if (rc)
+ return rc;
+
+ DGNC_LOCK(brd->bd_lock, lock_flags);
+
+ /* If opened device is greater than our number of ports, bail. */
+ if (PORT_NUM(minor) > brd->nasync) {
+ DGNC_UNLOCK(brd->bd_lock, lock_flags);
+ return -ENXIO;
+ }
+
+ ch = brd->channels[PORT_NUM(minor)];
+ if (!ch) {
+ DGNC_UNLOCK(brd->bd_lock, lock_flags);
+ return -ENXIO;
+ }
+
+ /* Drop board lock */
+ DGNC_UNLOCK(brd->bd_lock, lock_flags);
+
+ /* Grab channel lock */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Figure out our type */
+ if (!IS_PRINT(minor)) {
+ un = &brd->channels[PORT_NUM(minor)]->ch_tun;
+ un->un_type = DGNC_SERIAL;
+ } else if (IS_PRINT(minor)) {
+ un = &brd->channels[PORT_NUM(minor)]->ch_pun;
+ un->un_type = DGNC_PRINT;
+ } else {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ DPR_OPEN(("%d Unknown TYPE!\n", __LINE__));
+ return -ENXIO;
+ }
+
+ /*
+ * If the port is still in a previous open, and in a state
+ * where we simply cannot safely keep going, wait until the
+ * state clears.
+ */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ rc = wait_event_interruptible(ch->ch_flags_wait, ((ch->ch_flags & CH_OPENING) == 0));
+
+ /* If ret is non-zero, user ctrl-c'ed us */
+ if (rc) {
+ DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
+ return -EINTR;
+ }
+
+ /*
+ * If either unit is in the middle of the fragile part of close,
+ * we just cannot touch the channel safely.
+ * Go to sleep, knowing that when the channel can be
+ * touched safely, the close routine will signal the
+ * ch_flags_wait to wake us back up.
+ */
+ rc = wait_event_interruptible(ch->ch_flags_wait,
+ (((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING) == 0));
+
+ /* If ret is non-zero, user ctrl-c'ed us */
+ if (rc) {
+ DPR_OPEN(("%d User ctrl c'ed\n", __LINE__));
+ return -EINTR;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+
+ /* Store our unit into driver_data, so we always have it available. */
+ tty->driver_data = un;
+
+ DPR_OPEN(("Open called. MAJOR: %d MINOR:%d PORT_NUM: %x unit: %p NAME: %s\n",
+ MAJOR(tty_devnum(tty)), MINOR(tty_devnum(tty)), PORT_NUM(minor), un, brd->name));
+
+ DPR_OPEN(("%d: tflag=%x pflag=%x\n", __LINE__, ch->ch_tun.un_flags, ch->ch_pun.un_flags));
+
+ /*
+ * Initialize tty's
+ */
+ if (!(un->un_flags & UN_ISOPEN)) {
+ /* Store important variables. */
+ un->un_tty = tty;
+
+ /* Maybe do something here to the TTY struct as well? */
+ }
+
+
+ /*
+ * Allocate channel buffers for read/write/error.
+ * Set flag, so we don't get trounced on.
+ */
+ ch->ch_flags |= (CH_OPENING);
+
+ /* Drop locks, as malloc with GFP_KERNEL can sleep */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (!ch->ch_rqueue)
+ ch->ch_rqueue = kzalloc(RQUEUESIZE, GFP_KERNEL);
+ if (!ch->ch_equeue)
+ ch->ch_equeue = kzalloc(EQUEUESIZE, GFP_KERNEL);
+ if (!ch->ch_wqueue)
+ ch->ch_wqueue = kzalloc(WQUEUESIZE, GFP_KERNEL);
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags &= ~(CH_OPENING);
+ wake_up_interruptible(&ch->ch_flags_wait);
+
+ /*
+ * Initialize if neither terminal or printer is open.
+ */
+ if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_ISOPEN)) {
+
+ DPR_OPEN(("dgnc_open: initializing channel in open...\n"));
+
+ /*
+ * Flush input queues.
+ */
+ ch->ch_r_head = ch->ch_r_tail = 0;
+ ch->ch_e_head = ch->ch_e_tail = 0;
+ ch->ch_w_head = ch->ch_w_tail = 0;
+
+ brd->bd_ops->flush_uart_write(ch);
+ brd->bd_ops->flush_uart_read(ch);
+
+ ch->ch_flags = 0;
+ ch->ch_cached_lsr = 0;
+ ch->ch_stop_sending_break = 0;
+ ch->ch_stops_sent = 0;
+
+ ch->ch_c_cflag = tty->termios.c_cflag;
+ ch->ch_c_iflag = tty->termios.c_iflag;
+ ch->ch_c_oflag = tty->termios.c_oflag;
+ ch->ch_c_lflag = tty->termios.c_lflag;
+ ch->ch_startc = tty->termios.c_cc[VSTART];
+ ch->ch_stopc = tty->termios.c_cc[VSTOP];
+
+ /*
+ * Bring up RTS and DTR...
+ * Also handle RTS or DTR toggle if set.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+
+ /* Tell UART to init itself */
+ brd->bd_ops->uart_init(ch);
+ }
+
+ /*
+ * Run param in case we changed anything
+ */
+ brd->bd_ops->param(tty);
+
+ dgnc_carrier(ch);
+
+ /*
+ * follow protocol for opening port
+ */
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ rc = dgnc_block_til_ready(tty, file, ch);
+
+ if (rc)
+ DPR_OPEN(("dgnc_tty_open returning after dgnc_block_til_ready "
+ "with %d\n", rc));
+
+ /* No going back now, increment our unit and channel counters */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_open_count++;
+ un->un_open_count++;
+ un->un_flags |= (UN_ISOPEN);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_OPEN(("dgnc_tty_open finished\n"));
+ return rc;
+}
+
+
+/*
+ * dgnc_block_til_ready()
+ *
+ * Wait for DCD, if needed.
+ */
+static int dgnc_block_til_ready(struct tty_struct *tty, struct file *file, struct channel_t *ch)
+{
+ int retval = 0;
+ struct un_t *un = NULL;
+ ulong lock_flags;
+ uint old_flags = 0;
+ int sleep_on_un_flags = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC || !file || !ch || ch->magic != DGNC_CHANNEL_MAGIC) {
+ return -ENXIO;
+ }
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -ENXIO;
+
+ DPR_OPEN(("dgnc_block_til_ready - before block.\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_wopen++;
+
+ /* Loop forever */
+ while (1) {
+
+ sleep_on_un_flags = 0;
+
+ /*
+ * If board has failed somehow during our sleep, bail with error.
+ */
+ if (ch->ch_bd->state == BOARD_FAILED) {
+ retval = -ENXIO;
+ break;
+ }
+
+ /* If tty was hung up, break out of loop and set error. */
+ if (tty_hung_up_p(file)) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ /*
+ * If either unit is in the middle of the fragile part of close,
+ * we just cannot touch the channel safely.
+ * Go back to sleep, knowing that when the channel can be
+ * touched safely, the close routine will signal the
+ * ch_wait_flags to wake us back up.
+ */
+ if (!((ch->ch_tun.un_flags | ch->ch_pun.un_flags) & UN_CLOSING)) {
+
+ /*
+ * Our conditions to leave cleanly and happily:
+ * 1) NONBLOCKING on the tty is set.
+ * 2) CLOCAL is set.
+ * 3) DCD (fake or real) is active.
+ */
+
+ if (file->f_flags & O_NONBLOCK)
+ break;
+
+ if (tty->flags & (1 << TTY_IO_ERROR)) {
+ retval = -EIO;
+ break;
+ }
+
+ if (ch->ch_flags & CH_CD) {
+ DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
+ break;
+ }
+
+ if (ch->ch_flags & CH_FCAR) {
+ DPR_OPEN(("%d: ch_flags: %x\n", __LINE__, ch->ch_flags));
+ break;
+ }
+ } else {
+ sleep_on_un_flags = 1;
+ }
+
+ /*
+ * If there is a signal pending, the user probably
+ * interrupted (ctrl-c) us.
+ * Leave loop with error set.
+ */
+ if (signal_pending(current)) {
+ DPR_OPEN(("%d: signal pending...\n", __LINE__));
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ DPR_OPEN(("dgnc_block_til_ready - blocking.\n"));
+
+ /*
+ * Store the flags before we let go of channel lock
+ */
+ if (sleep_on_un_flags)
+ old_flags = ch->ch_tun.un_flags | ch->ch_pun.un_flags;
+ else
+ old_flags = ch->ch_flags;
+
+ /*
+ * Let go of channel lock before calling schedule.
+ * Our poller will get any FEP events and wake us up when DCD
+ * eventually goes active.
+ */
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_OPEN(("Going to sleep on %s flags...\n",
+ (sleep_on_un_flags ? "un" : "ch")));
+
+ /*
+ * Wait for something in the flags to change from the current value.
+ */
+ if (sleep_on_un_flags)
+ retval = wait_event_interruptible(un->un_flags_wait,
+ (old_flags != (ch->ch_tun.un_flags | ch->ch_pun.un_flags)));
+ else
+ retval = wait_event_interruptible(ch->ch_flags_wait,
+ (old_flags != ch->ch_flags));
+
+ DPR_OPEN(("After sleep... retval: %x\n", retval));
+
+ /*
+ * We got woken up for some reason.
+ * Before looping around, grab our channel lock.
+ */
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ }
+
+ ch->ch_wopen--;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_OPEN(("dgnc_block_til_ready - after blocking.\n"));
+
+ if (retval) {
+ DPR_OPEN(("dgnc_block_til_ready - done. error. retval: %x\n", retval));
+ return retval;
+ }
+
+ DPR_OPEN(("dgnc_block_til_ready - done no error. jiffies: %lu\n", jiffies));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_hangup()
+ *
+ * Hangup the port. Like a close, but don't wait for output to drain.
+ */
+static void dgnc_tty_hangup(struct tty_struct *tty)
+{
+ struct un_t *un;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ DPR_CLOSE(("dgnc_hangup called. ch->ch_open_count: %d un->un_open_count: %d\n",
+ un->un_ch->ch_open_count, un->un_open_count));
+
+ /* flush the transmit queues */
+ dgnc_tty_flush_buffer(tty);
+
+ DPR_CLOSE(("dgnc_hangup finished. ch->ch_open_count: %d un->un_open_count: %d\n",
+ un->un_ch->ch_open_count, un->un_open_count));
+}
+
+
+/*
+ * dgnc_tty_close()
+ *
+ */
+static void dgnc_tty_close(struct tty_struct *tty, struct file *file)
+{
+ struct ktermios *ts;
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+ int rc = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ ts = &tty->termios;
+
+ DPR_CLOSE(("Close called\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Determine if this is the last close or not - and if we agree about
+ * which type of close it is with the Line Discipline
+ */
+ if ((tty->count == 1) && (un->un_open_count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. un_open_count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ APR(("tty->count is 1, un open count is %d\n", un->un_open_count));
+ un->un_open_count = 1;
+ }
+
+ if (--un->un_open_count < 0) {
+ APR(("bad serial port open count of %d\n", un->un_open_count));
+ un->un_open_count = 0;
+ }
+
+ ch->ch_open_count--;
+
+ if (ch->ch_open_count && un->un_open_count) {
+ DPR_CLOSE(("dgnc_tty_close: not last close ch: %d un:%d\n",
+ ch->ch_open_count, un->un_open_count));
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return;
+ }
+
+ /* OK, its the last close on the unit */
+ DPR_CLOSE(("dgnc_tty_close - last close on unit procedures\n"));
+
+ un->un_flags |= UN_CLOSING;
+
+ tty->closing = 1;
+
+
+ /*
+ * Only officially close channel if count is 0 and
+ * DIGI_PRINTER bit is not set.
+ */
+ if ((ch->ch_open_count == 0) && !(ch->ch_digi.digi_flags & DIGI_PRINTER)) {
+
+ ch->ch_flags &= ~(CH_STOPI | CH_FORCED_STOPI);
+
+ /*
+ * turn off print device when closing print device.
+ */
+ if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+ dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ ch->ch_flags &= ~CH_PRON;
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ /* wait for output to drain */
+ /* This will also return if we take an interrupt */
+
+ DPR_CLOSE(("Calling wait_for_drain\n"));
+ rc = bd->bd_ops->drain(tty, 0);
+
+ DPR_CLOSE(("After calling wait_for_drain\n"));
+
+ if (rc)
+ DPR_BASIC(("dgnc_tty_close - bad return: %d ", rc));
+
+ dgnc_tty_flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ tty->closing = 0;
+
+ /*
+ * If we have HUPCL set, lower DTR and RTS
+ */
+ if (ch->ch_c_cflag & HUPCL) {
+ DPR_CLOSE(("Close. HUPCL set, dropping DTR/RTS\n"));
+
+ /* Drop RTS/DTR */
+ ch->ch_mostat &= ~(UART_MCR_DTR | UART_MCR_RTS);
+ bd->bd_ops->assert_modem_signals(ch);
+
+ /*
+ * Go to sleep to ensure RTS/DTR
+ * have been dropped for modems to see it.
+ */
+ if (ch->ch_close_delay) {
+ DPR_CLOSE(("Close. Sleeping for RTS/DTR drop\n"));
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ dgnc_ms_sleep(ch->ch_close_delay);
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ DPR_CLOSE(("Close. After sleeping for RTS/DTR drop\n"));
+ }
+ }
+
+ ch->ch_old_baud = 0;
+
+ /* Turn off UART interrupts for this port */
+ ch->ch_bd->bd_ops->uart_off(ch);
+ } else {
+ /*
+ * turn off print device when closing print device.
+ */
+ if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+ dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ ch->ch_flags &= ~CH_PRON;
+ }
+ }
+
+ un->un_tty = NULL;
+ un->un_flags &= ~(UN_ISOPEN | UN_CLOSING);
+
+ DPR_CLOSE(("Close. Doing wakeups\n"));
+ wake_up_interruptible(&ch->ch_flags_wait);
+ wake_up_interruptible(&un->un_flags_wait);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_BASIC(("dgnc_tty_close - complete\n"));
+}
+
+
+/*
+ * dgnc_tty_chars_in_buffer()
+ *
+ * Return number of characters that have not been transmitted yet.
+ *
+ * This routine is used by the line discipline to determine if there
+ * is data waiting to be transmitted/drained/flushed or not.
+ */
+static int dgnc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct channel_t *ch = NULL;
+ struct un_t *un = NULL;
+ ushort thead;
+ ushort ttail;
+ uint tmask;
+ uint chars = 0;
+ ulong lock_flags = 0;
+
+ if (tty == NULL)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ tmask = WQUEUEMASK;
+ thead = ch->ch_w_head & tmask;
+ ttail = ch->ch_w_tail & tmask;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (ttail == thead) {
+ chars = 0;
+ } else {
+ if (thead >= ttail)
+ chars = thead - ttail;
+ else
+ chars = thead - ttail + WQUEUESIZE;
+ }
+
+ DPR_WRITE(("dgnc_tty_chars_in_buffer. Port: %x - %d (head: %d tail: %d)\n",
+ ch->ch_portnum, chars, thead, ttail));
+
+ return chars;
+}
+
+
+/*
+ * dgnc_maxcps_room
+ *
+ * Reduces bytes_available to the max number of characters
+ * that can be sent currently given the maxcps value, and
+ * returns the new bytes_available. This only affects printer
+ * output.
+ */
+static int dgnc_maxcps_room(struct tty_struct *tty, int bytes_available)
+{
+ struct channel_t *ch = NULL;
+ struct un_t *un = NULL;
+
+ if (!tty)
+ return bytes_available;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return bytes_available;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return bytes_available;
+
+ /*
+ * If its not the Transparent print device, return
+ * the full data amount.
+ */
+ if (un->un_type != DGNC_PRINT)
+ return bytes_available;
+
+ if (ch->ch_digi.digi_maxcps > 0 && ch->ch_digi.digi_bufsize > 0) {
+ int cps_limit = 0;
+ unsigned long current_time = jiffies;
+ unsigned long buffer_time = current_time +
+ (HZ * ch->ch_digi.digi_bufsize) / ch->ch_digi.digi_maxcps;
+
+ if (ch->ch_cpstime < current_time) {
+ /* buffer is empty */
+ ch->ch_cpstime = current_time; /* reset ch_cpstime */
+ cps_limit = ch->ch_digi.digi_bufsize;
+ } else if (ch->ch_cpstime < buffer_time) {
+ /* still room in the buffer */
+ cps_limit = ((buffer_time - ch->ch_cpstime) * ch->ch_digi.digi_maxcps) / HZ;
+ } else {
+ /* no room in the buffer */
+ cps_limit = 0;
+ }
+
+ bytes_available = min(cps_limit, bytes_available);
+ }
+
+ return bytes_available;
+}
+
+
+/*
+ * dgnc_tty_write_room()
+ *
+ * Return space available in Tx buffer
+ */
+static int dgnc_tty_write_room(struct tty_struct *tty)
+{
+ struct channel_t *ch = NULL;
+ struct un_t *un = NULL;
+ ushort head;
+ ushort tail;
+ ushort tmask;
+ int ret = 0;
+ ulong lock_flags = 0;
+
+ if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ tmask = WQUEUEMASK;
+ head = (ch->ch_w_head) & tmask;
+ tail = (ch->ch_w_tail) & tmask;
+
+ ret = tail - head - 1;
+ if (ret < 0)
+ ret += WQUEUESIZE;
+
+ /* Limit printer to maxcps */
+ ret = dgnc_maxcps_room(tty, ret);
+
+ /*
+ * If we are printer device, leave space for
+ * possibly both the on and off strings.
+ */
+ if (un->un_type == DGNC_PRINT) {
+ if (!(ch->ch_flags & CH_PRON))
+ ret -= ch->ch_digi.digi_onlen;
+ ret -= ch->ch_digi.digi_offlen;
+ } else {
+ if (ch->ch_flags & CH_PRON)
+ ret -= ch->ch_digi.digi_offlen;
+ }
+
+ if (ret < 0)
+ ret = 0;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_WRITE(("dgnc_tty_write_room - %d tail: %d head: %d\n", ret, tail, head));
+
+ return ret;
+}
+
+
+/*
+ * dgnc_tty_put_char()
+ *
+ * Put a character into ch->ch_buf
+ *
+ * - used by the line discipline for OPOST processing
+ */
+static int dgnc_tty_put_char(struct tty_struct *tty, unsigned char c)
+{
+ /*
+ * Simply call tty_write.
+ */
+ DPR_WRITE(("dgnc_tty_put_char called\n"));
+ dgnc_tty_write(tty, &c, 1);
+ return 1;
+}
+
+
+/*
+ * dgnc_tty_write()
+ *
+ * Take data from the user or kernel and send it out to the FEP.
+ * In here exists all the Transparent Print magic as well.
+ */
+static int dgnc_tty_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct channel_t *ch = NULL;
+ struct un_t *un = NULL;
+ int bufcount = 0, n = 0;
+ int orig_count = 0;
+ ulong lock_flags;
+ ushort head;
+ ushort tail;
+ ushort tmask;
+ uint remain;
+ int from_user = 0;
+
+ if (tty == NULL || dgnc_TmpWriteBuf == NULL)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return 0;
+
+ if (!count)
+ return 0;
+
+ DPR_WRITE(("dgnc_tty_write: Port: %x tty=%p user=%d len=%d\n",
+ ch->ch_portnum, tty, from_user, count));
+
+ /*
+ * Store original amount of characters passed in.
+ * This helps to figure out if we should ask the FEP
+ * to send us an event when it has more space available.
+ */
+ orig_count = count;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Get our space available for the channel from the board */
+ tmask = WQUEUEMASK;
+ head = (ch->ch_w_head) & tmask;
+ tail = (ch->ch_w_tail) & tmask;
+
+ bufcount = tail - head - 1;
+ if (bufcount < 0)
+ bufcount += WQUEUESIZE;
+
+ DPR_WRITE(("%d: bufcount: %x count: %x tail: %x head: %x tmask: %x\n",
+ __LINE__, bufcount, count, tail, head, tmask));
+
+ /*
+ * Limit printer output to maxcps overall, with bursts allowed
+ * up to bufsize characters.
+ */
+ bufcount = dgnc_maxcps_room(tty, bufcount);
+
+ /*
+ * Take minimum of what the user wants to send, and the
+ * space available in the FEP buffer.
+ */
+ count = min(count, bufcount);
+
+ /*
+ * Bail if no space left.
+ */
+ if (count <= 0) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ /*
+ * Output the printer ON string, if we are in terminal mode, but
+ * need to be in printer mode.
+ */
+ if ((un->un_type == DGNC_PRINT) && !(ch->ch_flags & CH_PRON)) {
+ dgnc_wmove(ch, ch->ch_digi.digi_onstr,
+ (int) ch->ch_digi.digi_onlen);
+ head = (ch->ch_w_head) & tmask;
+ ch->ch_flags |= CH_PRON;
+ }
+
+ /*
+ * On the other hand, output the printer OFF string, if we are
+ * currently in printer mode, but need to output to the terminal.
+ */
+ if ((un->un_type != DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+ dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ head = (ch->ch_w_head) & tmask;
+ ch->ch_flags &= ~CH_PRON;
+ }
+
+ /*
+ * If there is nothing left to copy, or I can't handle any more data, leave.
+ */
+ if (count <= 0) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ if (from_user) {
+
+ count = min(count, WRITEBUFLEN);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * If data is coming from user space, copy it into a temporary
+ * buffer so we don't get swapped out while doing the copy to
+ * the board.
+ */
+ /* we're allowed to block if it's from_user */
+ if (down_interruptible(&dgnc_TmpWriteSem))
+ return -EINTR;
+
+ /*
+ * copy_from_user() returns the number
+ * of bytes that could *NOT* be copied.
+ */
+ count -= copy_from_user(dgnc_TmpWriteBuf, (const uchar __user *) buf, count);
+
+ if (!count) {
+ up(&dgnc_TmpWriteSem);
+ return -EFAULT;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ buf = dgnc_TmpWriteBuf;
+
+ }
+
+ n = count;
+
+ /*
+ * If the write wraps over the top of the circular buffer,
+ * move the portion up to the wrap point, and reset the
+ * pointers to the bottom.
+ */
+ remain = WQUEUESIZE - head;
+
+ if (n >= remain) {
+ n -= remain;
+ memcpy(ch->ch_wqueue + head, buf, remain);
+ dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
+ head = 0;
+ buf += remain;
+ }
+
+ if (n > 0) {
+ /*
+ * Move rest of data.
+ */
+ remain = n;
+ memcpy(ch->ch_wqueue + head, buf, remain);
+ dgnc_sniff_nowait_nolock(ch, "USER WRITE", ch->ch_wqueue + head, remain);
+ head += remain;
+ }
+
+ if (count) {
+ head &= tmask;
+ ch->ch_w_head = head;
+ }
+
+#if 0
+ /*
+ * If this is the print device, and the
+ * printer is still on, we need to turn it
+ * off before going idle.
+ */
+ if (count == orig_count) {
+ if ((un->un_type == DGNC_PRINT) && (ch->ch_flags & CH_PRON)) {
+ head &= tmask;
+ ch->ch_w_head = head;
+ dgnc_wmove(ch, ch->ch_digi.digi_offstr,
+ (int) ch->ch_digi.digi_offlen);
+ head = (ch->ch_w_head) & tmask;
+ ch->ch_flags &= ~CH_PRON;
+ }
+ }
+#endif
+
+ /* Update printer buffer empty time. */
+ if ((un->un_type == DGNC_PRINT) && (ch->ch_digi.digi_maxcps > 0)
+ && (ch->ch_digi.digi_bufsize > 0)) {
+ ch->ch_cpstime += (HZ * count) / ch->ch_digi.digi_maxcps;
+ }
+
+ if (from_user) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ up(&dgnc_TmpWriteSem);
+ } else {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ }
+
+ DPR_WRITE(("Write finished - Write %d bytes of %d.\n", count, orig_count));
+
+ if (count) {
+ /*
+ * Channel lock is grabbed and then released
+ * inside this routine.
+ */
+ ch->ch_bd->bd_ops->copy_data_from_queue_to_uart(ch);
+ }
+
+ return count;
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+
+static int dgnc_tty_tiocmget(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ int result = -EIO;
+ uchar mstat = 0;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return result;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return result;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return result;
+
+ DPR_IOCTL(("dgnc_tty_tiocmget start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ mstat = (ch->ch_mostat | ch->ch_mistat);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ result = 0;
+
+ if (mstat & UART_MCR_DTR)
+ result |= TIOCM_DTR;
+ if (mstat & UART_MCR_RTS)
+ result |= TIOCM_RTS;
+ if (mstat & UART_MSR_CTS)
+ result |= TIOCM_CTS;
+ if (mstat & UART_MSR_DSR)
+ result |= TIOCM_DSR;
+ if (mstat & UART_MSR_RI)
+ result |= TIOCM_RI;
+ if (mstat & UART_MSR_DCD)
+ result |= TIOCM_CD;
+
+ DPR_IOCTL(("dgnc_tty_tiocmget finish\n"));
+
+ return result;
+}
+
+
+/*
+ * dgnc_tty_tiocmset()
+ *
+ * Set modem signals, called by ld.
+ */
+
+static int dgnc_tty_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int ret = -EIO;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return ret;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return ret;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return ret;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return ret;
+
+ DPR_IOCTL(("dgnc_tty_tiocmset start\n"));
+
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ if (set & TIOCM_RTS)
+ ch->ch_mostat |= UART_MCR_RTS;
+
+ if (set & TIOCM_DTR)
+ ch->ch_mostat |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+
+ if (clear & TIOCM_DTR)
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+
+ ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_tiocmset finish\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_send_break()
+ *
+ * Send a Break, called by ld.
+ */
+static int dgnc_tty_send_break(struct tty_struct *tty, int msec)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int ret = -EIO;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return ret;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return ret;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return ret;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return ret;
+
+ switch (msec) {
+ case -1:
+ msec = 0xFFFF;
+ break;
+ case 0:
+ msec = 0;
+ break;
+ default:
+ break;
+ }
+
+ DPR_IOCTL(("dgnc_tty_send_break start 1. %lx\n", jiffies));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_bd->bd_ops->send_break(ch, msec);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_send_break finish\n"));
+
+ return 0;
+
+}
+
+
+/*
+ * dgnc_tty_wait_until_sent()
+ *
+ * wait until data has been transmitted, called by ld.
+ */
+static void dgnc_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int rc;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ rc = bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return;
+ }
+ return;
+}
+
+
+/*
+ * dgnc_send_xchar()
+ *
+ * send a high priority character, called by ld.
+ */
+static void dgnc_tty_send_xchar(struct tty_struct *tty, char c)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_send_xchar start\n"));
+ printk("dgnc_tty_send_xchar start\n");
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ bd->bd_ops->send_immediate_char(ch, c);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_send_xchar finish\n"));
+ printk("dgnc_tty_send_xchar finish\n");
+ return;
+}
+
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static inline int dgnc_get_mstat(struct channel_t *ch)
+{
+ unsigned char mstat;
+ int result = -EIO;
+ ulong lock_flags;
+
+ DPR_IOCTL(("dgnc_getmstat start\n"));
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENXIO;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ mstat = (ch->ch_mostat | ch->ch_mistat);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ result = 0;
+
+ if (mstat & UART_MCR_DTR)
+ result |= TIOCM_DTR;
+ if (mstat & UART_MCR_RTS)
+ result |= TIOCM_RTS;
+ if (mstat & UART_MSR_CTS)
+ result |= TIOCM_CTS;
+ if (mstat & UART_MSR_DSR)
+ result |= TIOCM_DSR;
+ if (mstat & UART_MSR_RI)
+ result |= TIOCM_RI;
+ if (mstat & UART_MSR_DCD)
+ result |= TIOCM_CD;
+
+ DPR_IOCTL(("dgnc_getmstat finish\n"));
+
+ return result;
+}
+
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgnc_get_modem_info(struct channel_t *ch, unsigned int __user *value)
+{
+ int result;
+ int rc;
+
+ DPR_IOCTL(("dgnc_get_modem_info start\n"));
+
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENXIO;
+
+ result = dgnc_get_mstat(ch);
+
+ if (result < 0)
+ return -ENXIO;
+
+ rc = put_user(result, value);
+
+ DPR_IOCTL(("dgnc_get_modem_info finish\n"));
+ return rc;
+}
+
+
+/*
+ * dgnc_set_modem_info()
+ *
+ * Set modem signals, called by ld.
+ */
+static int dgnc_set_modem_info(struct tty_struct *tty, unsigned int command, unsigned int __user *value)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int ret = -ENXIO;
+ unsigned int arg = 0;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return ret;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return ret;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return ret;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return ret;
+
+ ret = 0;
+
+ DPR_IOCTL(("dgnc_set_modem_info() start\n"));
+
+ ret = get_user(arg, value);
+ if (ret)
+ return ret;
+
+ switch (command) {
+ case TIOCMBIS:
+ if (arg & TIOCM_RTS)
+ ch->ch_mostat |= UART_MCR_RTS;
+
+ if (arg & TIOCM_DTR)
+ ch->ch_mostat |= UART_MCR_DTR;
+
+ break;
+
+ case TIOCMBIC:
+ if (arg & TIOCM_RTS)
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+
+ if (arg & TIOCM_DTR)
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+
+ break;
+
+ case TIOCMSET:
+
+ if (arg & TIOCM_RTS)
+ ch->ch_mostat |= UART_MCR_RTS;
+ else
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+
+ if (arg & TIOCM_DTR)
+ ch->ch_mostat |= UART_MCR_DTR;
+ else
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_bd->bd_ops->assert_modem_signals(ch);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_set_modem_info finish\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_digigeta()
+ *
+ * Ioctl to get the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digigeta(struct tty_struct *tty, struct digi_t __user *retinfo)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ struct digi_t tmp;
+ ulong lock_flags;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ memcpy(&tmp, &ch->ch_digi, sizeof(tmp));
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+/*
+ * dgnc_tty_digiseta()
+ *
+ * Ioctl to set the information for ditty.
+ *
+ *
+ *
+ */
+static int dgnc_tty_digiseta(struct tty_struct *tty, struct digi_t __user *new_info)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ struct digi_t new_digi;
+ ulong lock_flags;
+
+ DPR_IOCTL(("DIGI_SETA start\n"));
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -EFAULT;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -EFAULT;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return -EFAULT;
+
+ if (copy_from_user(&new_digi, new_info, sizeof(new_digi))) {
+ DPR_IOCTL(("DIGI_SETA failed copy_from_user\n"));
+ return -EFAULT;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Handle transistions to and from RTS Toggle.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && (new_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat &= ~(UART_MCR_RTS);
+ if ((ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE) && !(new_digi.digi_flags & DIGI_RTS_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_RTS);
+
+ /*
+ * Handle transistions to and from DTR Toggle.
+ */
+ if (!(ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && (new_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat &= ~(UART_MCR_DTR);
+ if ((ch->ch_digi.digi_flags & DIGI_DTR_TOGGLE) && !(new_digi.digi_flags & DIGI_DTR_TOGGLE))
+ ch->ch_mostat |= (UART_MCR_DTR);
+
+ memcpy(&ch->ch_digi, &new_digi, sizeof(new_digi));
+
+ if (ch->ch_digi.digi_maxcps < 1)
+ ch->ch_digi.digi_maxcps = 1;
+
+ if (ch->ch_digi.digi_maxcps > 10000)
+ ch->ch_digi.digi_maxcps = 10000;
+
+ if (ch->ch_digi.digi_bufsize < 10)
+ ch->ch_digi.digi_bufsize = 10;
+
+ if (ch->ch_digi.digi_maxchar < 1)
+ ch->ch_digi.digi_maxchar = 1;
+
+ if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+ ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+ if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+ ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+ if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+ ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+ ch->ch_bd->bd_ops->param(tty);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("DIGI_SETA finish\n"));
+
+ return 0;
+}
+
+
+/*
+ * dgnc_set_termios()
+ */
+static void dgnc_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ unsigned long lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_c_cflag = tty->termios.c_cflag;
+ ch->ch_c_iflag = tty->termios.c_iflag;
+ ch->ch_c_oflag = tty->termios.c_oflag;
+ ch->ch_c_lflag = tty->termios.c_lflag;
+ ch->ch_startc = tty->termios.c_cc[VSTART];
+ ch->ch_stopc = tty->termios.c_cc[VSTOP];
+
+ ch->ch_bd->bd_ops->param(tty);
+ dgnc_carrier(ch);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+}
+
+
+static void dgnc_tty_throttle(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags = 0;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_throttle start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags |= (CH_FORCED_STOPI);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_throttle finish\n"));
+}
+
+
+static void dgnc_tty_unthrottle(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_unthrottle start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags &= ~(CH_FORCED_STOPI);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_unthrottle finish\n"));
+}
+
+
+static void dgnc_tty_start(struct tty_struct *tty)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgcn_tty_start start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags &= ~(CH_FORCED_STOP);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_start finish\n"));
+}
+
+
+static void dgnc_tty_stop(struct tty_struct *tty)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_stop start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags |= (CH_FORCED_STOP);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_stop finish\n"));
+}
+
+
+/*
+ * dgnc_tty_flush_chars()
+ *
+ * Flush the cook buffer
+ *
+ * Note to self, and any other poor souls who venture here:
+ *
+ * flush in this case DOES NOT mean dispose of the data.
+ * instead, it means "stop buffering and send it if you
+ * haven't already." Just guess how I figured that out... SRW 2-Jun-98
+ *
+ * It is also always called in interrupt context - JAR 8-Sept-99
+ */
+static void dgnc_tty_flush_chars(struct tty_struct *tty)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_flush_chars start\n"));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Do something maybe here */
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_flush_chars finish\n"));
+}
+
+
+
+/*
+ * dgnc_tty_flush_buffer()
+ *
+ * Flush Tx buffer (make in == out)
+ */
+static void dgnc_tty_flush_buffer(struct tty_struct *tty)
+{
+ struct channel_t *ch;
+ struct un_t *un;
+ ulong lock_flags;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return;
+
+ DPR_IOCTL(("dgnc_tty_flush_buffer on port: %d start\n", ch->ch_portnum));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_flags &= ~CH_STOP;
+
+ /* Flush our write queue */
+ ch->ch_w_head = ch->ch_w_tail;
+
+ /* Flush UARTs transmit FIFO */
+ ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+ if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+ }
+ if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_flush_buffer finish\n"));
+}
+
+
+
+/*****************************************************************************
+ *
+ * The IOCTL function and all of its helpers
+ *
+ *****************************************************************************/
+
+/*
+ * dgnc_tty_ioctl()
+ *
+ * The usual assortment of ioctl's
+ */
+static int dgnc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg)
+{
+ struct dgnc_board *bd;
+ struct channel_t *ch;
+ struct un_t *un;
+ int rc;
+ ulong lock_flags;
+ void __user *uarg = (void __user *) arg;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -ENODEV;
+
+ un = tty->driver_data;
+ if (!un || un->magic != DGNC_UNIT_MAGIC)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch || ch->magic != DGNC_CHANNEL_MAGIC)
+ return -ENODEV;
+
+ bd = ch->ch_bd;
+ if (!bd || bd->magic != DGNC_BOARD_MAGIC)
+ return -ENODEV;
+
+ DPR_IOCTL(("dgnc_tty_ioctl start on port %d - cmd %s (%x), arg %lx\n",
+ ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ if (un->un_open_count <= 0) {
+ DPR_BASIC(("dgnc_tty_ioctl - unit not open.\n"));
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return -EIO;
+ }
+
+ switch (cmd) {
+
+ /* Here are all the standard ioctl's that we MUST implement */
+
+ case TCSBRK:
+ /*
+ * TCSBRK is SVID version: non-zero arg --> no break
+ * this behaviour is exploited by tcdrain().
+ *
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds so we'll ask for something
+ * in the middle: 0.375 seconds.
+ */
+ rc = tty_check_change(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return -EINTR;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ if (((cmd == TCSBRK) && (!arg)) || (cmd == TCSBRKP)) {
+ ch->ch_bd->bd_ops->send_break(ch, 250);
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
+ ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+ return 0;
+
+
+ case TCSBRKP:
+ /* support for POSIX tcsendbreak()
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds so we'll ask for something
+ * in the middle: 0.375 seconds.
+ */
+ rc = tty_check_change(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return -EINTR;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_bd->bd_ops->send_break(ch, 250);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
+ ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+ return 0;
+
+ case TIOCSBRK:
+ rc = tty_check_change(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ if (rc)
+ return rc;
+
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return -EINTR;
+ }
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ ch->ch_bd->bd_ops->send_break(ch, 250);
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
+ ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+ return 0;
+
+ case TIOCCBRK:
+ /* Do Nothing */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+
+ case TIOCGSOFTCAR:
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) arg);
+ return rc;
+
+ case TIOCSSOFTCAR:
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = get_user(arg, (unsigned long __user *) arg);
+ if (rc)
+ return rc;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ tty->termios.c_cflag = ((tty->termios.c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+ ch->ch_bd->bd_ops->param(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ return 0;
+
+ case TIOCMGET:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return dgnc_get_modem_info(ch, uarg);
+
+ case TIOCMBIS:
+ case TIOCMBIC:
+ case TIOCMSET:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return dgnc_set_modem_info(tty, cmd, uarg);
+
+ /*
+ * Here are any additional ioctl's that we want to implement
+ */
+
+ case TCFLSH:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+ rc = tty_check_change(tty);
+ if (rc) {
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return rc;
+ }
+
+ if ((arg == TCIFLUSH) || (arg == TCIOFLUSH)) {
+ ch->ch_r_head = ch->ch_r_tail;
+ ch->ch_bd->bd_ops->flush_uart_read(ch);
+ /* Force queue flow control to be released, if needed */
+ dgnc_check_queue_flow_control(ch);
+ }
+
+ if ((arg == TCOFLUSH) || (arg == TCIOFLUSH)) {
+ if (!(un->un_type == DGNC_PRINT)) {
+ ch->ch_w_head = ch->ch_w_tail;
+ ch->ch_bd->bd_ops->flush_uart_write(ch);
+
+ if (ch->ch_tun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_tun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_tun.un_flags_wait);
+ }
+
+ if (ch->ch_pun.un_flags & (UN_LOW|UN_EMPTY)) {
+ ch->ch_pun.un_flags &= ~(UN_LOW|UN_EMPTY);
+ wake_up_interruptible(&ch->ch_pun.un_flags_wait);
+ }
+
+ }
+ }
+
+ /* pretend we didn't recognize this IOCTL */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return -ENOIOCTLCMD;
+ case TCSETSF:
+ case TCSETSW:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+ if (cmd == TCSETSF) {
+ /* flush rx */
+ ch->ch_flags &= ~CH_STOP;
+ ch->ch_r_head = ch->ch_r_tail;
+ ch->ch_bd->bd_ops->flush_uart_read(ch);
+ /* Force queue flow control to be released, if needed */
+ dgnc_check_queue_flow_control(ch);
+ }
+
+ /* now wait for all the output to drain */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d\n", rc));
+ return -EINTR;
+ }
+
+ DPR_IOCTL(("dgnc_tty_ioctl finish on port %d - cmd %s (%x), arg %lx\n",
+ ch->ch_portnum, dgnc_ioctl_name(cmd), cmd, arg));
+
+ /* pretend we didn't recognize this */
+ return -ENOIOCTLCMD;
+
+ case TCSETAW:
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return -EINTR;
+ }
+
+ /* pretend we didn't recognize this */
+ return -ENOIOCTLCMD;
+
+ case TCXONC:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ /* Make the ld do it */
+ return -ENOIOCTLCMD;
+
+ case DIGI_GETA:
+ /* get information for ditty */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return dgnc_tty_digigeta(tty, uarg);
+
+ case DIGI_SETAW:
+ case DIGI_SETAF:
+
+ /* set information for ditty */
+ if (cmd == (DIGI_SETAW)) {
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = ch->ch_bd->bd_ops->drain(tty, 0);
+ if (rc) {
+ DPR_IOCTL(("dgnc_tty_ioctl - bad return: %d ", rc));
+ return -EINTR;
+ }
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ } else {
+ tty_ldisc_flush(tty);
+ }
+ /* fall thru */
+
+ case DIGI_SETA:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return dgnc_tty_digiseta(tty, uarg);
+
+ case DIGI_LOOPBACK:
+ {
+ uint loopback = 0;
+ /* Let go of locks when accessing user space, could sleep */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = get_user(loopback, (unsigned int __user *) arg);
+ if (rc)
+ return rc;
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /* Enable/disable internal loopback for this port */
+ if (loopback)
+ ch->ch_flags |= CH_LOOPBACK;
+ else
+ ch->ch_flags &= ~(CH_LOOPBACK);
+
+ ch->ch_bd->bd_ops->param(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ case DIGI_GETCUSTOMBAUD:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = put_user(ch->ch_custom_speed, (unsigned int __user *) arg);
+ return rc;
+
+ case DIGI_SETCUSTOMBAUD:
+ {
+ int new_rate;
+ /* Let go of locks when accessing user space, could sleep */
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = get_user(new_rate, (int __user *) arg);
+ if (rc)
+ return rc;
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ dgnc_set_custom_speed(ch, new_rate);
+ ch->ch_bd->bd_ops->param(tty);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ /*
+ * This ioctl allows insertion of a character into the front
+ * of any pending data to be transmitted.
+ *
+ * This ioctl is to satify the "Send Character Immediate"
+ * call that the RealPort protocol spec requires.
+ */
+ case DIGI_REALPORT_SENDIMMEDIATE:
+ {
+ unsigned char c;
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = get_user(c, (unsigned char __user *) arg);
+ if (rc)
+ return rc;
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+ ch->ch_bd->bd_ops->send_immediate_char(ch, c);
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ return 0;
+ }
+
+ /*
+ * This ioctl returns all the current counts for the port.
+ *
+ * This ioctl is to satify the "Line Error Counters"
+ * call that the RealPort protocol spec requires.
+ */
+ case DIGI_REALPORT_GETCOUNTERS:
+ {
+ struct digi_getcounter buf;
+
+ buf.norun = ch->ch_err_overrun;
+ buf.noflow = 0; /* The driver doesn't keep this stat */
+ buf.nframe = ch->ch_err_frame;
+ buf.nparity = ch->ch_err_parity;
+ buf.nbreak = ch->ch_err_break;
+ buf.rbytes = ch->ch_rxcount;
+ buf.tbytes = ch->ch_txcount;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (copy_to_user(uarg, &buf, sizeof(buf)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ /*
+ * This ioctl returns all current events.
+ *
+ * This ioctl is to satify the "Event Reporting"
+ * call that the RealPort protocol spec requires.
+ */
+ case DIGI_REALPORT_GETEVENTS:
+ {
+ unsigned int events = 0;
+
+ /* NOTE: MORE EVENTS NEEDS TO BE ADDED HERE */
+ if (ch->ch_flags & CH_BREAK_SENDING)
+ events |= EV_TXB;
+ if ((ch->ch_flags & CH_STOP) || (ch->ch_flags & CH_FORCED_STOP))
+ events |= (EV_OPU | EV_OPS);
+
+ if ((ch->ch_flags & CH_STOPI) || (ch->ch_flags & CH_FORCED_STOPI)) {
+ events |= (EV_IPU | EV_IPS);
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+ rc = put_user(events, (unsigned int __user *) arg);
+ return rc;
+ }
+
+ /*
+ * This ioctl returns TOUT and TIN counters based
+ * upon the values passed in by the RealPort Server.
+ * It also passes back whether the UART Transmitter is
+ * empty as well.
+ */
+ case DIGI_REALPORT_GETBUFFERS:
+ {
+ struct digi_getbuffer buf;
+ int tdist;
+ int count;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Get data from user first.
+ */
+ if (copy_from_user(&buf, uarg, sizeof(buf)))
+ return -EFAULT;
+
+ DGNC_LOCK(ch->ch_lock, lock_flags);
+
+ /*
+ * Figure out how much data is in our RX and TX queues.
+ */
+ buf.rxbuf = (ch->ch_r_head - ch->ch_r_tail) & RQUEUEMASK;
+ buf.txbuf = (ch->ch_w_head - ch->ch_w_tail) & WQUEUEMASK;
+
+ /*
+ * Is the UART empty? Add that value to whats in our TX queue.
+ */
+ count = buf.txbuf + ch->ch_bd->bd_ops->get_uart_bytes_left(ch);
+
+ /*
+ * Figure out how much data the RealPort Server believes should
+ * be in our TX queue.
+ */
+ tdist = (buf.tIn - buf.tOut) & 0xffff;
+
+ /*
+ * If we have more data than the RealPort Server believes we
+ * should have, reduce our count to its amount.
+ *
+ * This count difference CAN happen because the Linux LD can
+ * insert more characters into our queue for OPOST processing
+ * that the RealPort Server doesn't know about.
+ */
+ if (buf.txbuf > tdist)
+ buf.txbuf = tdist;
+
+ /*
+ * Report whether our queue and UART TX are completely empty.
+ */
+ if (count)
+ buf.txdone = 0;
+ else
+ buf.txdone = 1;
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ if (copy_to_user(uarg, &buf, sizeof(buf)))
+ return -EFAULT;
+
+ return 0;
+ }
+ default:
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_ioctl - in default\n"));
+ DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n",
+ dgnc_ioctl_name(cmd), cmd, arg));
+
+ return -ENOIOCTLCMD;
+ }
+
+ DGNC_UNLOCK(ch->ch_lock, lock_flags);
+
+ DPR_IOCTL(("dgnc_tty_ioctl end - cmd %s (%x), arg %lx\n",
+ dgnc_ioctl_name(cmd), cmd, arg));
+
+ return 0;
+}
diff --git a/drivers/staging/dgnc/dgnc_tty.h b/drivers/staging/dgnc/dgnc_tty.h
new file mode 100644
index 00000000000..9d1c2847bd9
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_tty.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_TTY_H
+#define __DGNC_TTY_H
+
+#include "dgnc_driver.h"
+
+int dgnc_tty_register(struct dgnc_board *brd);
+
+int dgnc_tty_preinit(void);
+int dgnc_tty_init(struct dgnc_board *);
+
+void dgnc_tty_post_uninit(void);
+void dgnc_tty_uninit(struct dgnc_board *);
+
+void dgnc_input(struct channel_t *ch);
+void dgnc_carrier(struct channel_t *ch);
+void dgnc_wakeup_writes(struct channel_t *ch);
+void dgnc_check_queue_flow_control(struct channel_t *ch);
+
+void dgnc_sniff_nowait_nolock(struct channel_t *ch, uchar *text, uchar *buf, int nbuf);
+
+#endif
diff --git a/drivers/staging/dgnc/dgnc_types.h b/drivers/staging/dgnc/dgnc_types.h
new file mode 100644
index 00000000000..4fa358535f8
--- /dev/null
+++ b/drivers/staging/dgnc/dgnc_types.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DGNC_TYPES_H
+#define __DGNC_TYPES_H
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+/* Required for our shared headers! */
+typedef unsigned char uchar;
+
+#endif
diff --git a/drivers/staging/dgnc/digi.h b/drivers/staging/dgnc/digi.h
new file mode 100644
index 00000000000..6a9adf6591e
--- /dev/null
+++ b/drivers/staging/dgnc/digi.h
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+#ifndef __DIGI_H
+#define __DIGI_H
+
+/************************************************************************
+ *** Definitions for Digi ditty(1) command.
+ ************************************************************************/
+
+
+/*
+ * Copyright (c) 1988-96 Digi International Inc., All Rights Reserved.
+ */
+
+/************************************************************************
+ * This module provides application access to special Digi
+ * serial line enhancements which are not standard UNIX(tm) features.
+ ************************************************************************/
+
+#if !defined(TIOCMODG)
+
+#define TIOCMODG ('d'<<8) | 250 /* get modem ctrl state */
+#define TIOCMODS ('d'<<8) | 251 /* set modem ctrl state */
+
+#ifndef TIOCM_LE
+#define TIOCM_LE 0x01 /* line enable */
+#define TIOCM_DTR 0x02 /* data terminal ready */
+#define TIOCM_RTS 0x04 /* request to send */
+#define TIOCM_ST 0x08 /* secondary transmit */
+#define TIOCM_SR 0x10 /* secondary receive */
+#define TIOCM_CTS 0x20 /* clear to send */
+#define TIOCM_CAR 0x40 /* carrier detect */
+#define TIOCM_RNG 0x80 /* ring indicator */
+#define TIOCM_DSR 0x100 /* data set ready */
+#define TIOCM_RI TIOCM_RNG /* ring (alternate) */
+#define TIOCM_CD TIOCM_CAR /* carrier detect (alt) */
+#endif
+
+#endif
+
+#if !defined(TIOCMSET)
+#define TIOCMSET ('d'<<8) | 252 /* set modem ctrl state */
+#define TIOCMGET ('d'<<8) | 253 /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMBIC)
+#define TIOCMBIC ('d'<<8) | 254 /* set modem ctrl state */
+#define TIOCMBIS ('d'<<8) | 255 /* set modem ctrl state */
+#endif
+
+
+#if !defined(TIOCSDTR)
+#define TIOCSDTR ('e'<<8) | 0 /* set DTR */
+#define TIOCCDTR ('e'<<8) | 1 /* clear DTR */
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA ('e'<<8) | 94 /* Read params */
+
+#define DIGI_SETA ('e'<<8) | 95 /* Set params */
+#define DIGI_SETAW ('e'<<8) | 96 /* Drain & set params */
+#define DIGI_SETAF ('e'<<8) | 97 /* Drain, flush & set params */
+
+#define DIGI_KME ('e'<<8) | 98 /* Read/Write Host */
+ /* Adapter Memory */
+
+#define DIGI_GETFLOW ('e'<<8) | 99 /* Get startc/stopc flow */
+ /* control characters */
+#define DIGI_SETFLOW ('e'<<8) | 100 /* Set startc/stopc flow */
+ /* control characters */
+#define DIGI_GETAFLOW ('e'<<8) | 101 /* Get Aux. startc/stopc */
+ /* flow control chars */
+#define DIGI_SETAFLOW ('e'<<8) | 102 /* Set Aux. startc/stopc */
+ /* flow control chars */
+
+#define DIGI_GEDELAY ('d'<<8) | 246 /* Get edelay */
+#define DIGI_SEDELAY ('d'<<8) | 247 /* Set edelay */
+
+struct digiflow_t {
+ unsigned char startc; /* flow cntl start char */
+ unsigned char stopc; /* flow cntl stop char */
+};
+
+
+#ifdef FLOW_2200
+#define F2200_GETA ('e'<<8) | 104 /* Get 2x36 flow cntl flags */
+#define F2200_SETAW ('e'<<8) | 105 /* Set 2x36 flow cntl flags */
+#define F2200_MASK 0x03 /* 2200 flow cntl bit mask */
+#define FCNTL_2200 0x01 /* 2x36 terminal flow cntl */
+#define PCNTL_2200 0x02 /* 2x36 printer flow cntl */
+#define F2200_XON 0xf8
+#define P2200_XON 0xf9
+#define F2200_XOFF 0xfa
+#define P2200_XOFF 0xfb
+
+#define FXOFF_MASK 0x03 /* 2200 flow status mask */
+#define RCVD_FXOFF 0x01 /* 2x36 Terminal XOFF rcvd */
+#define RCVD_PXOFF 0x02 /* 2x36 Printer XOFF rcvd */
+#endif
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON 0x0001 /* Handle IXON in the FEP */
+#define DIGI_FAST 0x0002 /* Fast baud rates */
+#define RTSPACE 0x0004 /* RTS input flow control */
+#define CTSPACE 0x0008 /* CTS output flow control */
+#define DSRPACE 0x0010 /* DSR output flow control */
+#define DCDPACE 0x0020 /* DCD output flow control */
+#define DTRPACE 0x0040 /* DTR input flow control */
+#define DIGI_COOK 0x0080 /* Cooked processing done in FEP */
+#define DIGI_FORCEDCD 0x0100 /* Force carrier */
+#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+#define DIGI_PRINTER 0x0800 /* Hold port open for flow cntrl*/
+#define DIGI_PP_INPUT 0x1000 /* Change parallel port to input*/
+#define DIGI_DTR_TOGGLE 0x2000 /* Support DTR Toggle */
+#define DIGI_422 0x4000 /* for 422/232 selectable panel */
+#define DIGI_RTS_TOGGLE 0x8000 /* Support RTS Toggle */
+
+/************************************************************************
+ * These options are not supported on the comxi.
+ ************************************************************************/
+#define DIGI_COMXI (DIGI_FAST|DIGI_COOK|DSRPACE|DCDPACE|DTRPACE)
+
+#define DIGI_PLEN 28 /* String length */
+#define DIGI_TSIZ 10 /* Terminal string len */
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_t {
+ unsigned short digi_flags; /* Flags (see above) */
+ unsigned short digi_maxcps; /* Max printer CPS */
+ unsigned short digi_maxchar; /* Max chars in print queue */
+ unsigned short digi_bufsize; /* Buffer size */
+ unsigned char digi_onlen; /* Length of ON string */
+ unsigned char digi_offlen; /* Length of OFF string */
+ char digi_onstr[DIGI_PLEN]; /* Printer on string */
+ char digi_offstr[DIGI_PLEN]; /* Printer off string */
+ char digi_term[DIGI_TSIZ]; /* terminal string */
+};
+
+/************************************************************************
+ * KME definitions and structures.
+ ************************************************************************/
+#define RW_IDLE 0 /* Operation complete */
+#define RW_READ 1 /* Read Concentrator Memory */
+#define RW_WRITE 2 /* Write Concentrator Memory */
+
+struct rw_t {
+ unsigned char rw_req; /* Request type */
+ unsigned char rw_board; /* Host Adapter board number */
+ unsigned char rw_conc; /* Concentrator number */
+ unsigned char rw_reserved; /* Reserved for expansion */
+ unsigned int rw_addr; /* Address in concentrator */
+ unsigned short rw_size; /* Read/write request length */
+ unsigned char rw_data[128]; /* Data to read/write */
+};
+
+/***********************************************************************
+ * Shrink Buffer and Board Information definitions and structures.
+
+ ************************************************************************/
+ /* Board type return codes */
+#define PCXI_TYPE 1 /* Board type at the designated port is a PC/Xi */
+#define PCXM_TYPE 2 /* Board type at the designated port is a PC/Xm */
+#define PCXE_TYPE 3 /* Board type at the designated port is a PC/Xe */
+#define MCXI_TYPE 4 /* Board type at the designated port is a MC/Xi */
+#define COMXI_TYPE 5 /* Board type at the designated port is a COM/Xi */
+
+ /* Non-Zero Result codes. */
+#define RESULT_NOBDFND 1 /* A Digi product at that port is not config installed */
+#define RESULT_NODESCT 2 /* A memory descriptor was not obtainable */
+#define RESULT_NOOSSIG 3 /* FEP/OS signature was not detected on the board */
+#define RESULT_TOOSML 4 /* Too small an area to shrink. */
+#define RESULT_NOCHAN 5 /* Channel structure for the board was not found */
+
+struct shrink_buf_struct {
+ unsigned int shrink_buf_vaddr; /* Virtual address of board */
+ unsigned int shrink_buf_phys; /* Physical address of board */
+ unsigned int shrink_buf_bseg; /* Amount of board memory */
+ unsigned int shrink_buf_hseg; /* '186 Beginning of Dual-Port */
+
+ unsigned int shrink_buf_lseg; /* '186 Beginning of freed memory */
+ unsigned int shrink_buf_mseg; /* Linear address from start of
+ dual-port were freed memory
+ begins, host viewpoint. */
+
+ unsigned int shrink_buf_bdparam; /* Parameter for xxmemon and
+ xxmemoff */
+
+ unsigned int shrink_buf_reserva; /* Reserved */
+ unsigned int shrink_buf_reservb; /* Reserved */
+ unsigned int shrink_buf_reservc; /* Reserved */
+ unsigned int shrink_buf_reservd; /* Reserved */
+
+ unsigned char shrink_buf_result; /* Reason for call failing
+ Zero is Good return */
+ unsigned char shrink_buf_init; /* Non-Zero if it caused an
+ xxinit call. */
+
+ unsigned char shrink_buf_anports; /* Number of async ports */
+ unsigned char shrink_buf_snports; /* Number of sync ports */
+ unsigned char shrink_buf_type; /* Board type 1 = PC/Xi,
+ 2 = PC/Xm,
+ 3 = PC/Xe
+ 4 = MC/Xi
+ 5 = COMX/i */
+ unsigned char shrink_buf_card; /* Card number */
+
+};
+
+/************************************************************************
+ * Structure to get driver status information
+ ************************************************************************/
+struct digi_dinfo {
+ unsigned int dinfo_nboards; /* # boards configured */
+ char dinfo_reserved[12]; /* for future expansion */
+ char dinfo_version[16]; /* driver version */
+};
+
+#define DIGI_GETDD ('d'<<8) | 248 /* get driver info */
+
+/************************************************************************
+ * Structure used with ioctl commands for per-board information
+ *
+ * physsize and memsize differ when board has "windowed" memory
+ ************************************************************************/
+struct digi_info {
+ unsigned int info_bdnum; /* Board number (0 based) */
+ unsigned int info_ioport; /* io port address */
+ unsigned int info_physaddr; /* memory address */
+ unsigned int info_physsize; /* Size of host mem window */
+ unsigned int info_memsize; /* Amount of dual-port mem */
+ /* on board */
+ unsigned short info_bdtype; /* Board type */
+ unsigned short info_nports; /* number of ports */
+ char info_bdstate; /* board state */
+ char info_reserved[7]; /* for future expansion */
+};
+
+#define DIGI_GETBD ('d'<<8) | 249 /* get board info */
+
+struct digi_stat {
+ unsigned int info_chan; /* Channel number (0 based) */
+ unsigned int info_brd; /* Board number (0 based) */
+ unsigned int info_cflag; /* cflag for channel */
+ unsigned int info_iflag; /* iflag for channel */
+ unsigned int info_oflag; /* oflag for channel */
+ unsigned int info_mstat; /* mstat for channel */
+ unsigned int info_tx_data; /* tx_data for channel */
+ unsigned int info_rx_data; /* rx_data for channel */
+ unsigned int info_hflow; /* hflow for channel */
+ unsigned int info_reserved[8]; /* for future expansion */
+};
+
+#define DIGI_GETSTAT ('d'<<8) | 244 /* get board info */
+/************************************************************************
+ *
+ * Structure used with ioctl commands for per-channel information
+ *
+ ************************************************************************/
+struct digi_ch {
+ unsigned int info_bdnum; /* Board number (0 based) */
+ unsigned int info_channel; /* Channel index number */
+ unsigned int info_ch_cflag; /* Channel cflag */
+ unsigned int info_ch_iflag; /* Channel iflag */
+ unsigned int info_ch_oflag; /* Channel oflag */
+ unsigned int info_chsize; /* Channel structure size */
+ unsigned int info_sleep_stat; /* sleep status */
+ dev_t info_dev; /* device number */
+ unsigned char info_initstate; /* Channel init state */
+ unsigned char info_running; /* Channel running state */
+ int reserved[8]; /* reserved for future use */
+};
+
+/*
+* This structure is used with the DIGI_FEPCMD ioctl to
+* tell the driver which port to send the command for.
+*/
+struct digi_cmd {
+ int cmd;
+ int word;
+ int ncmds;
+ int chan; /* channel index (zero based) */
+ int bdid; /* board index (zero based) */
+};
+
+
+struct digi_getbuffer /* Struct for holding buffer use counts */
+{
+ unsigned long tIn;
+ unsigned long tOut;
+ unsigned long rxbuf;
+ unsigned long txbuf;
+ unsigned long txdone;
+};
+
+struct digi_getcounter {
+ unsigned long norun; /* number of UART overrun errors */
+ unsigned long noflow; /* number of buffer overflow errors */
+ unsigned long nframe; /* number of framing errors */
+ unsigned long nparity; /* number of parity errors */
+ unsigned long nbreak; /* number of breaks received */
+ unsigned long rbytes; /* number of received bytes */
+ unsigned long tbytes; /* number of bytes transmitted fully */
+};
+
+/*
+* info_sleep_stat defines
+*/
+#define INFO_RUNWAIT 0x0001
+#define INFO_WOPEN 0x0002
+#define INFO_TTIOW 0x0004
+#define INFO_CH_RWAIT 0x0008
+#define INFO_CH_WEMPTY 0x0010
+#define INFO_CH_WLOW 0x0020
+#define INFO_XXBUF_BUSY 0x0040
+
+#define DIGI_GETCH ('d'<<8) | 245 /* get board info */
+
+/* Board type definitions */
+
+#define SUBTYPE 0007
+#define T_PCXI 0000
+#define T_PCXM 0001
+#define T_PCXE 0002
+#define T_PCXR 0003
+#define T_SP 0004
+#define T_SP_PLUS 0005
+# define T_HERC 0000
+# define T_HOU 0001
+# define T_LON 0002
+# define T_CHA 0003
+#define FAMILY 0070
+#define T_COMXI 0000
+#define T_PCXX 0010
+#define T_CX 0020
+#define T_EPC 0030
+#define T_PCLITE 0040
+#define T_SPXX 0050
+#define T_AVXX 0060
+#define T_DXB 0070
+#define T_A2K_4_8 0070
+#define BUSTYPE 0700
+#define T_ISABUS 0000
+#define T_MCBUS 0100
+#define T_EISABUS 0200
+#define T_PCIBUS 0400
+
+/* Board State Definitions */
+
+#define BD_RUNNING 0x0
+#define BD_REASON 0x7f
+#define BD_NOTFOUND 0x1
+#define BD_NOIOPORT 0x2
+#define BD_NOMEM 0x3
+#define BD_NOBIOS 0x4
+#define BD_NOFEP 0x5
+#define BD_FAILED 0x6
+#define BD_ALLOCATED 0x7
+#define BD_TRIBOOT 0x8
+#define BD_BADKME 0x80
+
+#define DIGI_SPOLL ('d'<<8) | 254 /* change poller rate */
+
+#define DIGI_SETCUSTOMBAUD _IOW('e', 106, int) /* Set integer baud rate */
+#define DIGI_GETCUSTOMBAUD _IOR('e', 107, int) /* Get integer baud rate */
+
+#define DIGI_REALPORT_GETBUFFERS ('e'<<8 ) | 108
+#define DIGI_REALPORT_SENDIMMEDIATE ('e'<<8 ) | 109
+#define DIGI_REALPORT_GETCOUNTERS ('e'<<8 ) | 110
+#define DIGI_REALPORT_GETEVENTS ('e'<<8 ) | 111
+
+#define EV_OPU 0x0001 //!<Output paused by client
+#define EV_OPS 0x0002 //!<Output paused by reqular sw flowctrl
+#define EV_OPX 0x0004 //!<Output paused by extra sw flowctrl
+#define EV_OPH 0x0008 //!<Output paused by hw flowctrl
+#define EV_OPT 0x0800 //!<Output paused for RTS Toggle predelay
+
+#define EV_IPU 0x0010 //!<Input paused unconditionally by user
+#define EV_IPS 0x0020 //!<Input paused by high/low water marks
+//#define EV_IPH 0x0040 //!<Input paused w/ hardware
+#define EV_IPA 0x0400 //!<Input paused by pattern alarm module
+
+#define EV_TXB 0x0040 //!<Transmit break pending
+#define EV_TXI 0x0080 //!<Transmit immediate pending
+#define EV_TXF 0x0100 //!<Transmit flowctrl char pending
+#define EV_RXB 0x0200 //!<Break received
+
+#define EV_OPALL 0x080f //!<Output pause flags
+#define EV_IPALL 0x0430 //!<Input pause flags
+
+#endif /* DIGI_H */
diff --git a/drivers/staging/dgnc/dpacompat.h b/drivers/staging/dgnc/dpacompat.h
new file mode 100644
index 00000000000..f96963b9843
--- /dev/null
+++ b/drivers/staging/dgnc/dpacompat.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2003 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ * NOTE: THIS IS A SHARED HEADER. DO NOT CHANGE CODING STYLE!!!
+ */
+
+
+/*
+ * This structure holds data needed for the intelligent <--> nonintelligent
+ * DPA translation
+ */
+ struct ni_info {
+ int board;
+ int channel;
+ int dtr;
+ int rts;
+ int cts;
+ int dsr;
+ int ri;
+ int dcd;
+ int curtx;
+ int currx;
+ unsigned short iflag;
+ unsigned short oflag;
+ unsigned short cflag;
+ unsigned short lflag;
+
+ unsigned int mstat;
+ unsigned char hflow;
+
+ unsigned char xmit_stopped;
+ unsigned char recv_stopped;
+
+ unsigned int baud;
+};
+
+#define RW_READ 1
+#define RW_WRITE 2
+#define DIGI_KME ('e'<<8) | 98 /* Read/Write Host */
+
+#define SUBTYPE 0007
+#define T_PCXI 0000
+#define T_PCXEM 0001
+#define T_PCXE 0002
+#define T_PCXR 0003
+#define T_SP 0004
+#define T_SP_PLUS 0005
+
+#define T_HERC 0000
+#define T_HOU 0001
+#define T_LON 0002
+#define T_CHA 0003
+
+#define T_NEO 0000
+#define T_NEO_EXPRESS 0001
+#define T_CLASSIC 0002
+
+#define FAMILY 0070
+#define T_COMXI 0000
+#define T_NI 0000
+#define T_PCXX 0010
+#define T_CX 0020
+#define T_EPC 0030
+#define T_PCLITE 0040
+#define T_SPXX 0050
+#define T_AVXX 0060
+#define T_DXB 0070
+#define T_A2K_4_8 0070
+
+#define BUSTYPE 0700
+#define T_ISABUS 0000
+#define T_MCBUS 0100
+#define T_EISABUS 0200
+#define T_PCIBUS 0400
+
+/* Board State Definitions */
+
+#define BD_RUNNING 0x0
+#define BD_REASON 0x7f
+#define BD_NOTFOUND 0x1
+#define BD_NOIOPORT 0x2
+#define BD_NOMEM 0x3
+#define BD_NOBIOS 0x4
+#define BD_NOFEP 0x5
+#define BD_FAILED 0x6
+#define BD_ALLOCATED 0x7
+#define BD_TRIBOOT 0x8
+#define BD_BADKME 0x80
+
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+
+/* Ioctls needed for dpa operation */
+
+#define DIGI_GETDD ('d'<<8) | 248 /* get driver info */
+#define DIGI_GETBD ('d'<<8) | 249 /* get board info */
+#define DIGI_GET_NI_INFO ('d'<<8) | 250 /* nonintelligent state snfo */
+
+/* Other special ioctls */
+#define DIGI_TIMERIRQ ('d'<<8) | 251 /* Enable/disable RS_TIMER use */
+#define DIGI_LOOPBACK ('d'<<8) | 252 /* Enable/disable UART internal loopback */
diff --git a/drivers/staging/dgrp/Kconfig b/drivers/staging/dgrp/Kconfig
new file mode 100644
index 00000000000..e4c41552923
--- /dev/null
+++ b/drivers/staging/dgrp/Kconfig
@@ -0,0 +1,9 @@
+config DGRP
+ tristate "Digi Realport driver"
+ default n
+ depends on SYSFS && TTY
+ ---help---
+ Support for Digi Realport devices. These devices allow you to
+ access remote serial ports as if they are local tty devices. This
+ will build the kernel driver, you will still need the userspace
+ component to make your Realport device work.
diff --git a/drivers/staging/dgrp/Makefile b/drivers/staging/dgrp/Makefile
new file mode 100644
index 00000000000..d9c3b88d716
--- /dev/null
+++ b/drivers/staging/dgrp/Makefile
@@ -0,0 +1,12 @@
+obj-$(CONFIG_DGRP) += dgrp.o
+
+dgrp-y := \
+ dgrp_common.o \
+ dgrp_dpa_ops.o \
+ dgrp_driver.o \
+ dgrp_mon_ops.o \
+ dgrp_net_ops.o \
+ dgrp_ports_ops.o \
+ dgrp_specproc.o \
+ dgrp_tty.o \
+ dgrp_sysfs.o
diff --git a/drivers/staging/dgrp/README b/drivers/staging/dgrp/README
new file mode 100644
index 00000000000..1d8aaee270e
--- /dev/null
+++ b/drivers/staging/dgrp/README
@@ -0,0 +1,2 @@
+The user space code to work with this driver is located at
+https://github.com/wfp5p/dgrp-utils
diff --git a/drivers/staging/dgrp/TODO b/drivers/staging/dgrp/TODO
new file mode 100644
index 00000000000..3ef2611bc6d
--- /dev/null
+++ b/drivers/staging/dgrp/TODO
@@ -0,0 +1,13 @@
+- Use configfs for config stuff. This will require changes to the
+ user space code.
+
+- dgrp_send() and dgrp_receive() could use some refactoring
+
+- Don't automatically create CHAN_MAX (64) channel array entries for
+ every device as many devices are going to have much less than 64
+ channels.
+
+- The locking needs to be checked. It seems haphazardly done in most
+ places.
+
+- Check Kconfig dependencies
diff --git a/drivers/staging/dgrp/dgrp_common.c b/drivers/staging/dgrp/dgrp_common.c
new file mode 100644
index 00000000000..9a9b45624ba
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_common.c
@@ -0,0 +1,169 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_common.c
+ *
+ * Description:
+ *
+ * Definitions of global variables and functions which are either
+ * shared by the tty, mon, and net drivers; or which cross them
+ * functionally (like the poller).
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+
+#include "dgrp_common.h"
+
+/**
+ * dgrp_carrier -- check for carrier change state and act
+ * @ch: struct ch_struct *
+ */
+void dgrp_carrier(struct ch_struct *ch)
+{
+ struct nd_struct *nd;
+
+ int virt_carrier = 0;
+ int phys_carrier = 0;
+
+ /* fix case when the tty has already closed. */
+
+ if (!ch)
+ return;
+ nd = ch->ch_nd;
+ if (!nd)
+ return;
+
+ /*
+ * If we are currently waiting to determine the status of the port,
+ * we don't yet know the state of the modem lines. As a result,
+ * we ignore state changes when we are waiting for the modem lines
+ * to be established. We know, as a result of code in dgrp_net_ops,
+ * that we will be called again immediately following the reception
+ * of the status message with the true modem status flags in it.
+ */
+ if (ch->ch_expect & RR_STATUS)
+ return;
+
+ /*
+ * If CH_HANGUP is set, we gotta keep trying to get all the processes
+ * that have the port open to close the port.
+ * So lets just keep sending a hangup every time we get here.
+ */
+ if ((ch->ch_flag & CH_HANGUP) &&
+ (ch->ch_tun.un_open_count > 0))
+ tty_hangup(ch->ch_tun.un_tty);
+
+ /*
+ * Compute the effective state of both the physical and virtual
+ * senses of carrier.
+ */
+
+ if (ch->ch_s_mlast & DM_CD)
+ phys_carrier = 1;
+
+ if ((ch->ch_s_mlast & DM_CD) ||
+ (ch->ch_digi.digi_flags & DIGI_FORCEDCD) ||
+ (ch->ch_flag & CH_CLOCAL))
+ virt_carrier = 1;
+
+ /*
+ * Test for a VIRTUAL carrier transition to HIGH.
+ *
+ * The CH_HANGUP condition is intended to prevent any action
+ * except for close. As a result, we ignore positive carrier
+ * transitions during CH_HANGUP.
+ */
+ if (((ch->ch_flag & CH_HANGUP) == 0) &&
+ ((ch->ch_flag & CH_VIRT_CD) == 0) &&
+ (virt_carrier == 1)) {
+ /*
+ * When carrier rises, wake any threads waiting
+ * for carrier in the open routine.
+ */
+ nd->nd_tx_work = 1;
+
+ if (waitqueue_active(&ch->ch_flag_wait))
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+ /*
+ * Test for a PHYSICAL transition to low, so long as we aren't
+ * currently ignoring physical transitions (which is what "virtual
+ * carrier" indicates).
+ *
+ * The transition of the virtual carrier to low really doesn't
+ * matter... it really only means "ignore carrier state", not
+ * "make pretend that carrier is there".
+ */
+ if ((virt_carrier == 0) &&
+ ((ch->ch_flag & CH_PHYS_CD) != 0) &&
+ (phys_carrier == 0)) {
+ /*
+ * When carrier drops:
+ *
+ * Do a Hard Hangup if that is called for.
+ *
+ * Drop carrier on all open units.
+ *
+ * Flush queues, waking up any task waiting in the
+ * line discipline.
+ *
+ * Send a hangup to the control terminal.
+ *
+ * Enable all select calls.
+ */
+
+ nd->nd_tx_work = 1;
+
+ ch->ch_flag &= ~(CH_LOW | CH_EMPTY | CH_DRAIN | CH_INPUT);
+
+ if (waitqueue_active(&ch->ch_flag_wait))
+ wake_up_interruptible(&ch->ch_flag_wait);
+
+ if (ch->ch_tun.un_open_count > 0)
+ tty_hangup(ch->ch_tun.un_tty);
+
+ if (ch->ch_pun.un_open_count > 0)
+ tty_hangup(ch->ch_pun.un_tty);
+ }
+
+ /*
+ * Make sure that our cached values reflect the current reality.
+ */
+ if (virt_carrier == 1)
+ ch->ch_flag |= CH_VIRT_CD;
+ else
+ ch->ch_flag &= ~CH_VIRT_CD;
+
+ if (phys_carrier == 1)
+ ch->ch_flag |= CH_PHYS_CD;
+ else
+ ch->ch_flag &= ~CH_PHYS_CD;
+
+}
diff --git a/drivers/staging/dgrp/dgrp_common.h b/drivers/staging/dgrp/dgrp_common.h
new file mode 100644
index 00000000000..23aba6c4d22
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_common.h
@@ -0,0 +1,150 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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 __DGRP_COMMON_H
+#define __DGRP_COMMON_H
+
+#define DIGI_VERSION "1.9-29"
+
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include "drp.h"
+
+#define DGRP_TTIME 100
+#define DGRP_RTIME 100
+
+/************************************************************************
+ * All global storage allocation.
+ ************************************************************************/
+
+extern int dgrp_register_cudevices; /* enable legacy cu devices */
+extern int dgrp_register_prdevices; /* enable transparent print devices */
+extern int dgrp_poll_tick; /* Poll interval - in ms */
+
+extern struct list_head nd_struct_list;
+
+struct dgrp_poll_data {
+ spinlock_t poll_lock;
+ struct timer_list timer;
+ int poll_tick;
+ ulong poll_round; /* Timer rouding factor */
+ long node_active_count;
+};
+
+extern struct dgrp_poll_data dgrp_poll_data;
+extern void dgrp_poll_handler(unsigned long arg);
+
+/* from dgrp_mon_ops.c */
+extern const struct file_operations dgrp_mon_ops;
+
+/* from dgrp_tty.c */
+extern int dgrp_tty_init(struct nd_struct *nd);
+extern void dgrp_tty_uninit(struct nd_struct *nd);
+
+/* from dgrp_ports_ops.c */
+extern const struct file_operations dgrp_ports_ops;
+
+/* from dgrp_net_ops.c */
+extern const struct file_operations dgrp_net_ops;
+
+/* from dgrp_dpa_ops.c */
+extern const struct file_operations dgrp_dpa_ops;
+extern void dgrp_dpa_data(struct nd_struct *, int, u8 *, int);
+
+/* from dgrp_sysfs.c */
+extern int dgrp_create_class_sysfs_files(void);
+extern void dgrp_remove_class_sysfs_files(void);
+
+extern void dgrp_create_node_class_sysfs_files(struct nd_struct *nd);
+extern void dgrp_remove_node_class_sysfs_files(struct nd_struct *nd);
+
+extern void dgrp_create_tty_sysfs(struct un_struct *un, struct device *c);
+extern void dgrp_remove_tty_sysfs(struct device *c);
+
+/* from dgrp_specproc.c */
+extern void dgrp_unregister_proc(void);
+extern void dgrp_register_proc(void);
+
+/*-----------------------------------------------------------------------*
+ *
+ * Declarations for common operations:
+ *
+ * (either used by more than one of net, mon, or tty,
+ * or in interrupt context (i.e. the poller))
+ *
+ *-----------------------------------------------------------------------*/
+
+void dgrp_carrier(struct ch_struct *ch);
+
+
+/*
+ * ID manipulation macros (where c1 & c2 are characters, i is
+ * a long integer, and s is a character array of at least three members
+ */
+
+static inline void ID_TO_CHAR(long i, char *s)
+{
+ s[0] = ((i & 0xff00)>>8);
+ s[1] = (i & 0xff);
+ s[2] = 0;
+}
+
+static inline long CHAR_TO_ID(char *s)
+{
+ return ((s[0] & 0xff) << 8) | (s[1] & 0xff);
+}
+
+static inline struct nd_struct *nd_struct_get(long major)
+{
+ struct nd_struct *nd;
+
+ list_for_each_entry(nd, &nd_struct_list, list) {
+ if (major == nd->nd_major)
+ return nd;
+ }
+
+ return NULL;
+}
+
+static inline int nd_struct_add(struct nd_struct *entry)
+{
+ struct nd_struct *ptr;
+
+ ptr = nd_struct_get(entry->nd_major);
+
+ if (ptr)
+ return -EBUSY;
+
+ list_add_tail(&entry->list, &nd_struct_list);
+
+ return 0;
+}
+
+static inline int nd_struct_del(struct nd_struct *entry)
+{
+ struct nd_struct *nd;
+
+ nd = nd_struct_get(entry->nd_major);
+
+ if (!nd)
+ return -ENODEV;
+
+ list_del(&nd->list);
+ return 0;
+}
+
+#endif /* __DGRP_COMMON_H */
diff --git a/drivers/staging/dgrp/dgrp_dpa_ops.c b/drivers/staging/dgrp/dgrp_dpa_ops.c
new file mode 100644
index 00000000000..69bfe309376
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_dpa_ops.c
@@ -0,0 +1,534 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_dpa_ops.c
+ *
+ * Description:
+ *
+ * Handle the file operations required for the "dpa" devices.
+ * Includes those functions required to register the "dpa" devices
+ * in "/proc".
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/tty.h>
+#include <linux/poll.h>
+#include <linux/cred.h>
+#include <linux/sched.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_dpa_open(struct inode *, struct file *);
+static int dgrp_dpa_release(struct inode *, struct file *);
+static ssize_t dgrp_dpa_read(struct file *, char __user *, size_t, loff_t *);
+static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+static unsigned int dgrp_dpa_select(struct file *, struct poll_table_struct *);
+
+const struct file_operations dgrp_dpa_ops = {
+ .owner = THIS_MODULE,
+ .read = dgrp_dpa_read,
+ .poll = dgrp_dpa_select,
+ .unlocked_ioctl = dgrp_dpa_ioctl,
+ .open = dgrp_dpa_open,
+ .release = dgrp_dpa_release,
+};
+
+struct digi_node {
+ uint nd_state; /* Node state: 1 = up, 0 = down. */
+ uint nd_chan_count; /* Number of channels found */
+ uint nd_tx_byte; /* Tx data count */
+ uint nd_rx_byte; /* RX data count */
+ u8 nd_ps_desc[MAX_DESC_LEN]; /* Description from PS */
+};
+
+#define DIGI_GETNODE (('d'<<8) | 249) /* get board info */
+
+
+struct digi_chan {
+ uint ch_port; /* Port number to get info on */
+ uint ch_open; /* 1 if open, 0 if not */
+ uint ch_txcount; /* TX data count */
+ uint ch_rxcount; /* RX data count */
+ uint ch_s_brate; /* Realport BRATE */
+ uint ch_s_estat; /* Realport ELAST */
+ uint ch_s_cflag; /* Realport CFLAG */
+ uint ch_s_iflag; /* Realport IFLAG */
+ uint ch_s_oflag; /* Realport OFLAG */
+ uint ch_s_xflag; /* Realport XFLAG */
+ uint ch_s_mstat; /* Realport MLAST */
+};
+
+#define DIGI_GETCHAN (('d'<<8) | 248) /* get channel info */
+
+
+struct digi_vpd {
+ int vpd_len;
+ char vpd_data[VPDSIZE];
+};
+
+#define DIGI_GETVPD (('d'<<8) | 246) /* get VPD info */
+
+
+struct digi_debug {
+ int onoff;
+ int port;
+};
+
+#define DIGI_SETDEBUG (('d'<<8) | 247) /* set debug info */
+
+
+/*
+ * dgrp_dpa_open -- open the DPA device for a particular PortServer
+ */
+static int dgrp_dpa_open(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ int rtn = 0;
+
+ rtn = try_module_get(THIS_MODULE);
+ if (!rtn)
+ return -ENXIO;
+
+ rtn = 0;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ rtn = -EPERM;
+ goto done;
+ }
+
+ /*
+ * Make sure that the "private_data" field hasn't already been used.
+ */
+ if (file->private_data) {
+ rtn = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * Get the node pointer, and fail if it doesn't exist.
+ */
+ nd = PDE_DATA(inode);
+ if (!nd) {
+ rtn = -ENXIO;
+ goto done;
+ }
+
+ file->private_data = (void *) nd;
+
+ /*
+ * Allocate the DPA buffer.
+ */
+
+ if (nd->nd_dpa_buf) {
+ rtn = -EBUSY;
+ } else {
+ nd->nd_dpa_buf = kmalloc(DPA_MAX, GFP_KERNEL);
+
+ if (!nd->nd_dpa_buf) {
+ rtn = -ENOMEM;
+ } else {
+ nd->nd_dpa_out = 0;
+ nd->nd_dpa_in = 0;
+ nd->nd_dpa_lbolt = jiffies;
+ }
+ }
+
+done:
+
+ if (rtn)
+ module_put(THIS_MODULE);
+ return rtn;
+}
+
+/*
+ * dgrp_dpa_release -- close the DPA device for a particular PortServer
+ */
+static int dgrp_dpa_release(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ u8 *buf;
+ unsigned long lock_flags;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ goto done;
+
+ /*
+ * Free the dpa buffer.
+ */
+
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+ buf = nd->nd_dpa_buf;
+
+ nd->nd_dpa_buf = NULL;
+ nd->nd_dpa_out = nd->nd_dpa_in;
+
+ /*
+ * Wakeup any thread waiting for buffer space.
+ */
+
+ if (nd->nd_dpa_flag & DPA_WAIT_SPACE) {
+ nd->nd_dpa_flag &= ~DPA_WAIT_SPACE;
+ wake_up_interruptible(&nd->nd_dpa_wqueue);
+ }
+
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+
+ kfree(buf);
+
+done:
+ module_put(THIS_MODULE);
+ file->private_data = NULL;
+ return 0;
+}
+
+/*
+ * dgrp_dpa_read
+ *
+ * Copy data from the monitoring buffer to the user, freeing space
+ * in the monitoring buffer for more messages
+ */
+static ssize_t dgrp_dpa_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct nd_struct *nd;
+ int n;
+ int r;
+ int offset = 0;
+ int res = 0;
+ ssize_t rtn;
+ unsigned long lock_flags;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ return -ENXIO;
+
+ /*
+ * Wait for some data to appear in the buffer.
+ */
+
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+ for (;;) {
+ n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK;
+
+ if (n != 0)
+ break;
+
+ nd->nd_dpa_flag |= DPA_WAIT_DATA;
+
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+
+ /*
+ * Go to sleep waiting until the condition becomes true.
+ */
+ rtn = wait_event_interruptible(nd->nd_dpa_wqueue,
+ ((nd->nd_dpa_flag & DPA_WAIT_DATA) == 0));
+
+ if (rtn)
+ return rtn;
+
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+ }
+
+ /*
+ * Read whatever is there.
+ */
+
+ if (n > count)
+ n = count;
+
+ res = n;
+
+ r = DPA_MAX - nd->nd_dpa_out;
+
+ if (r <= n) {
+
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+ rtn = copy_to_user((void __user *)buf,
+ nd->nd_dpa_buf + nd->nd_dpa_out, r);
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+ if (rtn) {
+ rtn = -EFAULT;
+ goto done;
+ }
+
+ nd->nd_dpa_out = 0;
+ n -= r;
+ offset = r;
+ }
+
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+ rtn = copy_to_user((void __user *)buf + offset,
+ nd->nd_dpa_buf + nd->nd_dpa_out, n);
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+ if (rtn) {
+ rtn = -EFAULT;
+ goto done;
+ }
+
+ nd->nd_dpa_out += n;
+
+ *ppos += res;
+
+ rtn = res;
+
+ /*
+ * Wakeup any thread waiting for buffer space.
+ */
+
+ n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK;
+
+ if (nd->nd_dpa_flag & DPA_WAIT_SPACE &&
+ (DPA_MAX - n) > DPA_HIGH_WATER) {
+ nd->nd_dpa_flag &= ~DPA_WAIT_SPACE;
+ wake_up_interruptible(&nd->nd_dpa_wqueue);
+ }
+
+ done:
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+ return rtn;
+}
+
+static unsigned int dgrp_dpa_select(struct file *file,
+ struct poll_table_struct *table)
+{
+ unsigned int retval = 0;
+ struct nd_struct *nd = file->private_data;
+
+ if (nd->nd_dpa_out != nd->nd_dpa_in)
+ retval |= POLLIN | POLLRDNORM; /* Conditionally readable */
+
+ retval |= POLLOUT | POLLWRNORM; /* Always writeable */
+
+ return retval;
+}
+
+static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+
+ struct nd_struct *nd;
+ struct digi_chan getchan;
+ struct digi_node getnode;
+ struct ch_struct *ch;
+ struct digi_debug setdebug;
+ struct digi_vpd vpd;
+ unsigned int port;
+ void __user *uarg = (void __user *) arg;
+
+ nd = file->private_data;
+
+ switch (cmd) {
+ case DIGI_GETCHAN:
+ if (copy_from_user(&getchan, uarg, sizeof(struct digi_chan)))
+ return -EFAULT;
+
+ port = getchan.ch_port;
+
+ if (port > nd->nd_chan_count)
+ return -EINVAL;
+
+ ch = nd->nd_chan + port;
+
+ getchan.ch_open = (ch->ch_open_count > 0) ? 1 : 0;
+ getchan.ch_txcount = ch->ch_txcount;
+ getchan.ch_rxcount = ch->ch_rxcount;
+ getchan.ch_s_brate = ch->ch_s_brate;
+ getchan.ch_s_estat = ch->ch_s_elast;
+ getchan.ch_s_cflag = ch->ch_s_cflag;
+ getchan.ch_s_iflag = ch->ch_s_iflag;
+ getchan.ch_s_oflag = ch->ch_s_oflag;
+ getchan.ch_s_xflag = ch->ch_s_xflag;
+ getchan.ch_s_mstat = ch->ch_s_mlast;
+
+ if (copy_to_user(uarg, &getchan, sizeof(struct digi_chan)))
+ return -EFAULT;
+ break;
+
+
+ case DIGI_GETNODE:
+ getnode.nd_state = (nd->nd_state & NS_READY) ? 1 : 0;
+ getnode.nd_chan_count = nd->nd_chan_count;
+ getnode.nd_tx_byte = nd->nd_tx_byte;
+ getnode.nd_rx_byte = nd->nd_rx_byte;
+
+ memset(&getnode.nd_ps_desc, 0, MAX_DESC_LEN);
+ strlcpy(getnode.nd_ps_desc, nd->nd_ps_desc, MAX_DESC_LEN);
+
+ if (copy_to_user(uarg, &getnode, sizeof(struct digi_node)))
+ return -EFAULT;
+ break;
+
+
+ case DIGI_SETDEBUG:
+ if (copy_from_user(&setdebug, uarg, sizeof(struct digi_debug)))
+ return -EFAULT;
+
+ nd->nd_dpa_debug = setdebug.onoff;
+ nd->nd_dpa_port = setdebug.port;
+ break;
+
+
+ case DIGI_GETVPD:
+ memset(&vpd, 0, sizeof(vpd));
+ if (nd->nd_vpd_len > 0) {
+ vpd.vpd_len = nd->nd_vpd_len;
+ memcpy(&vpd.vpd_data, &nd->nd_vpd, nd->nd_vpd_len);
+ } else {
+ vpd.vpd_len = 0;
+ }
+
+ if (copy_to_user(uarg, &vpd, sizeof(struct digi_vpd)))
+ return -EFAULT;
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * dgrp_dpa() -- send data to the device monitor queue
+ * @nd: pointer to a node structure
+ * @buf: buffer of data to copy to the monitoring buffer
+ * @len: number of bytes to transfer to the buffer
+ *
+ * Called by the net device routines to send data to the device
+ * monitor queue. If the device monitor buffer is too full to
+ * accept the data, it waits until the buffer is ready.
+ */
+static void dgrp_dpa(struct nd_struct *nd, u8 *buf, int nbuf)
+{
+ int n;
+ int r;
+ unsigned long lock_flags;
+
+ /*
+ * Grab DPA lock.
+ */
+ spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags);
+
+ /*
+ * Loop while data remains.
+ */
+ while (nbuf > 0 && nd->nd_dpa_buf != NULL) {
+
+ n = (nd->nd_dpa_out - nd->nd_dpa_in - 1) & DPA_MASK;
+
+ /*
+ * Enforce flow control on the DPA device.
+ */
+ if (n < (DPA_MAX - DPA_HIGH_WATER))
+ nd->nd_dpa_flag |= DPA_WAIT_SPACE;
+
+ /*
+ * This should never happen, as the flow control above
+ * should have stopped things before they got to this point.
+ */
+ if (n == 0) {
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+ return;
+ }
+
+ /*
+ * Copy as much data as will fit.
+ */
+
+ if (n > nbuf)
+ n = nbuf;
+
+ r = DPA_MAX - nd->nd_dpa_in;
+
+ if (r <= n) {
+ memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, r);
+
+ n -= r;
+
+ nd->nd_dpa_in = 0;
+
+ buf += r;
+ nbuf -= r;
+ }
+
+ memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, n);
+
+ nd->nd_dpa_in += n;
+
+ buf += n;
+ nbuf -= n;
+
+ if (nd->nd_dpa_in >= DPA_MAX)
+ pr_info_ratelimited("%s - nd->nd_dpa_in (%i) >= DPA_MAX\n",
+ __func__, nd->nd_dpa_in);
+
+ /*
+ * Wakeup any thread waiting for data
+ */
+ if (nd->nd_dpa_flag & DPA_WAIT_DATA) {
+ nd->nd_dpa_flag &= ~DPA_WAIT_DATA;
+ wake_up_interruptible(&nd->nd_dpa_wqueue);
+ }
+ }
+
+ /*
+ * Release the DPA lock.
+ */
+ spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags);
+}
+
+/**
+ * dgrp_monitor_data() -- builds a DPA data packet
+ * @nd: pointer to a node structure
+ * @type: type of message to be logged in the DPA buffer
+ * @buf: buffer of data to be logged in the DPA buffer
+ * @size -- number of bytes in the "buf" buffer
+ */
+void dgrp_dpa_data(struct nd_struct *nd, int type, u8 *buf, int size)
+{
+ u8 header[5];
+
+ header[0] = type;
+
+ put_unaligned_be32(size, header + 1);
+
+ dgrp_dpa(nd, header, sizeof(header));
+ dgrp_dpa(nd, buf, size);
+}
diff --git a/drivers/staging/dgrp/dgrp_driver.c b/drivers/staging/dgrp/dgrp_driver.c
new file mode 100644
index 00000000000..b60a8da6350
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_driver.c
@@ -0,0 +1,105 @@
+/*
+ *
+ * Copyright 1999-2003 Digi International (www.digi.com)
+ * Jeff Randall
+ * James Puzzo <jamesp at digi dot com>
+ * Scott Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ * Driver specific includes
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+
+/*
+ * PortServer includes
+ */
+#include "dgrp_common.h"
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Digi International, http://www.digi.com");
+MODULE_DESCRIPTION("RealPort driver for Digi's ethernet-based serial connectivity product line");
+MODULE_VERSION(DIGI_VERSION);
+
+struct list_head nd_struct_list;
+struct dgrp_poll_data dgrp_poll_data;
+
+int dgrp_register_cudevices = 1;/* Turn on/off registering legacy cu devices */
+int dgrp_register_prdevices = 1;/* Turn on/off registering transparent print */
+int dgrp_poll_tick = 20; /* Poll interval - in ms */
+
+module_param_named(register_cudevices, dgrp_register_cudevices, int, 0644);
+MODULE_PARM_DESC(register_cudevices, "Turn on/off registering legacy cu devices");
+
+module_param_named(register_prdevices, dgrp_register_prdevices, int, 0644);
+MODULE_PARM_DESC(register_prdevices, "Turn on/off registering transparent print devices");
+
+module_param_named(pollrate, dgrp_poll_tick, int, 0644);
+MODULE_PARM_DESC(pollrate, "Poll interval in ms");
+
+/*
+ * init_module()
+ *
+ * Module load. This is where it all starts.
+ */
+static int __init dgrp_init_module(void)
+{
+ int ret;
+
+ INIT_LIST_HEAD(&nd_struct_list);
+
+ spin_lock_init(&dgrp_poll_data.poll_lock);
+ init_timer(&dgrp_poll_data.timer);
+ dgrp_poll_data.poll_tick = dgrp_poll_tick;
+ dgrp_poll_data.timer.function = dgrp_poll_handler;
+ dgrp_poll_data.timer.data = (unsigned long) &dgrp_poll_data;
+
+ ret = dgrp_create_class_sysfs_files();
+ if (ret)
+ return ret;
+
+ dgrp_register_proc();
+
+ return 0;
+}
+
+
+/*
+ * Module unload. This is where it all ends.
+ */
+static void __exit dgrp_cleanup_module(void)
+{
+ struct nd_struct *nd, *next;
+
+ /*
+ * Attempting to free resources in backwards
+ * order of allocation, in case that helps
+ * memory pool fragmentation.
+ */
+ dgrp_unregister_proc();
+
+ dgrp_remove_class_sysfs_files();
+
+
+ list_for_each_entry_safe(nd, next, &nd_struct_list, list) {
+ dgrp_tty_uninit(nd);
+ kfree(nd);
+ }
+}
+
+module_init(dgrp_init_module);
+module_exit(dgrp_cleanup_module);
diff --git a/drivers/staging/dgrp/dgrp_mon_ops.c b/drivers/staging/dgrp/dgrp_mon_ops.c
new file mode 100644
index 00000000000..d18be4180e3
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_mon_ops.c
@@ -0,0 +1,327 @@
+/*****************************************************************************
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_mon_ops.c
+ *
+ * Description:
+ *
+ * Handle the file operations required for the "monitor" devices.
+ * Includes those functions required to register the "mon" devices
+ * in "/proc".
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <asm/unaligned.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_mon_open(struct inode *, struct file *);
+static int dgrp_mon_release(struct inode *, struct file *);
+static ssize_t dgrp_mon_read(struct file *, char __user *, size_t, loff_t *);
+static long dgrp_mon_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+
+const struct file_operations dgrp_mon_ops = {
+ .owner = THIS_MODULE,
+ .read = dgrp_mon_read,
+ .unlocked_ioctl = dgrp_mon_ioctl,
+ .open = dgrp_mon_open,
+ .release = dgrp_mon_release,
+};
+
+/**
+ * dgrp_mon_open() -- open /proc/dgrp/ports device for a PortServer
+ * @inode: struct inode *
+ * @file: struct file *
+ *
+ * Open function to open the /proc/dgrp/ports device for a PortServer.
+ */
+static int dgrp_mon_open(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ struct timeval tv;
+ uint32_t time;
+ u8 *buf;
+ int rtn;
+
+ rtn = try_module_get(THIS_MODULE);
+ if (!rtn)
+ return -ENXIO;
+
+ rtn = 0;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ rtn = -EPERM;
+ goto done;
+ }
+
+ /*
+ * Make sure that the "private_data" field hasn't already been used.
+ */
+ if (file->private_data) {
+ rtn = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * Get the node pointer, and fail if it doesn't exist.
+ */
+ nd = PDE_DATA(inode);
+ if (!nd) {
+ rtn = -ENXIO;
+ goto done;
+ }
+
+ file->private_data = (void *) nd;
+
+ /*
+ * Allocate the monitor buffer.
+ */
+
+ /*
+ * Grab the MON lock.
+ */
+ down(&nd->nd_mon_semaphore);
+
+ if (nd->nd_mon_buf) {
+ rtn = -EBUSY;
+ goto done_up;
+ }
+
+ nd->nd_mon_buf = kmalloc(MON_MAX, GFP_KERNEL);
+
+ if (!nd->nd_mon_buf) {
+ rtn = -ENOMEM;
+ goto done_up;
+ }
+
+ /*
+ * Enter an RPDUMP file header into the buffer.
+ */
+
+ buf = nd->nd_mon_buf;
+
+ strcpy(buf, RPDUMP_MAGIC);
+ buf += strlen(buf) + 1;
+
+ do_gettimeofday(&tv);
+
+ /*
+ * tv.tv_sec might be a 64 bit quantity. Pare
+ * it down to 32 bits before attempting to encode
+ * it.
+ */
+ time = (uint32_t) (tv.tv_sec & 0xffffffff);
+
+ put_unaligned_be32(time, buf);
+ put_unaligned_be16(0, buf + 4);
+ buf += 6;
+
+ if (nd->nd_tx_module) {
+ buf[0] = RPDUMP_CLIENT;
+ put_unaligned_be32(0, buf + 1);
+ put_unaligned_be16(1, buf + 5);
+ buf[7] = 0xf0 + nd->nd_tx_module;
+ buf += 8;
+ }
+
+ if (nd->nd_rx_module) {
+ buf[0] = RPDUMP_SERVER;
+ put_unaligned_be32(0, buf + 1);
+ put_unaligned_be16(1, buf + 5);
+ buf[7] = 0xf0 + nd->nd_rx_module;
+ buf += 8;
+ }
+
+ nd->nd_mon_out = 0;
+ nd->nd_mon_in = buf - nd->nd_mon_buf;
+ nd->nd_mon_lbolt = jiffies;
+
+done_up:
+ up(&nd->nd_mon_semaphore);
+
+done:
+ if (rtn)
+ module_put(THIS_MODULE);
+ return rtn;
+}
+
+
+/**
+ * dgrp_mon_release() - Close the MON device for a particular PortServer
+ * @inode: struct inode *
+ * @file: struct file *
+ */
+static int dgrp_mon_release(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ goto done;
+
+ /*
+ * Free the monitor buffer.
+ */
+
+ down(&nd->nd_mon_semaphore);
+
+ kfree(nd->nd_mon_buf);
+ nd->nd_mon_buf = NULL;
+ nd->nd_mon_out = nd->nd_mon_in;
+
+ /*
+ * Wakeup any thread waiting for buffer space.
+ */
+
+ if (nd->nd_mon_flag & MON_WAIT_SPACE) {
+ nd->nd_mon_flag &= ~MON_WAIT_SPACE;
+ wake_up_interruptible(&nd->nd_mon_wqueue);
+ }
+
+ up(&nd->nd_mon_semaphore);
+
+ /*
+ * Make sure there is no thread in the middle of writing a packet.
+ */
+ down(&nd->nd_net_semaphore);
+ up(&nd->nd_net_semaphore);
+
+done:
+ module_put(THIS_MODULE);
+ file->private_data = NULL;
+ return 0;
+}
+
+/**
+ * dgrp_mon_read() -- Copy data from the monitoring buffer to the user
+ */
+static ssize_t dgrp_mon_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct nd_struct *nd;
+ int r;
+ int offset = 0;
+ int res = 0;
+ ssize_t rtn;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ return -ENXIO;
+
+ /*
+ * Wait for some data to appear in the buffer.
+ */
+
+ down(&nd->nd_mon_semaphore);
+
+ for (;;) {
+ res = (nd->nd_mon_in - nd->nd_mon_out) & MON_MASK;
+
+ if (res)
+ break;
+
+ nd->nd_mon_flag |= MON_WAIT_DATA;
+
+ up(&nd->nd_mon_semaphore);
+
+ /*
+ * Go to sleep waiting until the condition becomes true.
+ */
+ rtn = wait_event_interruptible(nd->nd_mon_wqueue,
+ ((nd->nd_mon_flag & MON_WAIT_DATA) == 0));
+
+ if (rtn)
+ return rtn;
+
+ down(&nd->nd_mon_semaphore);
+ }
+
+ /*
+ * Read whatever is there.
+ */
+
+ if (res > count)
+ res = count;
+
+ r = MON_MAX - nd->nd_mon_out;
+
+ if (r <= res) {
+ rtn = copy_to_user((void __user *)buf,
+ nd->nd_mon_buf + nd->nd_mon_out, r);
+ if (rtn) {
+ up(&nd->nd_mon_semaphore);
+ return -EFAULT;
+ }
+
+ nd->nd_mon_out = 0;
+ res -= r;
+ offset = r;
+ }
+
+ rtn = copy_to_user((void __user *) buf + offset,
+ nd->nd_mon_buf + nd->nd_mon_out, res);
+ if (rtn) {
+ up(&nd->nd_mon_semaphore);
+ return -EFAULT;
+ }
+
+ nd->nd_mon_out += res;
+
+ *ppos += res;
+
+ up(&nd->nd_mon_semaphore);
+
+ /*
+ * Wakeup any thread waiting for buffer space.
+ */
+
+ if (nd->nd_mon_flag & MON_WAIT_SPACE) {
+ nd->nd_mon_flag &= ~MON_WAIT_SPACE;
+ wake_up_interruptible(&nd->nd_mon_wqueue);
+ }
+
+ return res;
+}
+
+/* ioctl is not valid on monitor device */
+static long dgrp_mon_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return -EINVAL;
+}
diff --git a/drivers/staging/dgrp/dgrp_net_ops.c b/drivers/staging/dgrp/dgrp_net_ops.c
new file mode 100644
index 00000000000..33ac7fb88cb
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_net_ops.c
@@ -0,0 +1,3666 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_net_ops.c
+ *
+ * Description:
+ *
+ * Handle the file operations required for the "network" devices.
+ * Includes those functions required to register the "net" devices
+ * in "/proc".
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/spinlock.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/ratelimit.h>
+#include <asm/unaligned.h>
+
+#define MYFLIPLEN TBUF_MAX
+
+#include "dgrp_common.h"
+
+#define TTY_FLIPBUF_SIZE 512
+#define DEVICE_NAME_SIZE 50
+
+/*
+ * Generic helper function declarations
+ */
+static void parity_scan(struct ch_struct *ch, unsigned char *cbuf,
+ unsigned char *fbuf, int *len);
+
+/*
+ * File operation declarations
+ */
+static int dgrp_net_open(struct inode *, struct file *);
+static int dgrp_net_release(struct inode *, struct file *);
+static ssize_t dgrp_net_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t dgrp_net_write(struct file *, const char __user *, size_t,
+ loff_t *);
+static long dgrp_net_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+static unsigned int dgrp_net_select(struct file *file,
+ struct poll_table_struct *table);
+
+const struct file_operations dgrp_net_ops = {
+ .owner = THIS_MODULE,
+ .read = dgrp_net_read,
+ .write = dgrp_net_write,
+ .poll = dgrp_net_select,
+ .unlocked_ioctl = dgrp_net_ioctl,
+ .open = dgrp_net_open,
+ .release = dgrp_net_release,
+};
+
+/**
+ * dgrp_dump() -- prints memory for debugging purposes.
+ * @mem: Memory location which should be printed to the console
+ * @len: Number of bytes to be dumped
+ */
+static void dgrp_dump(u8 *mem, int len)
+{
+ int i;
+
+ pr_debug("dgrp dump length = %d, data = ", len);
+ for (i = 0; i < len; ++i)
+ pr_debug("%.2x ", mem[i]);
+ pr_debug("\n");
+}
+
+/**
+ * dgrp_read_data_block() -- Read a data block
+ * @ch: struct ch_struct *
+ * @flipbuf: u8 *
+ * @flipbuf_size: size of flipbuf
+ */
+static void dgrp_read_data_block(struct ch_struct *ch, u8 *flipbuf,
+ int flipbuf_size)
+{
+ int t;
+ int n;
+
+ if (flipbuf_size <= 0)
+ return;
+
+ t = RBUF_MAX - ch->ch_rout;
+ n = flipbuf_size;
+
+ if (n >= t) {
+ memcpy(flipbuf, ch->ch_rbuf + ch->ch_rout, t);
+ flipbuf += t;
+ n -= t;
+ ch->ch_rout = 0;
+ }
+
+ memcpy(flipbuf, ch->ch_rbuf + ch->ch_rout, n);
+ flipbuf += n;
+ ch->ch_rout += n;
+}
+
+
+/**
+ * dgrp_input() -- send data to the line disipline
+ * @ch: pointer to channel struct
+ *
+ * Copys the rbuf to the flipbuf and sends to line discipline.
+ * Sends input buffer data to the line discipline.
+ *
+ */
+static void dgrp_input(struct ch_struct *ch)
+{
+ struct nd_struct *nd;
+ struct tty_struct *tty;
+ int data_len;
+ int len;
+ int tty_count;
+ ulong lock_flags;
+ u8 *myflipbuf;
+ u8 *myflipflagbuf;
+
+ if (!ch)
+ return;
+
+ nd = ch->ch_nd;
+
+ if (!nd)
+ return;
+
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+ myflipbuf = nd->nd_inputbuf;
+ myflipflagbuf = nd->nd_inputflagbuf;
+
+ if (!ch->ch_open_count) {
+ ch->ch_rout = ch->ch_rin;
+ goto out;
+ }
+
+ if (ch->ch_tun.un_flag & UN_CLOSING) {
+ ch->ch_rout = ch->ch_rin;
+ goto out;
+ }
+
+ tty = (ch->ch_tun).un_tty;
+
+
+ if (!tty || tty->magic != TTY_MAGIC) {
+ ch->ch_rout = ch->ch_rin;
+ goto out;
+ }
+
+ tty_count = tty->count;
+ if (!tty_count) {
+ ch->ch_rout = ch->ch_rin;
+ goto out;
+ }
+
+ if (tty->closing || test_bit(TTY_CLOSING, &tty->flags)) {
+ ch->ch_rout = ch->ch_rin;
+ goto out;
+ }
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+ /* data_len should be the number of chars that we read in */
+ data_len = (ch->ch_rin - ch->ch_rout) & RBUF_MASK;
+
+ /* len is the amount of data we are going to transfer here */
+ len = tty_buffer_request_room(&ch->port, data_len);
+
+ /* Check DPA flow control */
+ if ((nd->nd_dpa_debug) &&
+ (nd->nd_dpa_flag & DPA_WAIT_SPACE) &&
+ (nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))))
+ len = 0;
+
+ if ((len) && !(ch->ch_flag & CH_RXSTOP)) {
+
+ dgrp_read_data_block(ch, myflipbuf, len);
+
+ if (I_PARMRK(tty) || I_BRKINT(tty) || I_INPCK(tty))
+ parity_scan(ch, myflipbuf, myflipflagbuf, &len);
+ else
+ memset(myflipflagbuf, TTY_NORMAL, len);
+
+ if ((nd->nd_dpa_debug) &&
+ (nd->nd_dpa_port == PORT_NUM(MINOR(tty_devnum(tty)))))
+ dgrp_dpa_data(nd, 1, myflipbuf, len);
+
+ tty_insert_flip_string_flags(&ch->port, myflipbuf,
+ myflipflagbuf, len);
+ tty_flip_buffer_push(&ch->port);
+
+ ch->ch_rxcount += len;
+ }
+
+ /*
+ * Wake up any sleepers (maybe dgrp close) that might be waiting
+ * for a channel flag state change.
+ */
+ wake_up_interruptible(&ch->ch_flag_wait);
+ return;
+
+out:
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+}
+
+
+/*
+ * parity_scan
+ *
+ * Loop to inspect each single character or 0xFF escape.
+ *
+ * if PARMRK & ~DOSMODE:
+ * 0xFF 0xFF Normal 0xFF character, escaped
+ * to eliminate confusion.
+ * 0xFF 0x00 0x00 Break
+ * 0xFF 0x00 CC Error character CC.
+ * CC Normal character CC.
+ *
+ * if PARMRK & DOSMODE:
+ * 0xFF 0x18 0x00 Break
+ * 0xFF 0x08 0x00 Framing Error
+ * 0xFF 0x04 0x00 Parity error
+ * 0xFF 0x0C 0x00 Both Framing and Parity error
+ *
+ * TODO: do we need to do the XMODEM, XOFF, XON, XANY processing??
+ * as per protocol
+ */
+static void parity_scan(struct ch_struct *ch, unsigned char *cbuf,
+ unsigned char *fbuf, int *len)
+{
+ int l = *len;
+ int count = 0;
+ int DOS = ((ch->ch_iflag & IF_DOSMODE) == 0 ? 0 : 1);
+ unsigned char *cout; /* character buffer */
+ unsigned char *fout; /* flag buffer */
+ unsigned char *in;
+ unsigned char c;
+
+ in = cbuf;
+ cout = cbuf;
+ fout = fbuf;
+
+ while (l--) {
+ c = *in;
+ in++;
+
+ switch (ch->ch_pscan_state) {
+ default:
+ /* reset to sanity and fall through */
+ ch->ch_pscan_state = 0;
+
+ case 0:
+ /* No FF seen yet */
+ if (c == 0xff) /* delete this character from stream */
+ ch->ch_pscan_state = 1;
+ else {
+ *cout++ = c;
+ *fout++ = TTY_NORMAL;
+ count += 1;
+ }
+ break;
+
+ case 1:
+ /* first FF seen */
+ if (c == 0xff) {
+ /* doubled ff, transform to single ff */
+ *cout++ = c;
+ *fout++ = TTY_NORMAL;
+ count += 1;
+ ch->ch_pscan_state = 0;
+ } else {
+ /* save value examination in next state */
+ ch->ch_pscan_savechar = c;
+ ch->ch_pscan_state = 2;
+ }
+ break;
+
+ case 2:
+ /* third character of ff sequence */
+ *cout++ = c;
+ if (DOS) {
+ if (ch->ch_pscan_savechar & 0x10)
+ *fout++ = TTY_BREAK;
+ else if (ch->ch_pscan_savechar & 0x08)
+ *fout++ = TTY_FRAME;
+ else
+ /*
+ * either marked as a parity error,
+ * indeterminate, or not in DOSMODE
+ * call it a parity error
+ */
+ *fout++ = TTY_PARITY;
+ } else {
+ /* case FF XX ?? where XX is not 00 */
+ if (ch->ch_pscan_savechar & 0xff) {
+ /* this should not happen */
+ pr_info("%s: parity_scan: error unexpected byte\n",
+ __func__);
+ *fout++ = TTY_PARITY;
+ }
+ /* case FF 00 XX where XX is not 00 */
+ else if (c == 0xff)
+ *fout++ = TTY_PARITY;
+ /* case FF 00 00 */
+ else
+ *fout++ = TTY_BREAK;
+
+ }
+ count += 1;
+ ch->ch_pscan_state = 0;
+ }
+ }
+ *len = count;
+}
+
+
+/**
+ * dgrp_net_idle() -- Idle the network connection
+ * @nd: pointer to node structure to idle
+ */
+static void dgrp_net_idle(struct nd_struct *nd)
+{
+ struct ch_struct *ch;
+ int i;
+
+ nd->nd_tx_work = 1;
+
+ nd->nd_state = NS_IDLE;
+ nd->nd_flag = 0;
+
+ for (i = nd->nd_seq_out; ; i = (i + 1) & SEQ_MASK) {
+ if (!nd->nd_seq_wait[i]) {
+ nd->nd_seq_wait[i] = 0;
+ wake_up_interruptible(&nd->nd_seq_wque[i]);
+ }
+
+ if (i == nd->nd_seq_in)
+ break;
+ }
+
+ nd->nd_seq_out = nd->nd_seq_in;
+
+ nd->nd_unack = 0;
+ nd->nd_remain = 0;
+
+ nd->nd_tx_module = 0x10;
+ nd->nd_rx_module = 0x00;
+
+ for (i = 0, ch = nd->nd_chan; i < CHAN_MAX; i++, ch++) {
+ ch->ch_state = CS_IDLE;
+
+ ch->ch_otype = 0;
+ ch->ch_otype_waiting = 0;
+ }
+}
+
+/*
+ * Increase the number of channels, waking up any
+ * threads that might be waiting for the channels
+ * to appear.
+ */
+static void increase_channel_count(struct nd_struct *nd, int n)
+{
+ struct ch_struct *ch;
+ struct device *classp;
+ char name[DEVICE_NAME_SIZE];
+ int ret;
+ u8 *buf;
+ int i;
+
+ for (i = nd->nd_chan_count; i < n; ++i) {
+ ch = nd->nd_chan + i;
+
+ /* FIXME: return a useful error instead! */
+ buf = kmalloc(TBUF_MAX, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ if (ch->ch_tbuf)
+ pr_info_ratelimited("%s - ch_tbuf was not NULL\n",
+ __func__);
+
+ ch->ch_tbuf = buf;
+
+ buf = kmalloc(RBUF_MAX, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ if (ch->ch_rbuf)
+ pr_info("%s - ch_rbuf was not NULL\n",
+ __func__);
+ ch->ch_rbuf = buf;
+
+ classp = tty_port_register_device(&ch->port,
+ nd->nd_serial_ttdriver, i,
+ NULL);
+
+ ch->ch_tun.un_sysfs = classp;
+ snprintf(name, DEVICE_NAME_SIZE, "tty_%d", i);
+
+ dgrp_create_tty_sysfs(&ch->ch_tun, classp);
+ ret = sysfs_create_link(&nd->nd_class_dev->kobj,
+ &classp->kobj, name);
+
+ /* NOTE: We don't support "cu" devices anymore,
+ * so you will notice we don't register them
+ * here anymore. */
+ if (dgrp_register_prdevices) {
+ classp = tty_register_device(nd->nd_xprint_ttdriver,
+ i, NULL);
+ ch->ch_pun.un_sysfs = classp;
+ snprintf(name, DEVICE_NAME_SIZE, "pr_%d", i);
+
+ dgrp_create_tty_sysfs(&ch->ch_pun, classp);
+ ret = sysfs_create_link(&nd->nd_class_dev->kobj,
+ &classp->kobj, name);
+ }
+
+ nd->nd_chan_count = i + 1;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+}
+
+/*
+ * Decrease the number of channels, and wake up any threads that might
+ * be waiting on the channels that vanished.
+ */
+static void decrease_channel_count(struct nd_struct *nd, int n)
+{
+ struct ch_struct *ch;
+ char name[DEVICE_NAME_SIZE];
+ int i;
+
+ for (i = nd->nd_chan_count - 1; i >= n; --i) {
+ ch = nd->nd_chan + i;
+
+ /*
+ * Make any open ports inoperative.
+ */
+ ch->ch_state = CS_IDLE;
+
+ ch->ch_otype = 0;
+ ch->ch_otype_waiting = 0;
+
+ /*
+ * Only "HANGUP" if we care about carrier
+ * transitions and we are already open.
+ */
+ if (ch->ch_open_count != 0) {
+ ch->ch_flag |= CH_HANGUP;
+ dgrp_carrier(ch);
+ }
+
+ /*
+ * Unlike the CH_HANGUP flag above, use another
+ * flag to indicate to the RealPort state machine
+ * that this port has disappeared.
+ */
+ if (ch->ch_open_count != 0)
+ ch->ch_flag |= CH_PORT_GONE;
+
+ wake_up_interruptible(&ch->ch_flag_wait);
+
+ nd->nd_chan_count = i;
+
+ kfree(ch->ch_tbuf);
+ ch->ch_tbuf = NULL;
+
+ kfree(ch->ch_rbuf);
+ ch->ch_rbuf = NULL;
+
+ nd->nd_chan_count = i;
+
+ dgrp_remove_tty_sysfs(ch->ch_tun.un_sysfs);
+ snprintf(name, DEVICE_NAME_SIZE, "tty_%d", i);
+ sysfs_remove_link(&nd->nd_class_dev->kobj, name);
+ tty_unregister_device(nd->nd_serial_ttdriver, i);
+
+ /*
+ * NOTE: We don't support "cu" devices anymore, so don't
+ * unregister them here anymore.
+ */
+
+ if (dgrp_register_prdevices) {
+ dgrp_remove_tty_sysfs(ch->ch_pun.un_sysfs);
+ snprintf(name, DEVICE_NAME_SIZE, "pr_%d", i);
+ sysfs_remove_link(&nd->nd_class_dev->kobj, name);
+ tty_unregister_device(nd->nd_xprint_ttdriver, i);
+ }
+ }
+}
+
+/**
+ * dgrp_chan_count() -- Adjust the node channel count.
+ * @nd: pointer to a node structure
+ * @n: new value for channel count
+ *
+ * Adjusts the node channel count. If new ports have appeared, it tries
+ * to signal those processes that might have been waiting for ports to
+ * appear. If ports have disappeared it tries to signal those processes
+ * that might be hung waiting for a response for the now non-existant port.
+ */
+static void dgrp_chan_count(struct nd_struct *nd, int n)
+{
+ if (n == nd->nd_chan_count)
+ return;
+
+ if (n > nd->nd_chan_count)
+ increase_channel_count(nd, n);
+
+ if (n < nd->nd_chan_count)
+ decrease_channel_count(nd, n);
+}
+
+/**
+ * dgrp_monitor() -- send data to the device monitor queue
+ * @nd: pointer to a node structure
+ * @buf: data to copy to the monitoring buffer
+ * @len: number of bytes to transfer to the buffer
+ *
+ * Called by the net device routines to send data to the device
+ * monitor queue. If the device monitor buffer is too full to
+ * accept the data, it waits until the buffer is ready.
+ */
+static void dgrp_monitor(struct nd_struct *nd, u8 *buf, int len)
+{
+ int n;
+ int r;
+ int rtn;
+
+ /*
+ * Grab monitor lock.
+ */
+ down(&nd->nd_mon_semaphore);
+
+ /*
+ * Loop while data remains.
+ */
+ while ((len > 0) && (nd->nd_mon_buf)) {
+ /*
+ * Determine the amount of available space left in the
+ * buffer. If there's none, wait until some appears.
+ */
+
+ n = (nd->nd_mon_out - nd->nd_mon_in - 1) & MON_MASK;
+
+ if (!n) {
+ nd->nd_mon_flag |= MON_WAIT_SPACE;
+
+ up(&nd->nd_mon_semaphore);
+
+ /*
+ * Go to sleep waiting until the condition becomes true.
+ */
+ rtn = wait_event_interruptible(nd->nd_mon_wqueue,
+ ((nd->nd_mon_flag & MON_WAIT_SPACE) == 0));
+
+/* FIXME: really ignore rtn? */
+
+ /*
+ * We can't exit here if we receive a signal, since
+ * to do so would trash the debug stream.
+ */
+
+ down(&nd->nd_mon_semaphore);
+
+ continue;
+ }
+
+ /*
+ * Copy as much data as will fit.
+ */
+
+ if (n > len)
+ n = len;
+
+ r = MON_MAX - nd->nd_mon_in;
+
+ if (r <= n) {
+ memcpy(nd->nd_mon_buf + nd->nd_mon_in, buf, r);
+
+ n -= r;
+
+ nd->nd_mon_in = 0;
+
+ buf += r;
+ len -= r;
+ }
+
+ memcpy(nd->nd_mon_buf + nd->nd_mon_in, buf, n);
+
+ nd->nd_mon_in += n;
+
+ buf += n;
+ len -= n;
+
+ if (nd->nd_mon_in >= MON_MAX)
+ pr_info_ratelimited("%s - nd_mon_in (%i) >= MON_MAX\n",
+ __func__, nd->nd_mon_in);
+
+ /*
+ * Wakeup any thread waiting for data
+ */
+
+ if (nd->nd_mon_flag & MON_WAIT_DATA) {
+ nd->nd_mon_flag &= ~MON_WAIT_DATA;
+ wake_up_interruptible(&nd->nd_mon_wqueue);
+ }
+ }
+
+ /*
+ * Release the monitor lock.
+ */
+ up(&nd->nd_mon_semaphore);
+}
+
+/**
+ * dgrp_encode_time() -- Encodes rpdump time into a 4-byte quantity.
+ * @nd: pointer to a node structure
+ * @buf: destination buffer
+ *
+ * Encodes "rpdump" time into a 4-byte quantity. Time is measured since
+ * open.
+ */
+static void dgrp_encode_time(struct nd_struct *nd, u8 *buf)
+{
+ ulong t;
+
+ /*
+ * Convert time in HZ since open to time in milliseconds
+ * since open.
+ */
+ t = jiffies - nd->nd_mon_lbolt;
+ t = 1000 * (t / HZ) + 1000 * (t % HZ) / HZ;
+
+ put_unaligned_be32((uint)(t & 0xffffffff), buf);
+}
+
+
+
+/**
+ * dgrp_monitor_message() -- Builds a rpdump style message.
+ * @nd: pointer to a node structure
+ * @message: destination buffer
+ */
+static void dgrp_monitor_message(struct nd_struct *nd, char *message)
+{
+ u8 header[7];
+ int n;
+
+ header[0] = RPDUMP_MESSAGE;
+
+ dgrp_encode_time(nd, header + 1);
+
+ n = strlen(message);
+
+ put_unaligned_be16(n, header + 5);
+
+ dgrp_monitor(nd, header, sizeof(header));
+ dgrp_monitor(nd, (u8 *) message, n);
+}
+
+
+
+/**
+ * dgrp_monitor_reset() -- Note a reset in the monitoring buffer.
+ * @nd: pointer to a node structure
+ */
+static void dgrp_monitor_reset(struct nd_struct *nd)
+{
+ u8 header[5];
+
+ header[0] = RPDUMP_RESET;
+
+ dgrp_encode_time(nd, header + 1);
+
+ dgrp_monitor(nd, header, sizeof(header));
+}
+
+/**
+ * dgrp_monitor_data() -- builds a monitor data packet
+ * @nd: pointer to a node structure
+ * @type: type of message to be logged
+ * @buf: data to be logged
+ * @size: number of bytes in the buffer
+ */
+static void dgrp_monitor_data(struct nd_struct *nd, u8 type, u8 *buf, int size)
+{
+ u8 header[7];
+
+ header[0] = type;
+
+ dgrp_encode_time(nd, header + 1);
+
+ put_unaligned_be16(size, header + 5);
+
+ dgrp_monitor(nd, header, sizeof(header));
+ dgrp_monitor(nd, buf, size);
+}
+
+static int alloc_nd_buffers(struct nd_struct *nd)
+{
+
+ nd->nd_iobuf = NULL;
+ nd->nd_writebuf = NULL;
+ nd->nd_inputbuf = NULL;
+ nd->nd_inputflagbuf = NULL;
+
+ /*
+ * Allocate the network read/write buffer.
+ */
+ nd->nd_iobuf = kzalloc(UIO_MAX + 10, GFP_KERNEL);
+ if (!nd->nd_iobuf)
+ goto out_err;
+
+ /*
+ * Allocate a buffer for doing the copy from user space to
+ * kernel space in the write routines.
+ */
+ nd->nd_writebuf = kzalloc(WRITEBUFLEN, GFP_KERNEL);
+ if (!nd->nd_writebuf)
+ goto out_err;
+
+ /*
+ * Allocate a buffer for doing the copy from kernel space to
+ * tty buffer space in the read routines.
+ */
+ nd->nd_inputbuf = kzalloc(MYFLIPLEN, GFP_KERNEL);
+ if (!nd->nd_inputbuf)
+ goto out_err;
+
+ /*
+ * Allocate a buffer for doing the copy from kernel space to
+ * tty buffer space in the read routines.
+ */
+ nd->nd_inputflagbuf = kzalloc(MYFLIPLEN, GFP_KERNEL);
+ if (!nd->nd_inputflagbuf)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ kfree(nd->nd_iobuf);
+ kfree(nd->nd_writebuf);
+ kfree(nd->nd_inputbuf);
+ kfree(nd->nd_inputflagbuf);
+ return -ENOMEM;
+}
+
+/*
+ * dgrp_net_open() -- Open the NET device for a particular PortServer
+ */
+static int dgrp_net_open(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ ulong lock_flags;
+ int rtn;
+
+ rtn = try_module_get(THIS_MODULE);
+ if (!rtn)
+ return -EAGAIN;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ rtn = -EPERM;
+ goto done;
+ }
+
+ /*
+ * Make sure that the "private_data" field hasn't already been used.
+ */
+ if (file->private_data) {
+ rtn = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * Get the node pointer, and fail if it doesn't exist.
+ */
+ nd = PDE_DATA(inode);
+ if (!nd) {
+ rtn = -ENXIO;
+ goto done;
+ }
+
+ file->private_data = (void *) nd;
+
+ /*
+ * Grab the NET lock.
+ */
+ down(&nd->nd_net_semaphore);
+
+ if (nd->nd_state != NS_CLOSED) {
+ rtn = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * Initialize the link speed parameters.
+ */
+
+ nd->nd_link.lk_fast_rate = UIO_MAX;
+ nd->nd_link.lk_slow_rate = UIO_MAX;
+
+ nd->nd_link.lk_fast_delay = 1000;
+ nd->nd_link.lk_slow_delay = 1000;
+
+ nd->nd_link.lk_header_size = 46;
+
+
+ rtn = alloc_nd_buffers(nd);
+ if (rtn)
+ goto unlock;
+
+ /*
+ * The port is now open, so move it to the IDLE state
+ */
+ dgrp_net_idle(nd);
+
+ nd->nd_tx_time = jiffies;
+
+ /*
+ * If the polling routing is not running, start it running here
+ */
+ spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+ if (!dgrp_poll_data.node_active_count) {
+ dgrp_poll_data.node_active_count = 2;
+ dgrp_poll_data.timer.expires = jiffies +
+ dgrp_poll_tick * HZ / 1000;
+ add_timer(&dgrp_poll_data.timer);
+ }
+
+ spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+ dgrp_monitor_message(nd, "Net Open");
+
+unlock:
+ /*
+ * Release the NET lock.
+ */
+ up(&nd->nd_net_semaphore);
+
+done:
+ if (rtn)
+ module_put(THIS_MODULE);
+
+ return rtn;
+}
+
+/* dgrp_net_release() -- close the NET device for a particular PortServer */
+static int dgrp_net_release(struct inode *inode, struct file *file)
+{
+ struct nd_struct *nd;
+ ulong lock_flags;
+
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ goto done;
+
+/* TODO : historical locking placeholder */
+/*
+ * In the HPUX version of the RealPort driver (which served as a basis
+ * for this driver) this locking code was used. Saved if ever we need
+ * to review the locking under Linux.
+ */
+/* spinlock(&nd->nd_lock); */
+
+
+ /*
+ * Grab the NET lock.
+ */
+ down(&nd->nd_net_semaphore);
+
+ /*
+ * Before "closing" the internal connection, make sure all
+ * ports are "idle".
+ */
+ dgrp_net_idle(nd);
+
+ nd->nd_state = NS_CLOSED;
+ nd->nd_flag = 0;
+
+ /*
+ * TODO ... must the wait queue be reset on close?
+ * should any pending waiters be reset?
+ * Let's decide to assert that the waitq is empty... and see
+ * how soon we break.
+ */
+ if (waitqueue_active(&nd->nd_tx_waitq))
+ pr_info("%s - expected waitqueue_active to be false\n",
+ __func__);
+
+ nd->nd_send = 0;
+
+ kfree(nd->nd_iobuf);
+ nd->nd_iobuf = NULL;
+
+/* TODO : historical locking placeholder */
+/*
+ * In the HPUX version of the RealPort driver (which served as a basis
+ * for this driver) this locking code was used. Saved if ever we need
+ * to review the locking under Linux.
+ */
+/* spinunlock( &nd->nd_lock ); */
+
+
+ kfree(nd->nd_writebuf);
+ nd->nd_writebuf = NULL;
+
+ kfree(nd->nd_inputbuf);
+ nd->nd_inputbuf = NULL;
+
+ kfree(nd->nd_inputflagbuf);
+ nd->nd_inputflagbuf = NULL;
+
+/* TODO : historical locking placeholder */
+/*
+ * In the HPUX version of the RealPort driver (which served as a basis
+ * for this driver) this locking code was used. Saved if ever we need
+ * to review the locking under Linux.
+ */
+/* spinlock(&nd->nd_lock); */
+
+ /*
+ * Set the active port count to zero.
+ */
+ dgrp_chan_count(nd, 0);
+
+/* TODO : historical locking placeholder */
+/*
+ * In the HPUX version of the RealPort driver (which served as a basis
+ * for this driver) this locking code was used. Saved if ever we need
+ * to review the locking under Linux.
+ */
+/* spinunlock(&nd->nd_lock); */
+
+ /*
+ * Release the NET lock.
+ */
+ up(&nd->nd_net_semaphore);
+
+ /*
+ * Cause the poller to stop scheduling itself if this is
+ * the last active node.
+ */
+ spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+ if (dgrp_poll_data.node_active_count == 2) {
+ del_timer(&dgrp_poll_data.timer);
+ dgrp_poll_data.node_active_count = 0;
+ }
+
+ spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+ down(&nd->nd_net_semaphore);
+
+ dgrp_monitor_message(nd, "Net Close");
+
+ up(&nd->nd_net_semaphore);
+
+done:
+ module_put(THIS_MODULE);
+ file->private_data = NULL;
+ return 0;
+}
+
+/* used in dgrp_send to setup command header */
+static inline u8 *set_cmd_header(u8 *b, u8 port, u8 cmd)
+{
+ *b++ = 0xb0 + (port & 0x0f);
+ *b++ = cmd;
+ return b;
+}
+
+/**
+ * dgrp_send() -- build a packet for transmission to the server
+ * @nd: pointer to a node structure
+ * @tmax: maximum bytes to transmit
+ *
+ * returns number of bytes sent
+ */
+static int dgrp_send(struct nd_struct *nd, long tmax)
+{
+ struct ch_struct *ch = nd->nd_chan;
+ u8 *b;
+ u8 *buf;
+ u8 *mbuf;
+ u8 port;
+ int mod;
+ long send;
+ int maxport;
+ long lastport = -1;
+ ushort rwin;
+ long in;
+ ushort n;
+ long t;
+ long ttotal;
+ long tchan;
+ long tsend;
+ ushort tsafe;
+ long work;
+ long send_sync;
+ long wanted_sync_port = -1;
+ ushort tdata[CHAN_MAX];
+ long used_buffer;
+
+ mbuf = nd->nd_iobuf + UIO_BASE;
+ buf = b = mbuf;
+
+ send_sync = nd->nd_link.lk_slow_rate < UIO_MAX;
+
+ ttotal = 0;
+ tchan = 0;
+
+ memset(tdata, 0, sizeof(tdata));
+
+
+ /*
+ * If there are any outstanding requests to be serviced,
+ * service them here.
+ */
+ if (nd->nd_send & NR_PASSWORD) {
+
+ /*
+ * Send Password response.
+ */
+
+ b[0] = 0xfc;
+ b[1] = 0x20;
+ put_unaligned_be16(strlen(nd->password), b + 2);
+ b += 4;
+ b += strlen(nd->password);
+ nd->nd_send &= ~(NR_PASSWORD);
+ }
+
+
+ /*
+ * Loop over all modules to generate commands, and determine
+ * the amount of data queued for transmit.
+ */
+
+ for (mod = 0, port = 0; port < nd->nd_chan_count; mod++) {
+ /*
+ * If this is not the current module, enter a module select
+ * code in the buffer.
+ */
+
+ if (mod != nd->nd_tx_module)
+ mbuf = ++b;
+
+ /*
+ * Loop to process one module.
+ */
+
+ maxport = port + 16;
+
+ if (maxport > nd->nd_chan_count)
+ maxport = nd->nd_chan_count;
+
+ for (; port < maxport; port++, ch++) {
+ /*
+ * Switch based on channel state.
+ */
+
+ switch (ch->ch_state) {
+ /*
+ * Send requests when the port is closed, and there
+ * are no Open, Close or Cancel requests expected.
+ */
+
+ case CS_IDLE:
+ /*
+ * Wait until any open error code
+ * has been delivered to all
+ * associated ports.
+ */
+
+ if (ch->ch_open_error) {
+ if (ch->ch_wait_count[ch->ch_otype]) {
+ work = 1;
+ break;
+ }
+
+ ch->ch_open_error = 0;
+ }
+
+ /*
+ * Wait until the channel HANGUP flag is reset
+ * before sending the first open. We can only
+ * get to this state after a server disconnect.
+ */
+
+ if ((ch->ch_flag & CH_HANGUP) != 0)
+ break;
+
+ /*
+ * If recovering from a TCP disconnect, or if
+ * there is an immediate open pending, send an
+ * Immediate Open request.
+ */
+ if ((ch->ch_flag & CH_PORT_GONE) ||
+ ch->ch_wait_count[OTYPE_IMMEDIATE] != 0) {
+ b = set_cmd_header(b, port, 10);
+ *b++ = 0;
+
+ ch->ch_state = CS_WAIT_OPEN;
+ ch->ch_otype = OTYPE_IMMEDIATE;
+ break;
+ }
+
+ /*
+ * If there is no Persistent or Incoming Open on the wait
+ * list in the server, and a thread is waiting for a
+ * Persistent or Incoming Open, send a Persistent or Incoming
+ * Open Request.
+ */
+ if (ch->ch_otype_waiting == 0) {
+ if (ch->ch_wait_count[OTYPE_PERSISTENT] != 0) {
+ b = set_cmd_header(b, port, 10);
+ *b++ = 1;
+
+ ch->ch_state = CS_WAIT_OPEN;
+ ch->ch_otype = OTYPE_PERSISTENT;
+ } else if (ch->ch_wait_count[OTYPE_INCOMING] != 0) {
+ b = set_cmd_header(b, port, 10);
+ *b++ = 2;
+
+ ch->ch_state = CS_WAIT_OPEN;
+ ch->ch_otype = OTYPE_INCOMING;
+ }
+ break;
+ }
+
+ /*
+ * If a Persistent or Incoming Open is pending in
+ * the server, but there is no longer an open
+ * thread waiting for it, cancel the request.
+ */
+
+ if (ch->ch_wait_count[ch->ch_otype_waiting] == 0) {
+ b = set_cmd_header(b, port, 10);
+ *b++ = 4;
+
+ ch->ch_state = CS_WAIT_CANCEL;
+ ch->ch_otype = ch->ch_otype_waiting;
+ }
+ break;
+
+ /*
+ * Send port parameter queries.
+ */
+ case CS_SEND_QUERY:
+ /*
+ * Clear out all FEP state that might remain
+ * from the last connection.
+ */
+
+ ch->ch_flag |= CH_PARAM;
+
+ ch->ch_flag &= ~CH_RX_FLUSH;
+
+ ch->ch_expect = 0;
+
+ ch->ch_s_tin = 0;
+ ch->ch_s_tpos = 0;
+ ch->ch_s_tsize = 0;
+ ch->ch_s_treq = 0;
+ ch->ch_s_elast = 0;
+
+ ch->ch_s_rin = 0;
+ ch->ch_s_rwin = 0;
+ ch->ch_s_rsize = 0;
+
+ ch->ch_s_tmax = 0;
+ ch->ch_s_ttime = 0;
+ ch->ch_s_rmax = 0;
+ ch->ch_s_rtime = 0;
+ ch->ch_s_rlow = 0;
+ ch->ch_s_rhigh = 0;
+
+ ch->ch_s_brate = 0;
+ ch->ch_s_iflag = 0;
+ ch->ch_s_cflag = 0;
+ ch->ch_s_oflag = 0;
+ ch->ch_s_xflag = 0;
+
+ ch->ch_s_mout = 0;
+ ch->ch_s_mflow = 0;
+ ch->ch_s_mctrl = 0;
+ ch->ch_s_xon = 0;
+ ch->ch_s_xoff = 0;
+ ch->ch_s_lnext = 0;
+ ch->ch_s_xxon = 0;
+ ch->ch_s_xxoff = 0;
+
+ /* Send Sequence Request */
+ b = set_cmd_header(b, port, 14);
+
+ /* Configure Event Conditions Packet */
+ b = set_cmd_header(b, port, 42);
+ put_unaligned_be16(0x02c0, b);
+ b += 2;
+ *b++ = (DM_DTR | DM_RTS | DM_CTS |
+ DM_DSR | DM_RI | DM_CD);
+
+ /* Send Status Request */
+ b = set_cmd_header(b, port, 16);
+
+ /* Send Buffer Request */
+ b = set_cmd_header(b, port, 20);
+
+ /* Send Port Capability Request */
+ b = set_cmd_header(b, port, 22);
+
+ ch->ch_expect = (RR_SEQUENCE |
+ RR_STATUS |
+ RR_BUFFER |
+ RR_CAPABILITY);
+
+ ch->ch_state = CS_WAIT_QUERY;
+
+ /* Raise modem signals */
+ b = set_cmd_header(b, port, 44);
+
+ if (ch->ch_flag & CH_PORT_GONE)
+ ch->ch_s_mout = ch->ch_mout;
+ else
+ ch->ch_s_mout = ch->ch_mout = DM_DTR | DM_RTS;
+
+ *b++ = ch->ch_mout;
+ *b++ = ch->ch_s_mflow = 0;
+ *b++ = ch->ch_s_mctrl = ch->ch_mctrl = 0;
+
+ if (ch->ch_flag & CH_PORT_GONE)
+ ch->ch_flag &= ~CH_PORT_GONE;
+
+ break;
+
+ /*
+ * Handle normal open and ready mode.
+ */
+
+ case CS_READY:
+
+ /*
+ * If the port is not open, and there are no
+ * no longer any ports requesting an open,
+ * then close the port.
+ */
+
+ if (ch->ch_open_count == 0 &&
+ ch->ch_wait_count[ch->ch_otype] == 0) {
+ goto send_close;
+ }
+
+ /*
+ * Process waiting input.
+ *
+ * If there is no one to read it, discard the data.
+ *
+ * Otherwise if we are not in fastcook mode, or if there is a
+ * fastcook thread waiting for data, send the data to the
+ * line discipline.
+ */
+ if (ch->ch_rin != ch->ch_rout) {
+ if (ch->ch_tun.un_open_count == 0 ||
+ (ch->ch_tun.un_flag & UN_CLOSING) ||
+ (ch->ch_cflag & CF_CREAD) == 0) {
+ ch->ch_rout = ch->ch_rin;
+ } else if ((ch->ch_flag & CH_FAST_READ) == 0 ||
+ ch->ch_inwait != 0) {
+ dgrp_input(ch);
+
+ if (ch->ch_rin != ch->ch_rout)
+ work = 1;
+ }
+ }
+
+ /*
+ * Handle receive flush, and changes to
+ * server port parameters.
+ */
+
+ if (ch->ch_flag & (CH_RX_FLUSH | CH_PARAM)) {
+ /*
+ * If we are in receive flush mode,
+ * and enough data has gone by, reset
+ * receive flush mode.
+ */
+ if (ch->ch_flag & CH_RX_FLUSH) {
+ if (((ch->ch_flush_seq - nd->nd_seq_out) & SEQ_MASK) >
+ ((nd->nd_seq_in - nd->nd_seq_out) & SEQ_MASK))
+ ch->ch_flag &= ~CH_RX_FLUSH;
+ else
+ work = 1;
+ }
+
+ /*
+ * Send TMAX, TTIME.
+ */
+
+ if (ch->ch_s_tmax != ch->ch_tmax ||
+ ch->ch_s_ttime != ch->ch_ttime) {
+ b = set_cmd_header(b, port, 48);
+
+ ch->ch_s_tmax = ch->ch_tmax;
+ ch->ch_s_ttime = ch->ch_ttime;
+
+ put_unaligned_be16(ch->ch_s_tmax,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_ttime,
+ b);
+ b += 2;
+ }
+
+ /*
+ * Send RLOW, RHIGH.
+ */
+
+ if (ch->ch_s_rlow != ch->ch_rlow ||
+ ch->ch_s_rhigh != ch->ch_rhigh) {
+ b = set_cmd_header(b, port, 45);
+
+ ch->ch_s_rlow = ch->ch_rlow;
+ ch->ch_s_rhigh = ch->ch_rhigh;
+
+ put_unaligned_be16(ch->ch_s_rlow,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_rhigh,
+ b);
+ b += 2;
+ }
+
+ /*
+ * Send BRATE, CFLAG, IFLAG,
+ * OFLAG, XFLAG.
+ */
+
+ if (ch->ch_s_brate != ch->ch_brate ||
+ ch->ch_s_cflag != ch->ch_cflag ||
+ ch->ch_s_iflag != ch->ch_iflag ||
+ ch->ch_s_oflag != ch->ch_oflag ||
+ ch->ch_s_xflag != ch->ch_xflag) {
+ b = set_cmd_header(b, port, 40);
+
+ ch->ch_s_brate = ch->ch_brate;
+ ch->ch_s_cflag = ch->ch_cflag;
+ ch->ch_s_iflag = ch->ch_iflag;
+ ch->ch_s_oflag = ch->ch_oflag;
+ ch->ch_s_xflag = ch->ch_xflag;
+
+ put_unaligned_be16(ch->ch_s_brate,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_cflag,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_iflag,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_oflag,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_xflag,
+ b);
+ b += 2;
+ }
+
+ /*
+ * Send MOUT, MFLOW, MCTRL.
+ */
+
+ if (ch->ch_s_mout != ch->ch_mout ||
+ ch->ch_s_mflow != ch->ch_mflow ||
+ ch->ch_s_mctrl != ch->ch_mctrl) {
+ b = set_cmd_header(b, port, 44);
+
+ *b++ = ch->ch_s_mout = ch->ch_mout;
+ *b++ = ch->ch_s_mflow = ch->ch_mflow;
+ *b++ = ch->ch_s_mctrl = ch->ch_mctrl;
+ }
+
+ /*
+ * Send Flow control characters.
+ */
+
+ if (ch->ch_s_xon != ch->ch_xon ||
+ ch->ch_s_xoff != ch->ch_xoff ||
+ ch->ch_s_lnext != ch->ch_lnext ||
+ ch->ch_s_xxon != ch->ch_xxon ||
+ ch->ch_s_xxoff != ch->ch_xxoff) {
+ b = set_cmd_header(b, port, 46);
+
+ *b++ = ch->ch_s_xon = ch->ch_xon;
+ *b++ = ch->ch_s_xoff = ch->ch_xoff;
+ *b++ = ch->ch_s_lnext = ch->ch_lnext;
+ *b++ = ch->ch_s_xxon = ch->ch_xxon;
+ *b++ = ch->ch_s_xxoff = ch->ch_xxoff;
+ }
+
+ /*
+ * Send RMAX, RTIME.
+ */
+
+ if (ch->ch_s_rmax != ch->ch_rmax ||
+ ch->ch_s_rtime != ch->ch_rtime) {
+ b = set_cmd_header(b, port, 47);
+
+ ch->ch_s_rmax = ch->ch_rmax;
+ ch->ch_s_rtime = ch->ch_rtime;
+
+ put_unaligned_be16(ch->ch_s_rmax,
+ b);
+ b += 2;
+
+ put_unaligned_be16(ch->ch_s_rtime,
+ b);
+ b += 2;
+ }
+
+ ch->ch_flag &= ~CH_PARAM;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+
+ /*
+ * Handle action commands.
+ */
+
+ if (ch->ch_send != 0) {
+ /* int send = ch->ch_send & ~ch->ch_expect; */
+ send = ch->ch_send & ~ch->ch_expect;
+
+ /* Send character immediate */
+ if ((send & RR_TX_ICHAR) != 0) {
+ b = set_cmd_header(b, port, 60);
+
+ *b++ = ch->ch_xon;
+ ch->ch_expect |= RR_TX_ICHAR;
+ }
+
+ /* BREAK request */
+ if ((send & RR_TX_BREAK) != 0) {
+ if (ch->ch_break_time != 0) {
+ b = set_cmd_header(b, port, 61);
+ put_unaligned_be16(ch->ch_break_time,
+ b);
+ b += 2;
+
+ ch->ch_expect |= RR_TX_BREAK;
+ ch->ch_break_time = 0;
+ } else {
+ ch->ch_send &= ~RR_TX_BREAK;
+ ch->ch_flag &= ~CH_TX_BREAK;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+ }
+
+ /*
+ * Flush input/output buffers.
+ */
+
+ if ((send & (RR_RX_FLUSH | RR_TX_FLUSH)) != 0) {
+ b = set_cmd_header(b, port, 62);
+
+ *b++ = ((send & RR_TX_FLUSH) == 0 ? 1 :
+ (send & RR_RX_FLUSH) == 0 ? 2 : 3);
+
+ if (send & RR_RX_FLUSH) {
+ ch->ch_flush_seq = nd->nd_seq_in;
+ ch->ch_flag |= CH_RX_FLUSH;
+ work = 1;
+ send_sync = 1;
+ wanted_sync_port = port;
+ }
+
+ ch->ch_send &= ~(RR_RX_FLUSH | RR_TX_FLUSH);
+ }
+
+ /* Pause input/output */
+ if ((send & (RR_RX_STOP | RR_TX_STOP)) != 0) {
+ b = set_cmd_header(b, port, 63);
+ *b = 0;
+
+ if ((send & RR_TX_STOP) != 0)
+ *b |= EV_OPU;
+
+ if ((send & RR_RX_STOP) != 0)
+ *b |= EV_IPU;
+
+ b++;
+
+ ch->ch_send &= ~(RR_RX_STOP | RR_TX_STOP);
+ }
+
+ /* Start input/output */
+ if ((send & (RR_RX_START | RR_TX_START)) != 0) {
+ b = set_cmd_header(b, port, 64);
+ *b = 0;
+
+ if ((send & RR_TX_START) != 0)
+ *b |= EV_OPU | EV_OPS | EV_OPX;
+
+ if ((send & RR_RX_START) != 0)
+ *b |= EV_IPU | EV_IPS;
+
+ b++;
+
+ ch->ch_send &= ~(RR_RX_START | RR_TX_START);
+ }
+ }
+
+
+ /*
+ * Send a window sequence to acknowledge received data.
+ */
+
+ rwin = (ch->ch_s_rin +
+ ((ch->ch_rout - ch->ch_rin - 1) & RBUF_MASK));
+
+ n = (rwin - ch->ch_s_rwin) & 0xffff;
+
+ if (n >= RBUF_MAX / 4) {
+ b[0] = 0xa0 + (port & 0xf);
+ ch->ch_s_rwin = rwin;
+ put_unaligned_be16(rwin, b + 1);
+ b += 3;
+ }
+
+ /*
+ * If the terminal is waiting on LOW
+ * water or EMPTY, and the condition
+ * is now satisfied, call the line
+ * discipline to put more data in the
+ * buffer.
+ */
+
+ n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+ if ((ch->ch_tun.un_flag & (UN_EMPTY|UN_LOW)) != 0) {
+ if ((ch->ch_tun.un_flag & UN_LOW) != 0 ?
+ (n <= TBUF_LOW) :
+ (n == 0 && ch->ch_s_tpos == ch->ch_s_tin)) {
+ ch->ch_tun.un_flag &= ~(UN_EMPTY|UN_LOW);
+
+ if (waitqueue_active(&((ch->ch_tun.un_tty)->write_wait)))
+ wake_up_interruptible(&((ch->ch_tun.un_tty)->write_wait));
+ tty_wakeup(ch->ch_tun.un_tty);
+ n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+ }
+ }
+
+ /*
+ * If the printer is waiting on LOW
+ * water, TIME, EMPTY or PWAIT, and is
+ * now ready to put more data in the
+ * buffer, call the line discipline to
+ * do the job.
+ */
+
+ /* FIXME: jiffies - ch->ch_waketime can never
+ be < 0. Someone needs to work out what is
+ actually intended here */
+ if (ch->ch_pun.un_open_count &&
+ (ch->ch_pun.un_flag &
+ (UN_EMPTY|UN_TIME|UN_LOW|UN_PWAIT)) != 0) {
+
+ if ((ch->ch_pun.un_flag & UN_LOW) != 0 ?
+ (n <= TBUF_LOW) :
+ (ch->ch_pun.un_flag & UN_TIME) != 0 ?
+ time_is_before_jiffies(ch->ch_waketime) :
+ (n == 0 && ch->ch_s_tpos == ch->ch_s_tin) &&
+ ((ch->ch_pun.un_flag & UN_EMPTY) != 0 ||
+ ((ch->ch_tun.un_open_count &&
+ ch->ch_tun.un_tty->ops->chars_in_buffer) ?
+ (ch->ch_tun.un_tty->ops->chars_in_buffer)(ch->ch_tun.un_tty) == 0
+ : 1
+ )
+ )) {
+ ch->ch_pun.un_flag &= ~(UN_EMPTY | UN_TIME | UN_LOW | UN_PWAIT);
+
+ if (waitqueue_active(&((ch->ch_pun.un_tty)->write_wait)))
+ wake_up_interruptible(&((ch->ch_pun.un_tty)->write_wait));
+ tty_wakeup(ch->ch_pun.un_tty);
+ n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+ } else if ((ch->ch_pun.un_flag & UN_TIME) != 0) {
+ work = 1;
+ }
+ }
+
+
+ /*
+ * Determine the max number of bytes
+ * this port can send, including
+ * packet header overhead.
+ */
+
+ t = ((ch->ch_s_tsize + ch->ch_s_tpos - ch->ch_s_tin) & 0xffff);
+
+ if (n > t)
+ n = t;
+
+ if (n != 0) {
+ n += (n <= 8 ? 1 : n <= 255 ? 2 : 3);
+
+ tdata[tchan++] = n;
+ ttotal += n;
+ }
+ break;
+
+ /*
+ * Close the port.
+ */
+
+send_close:
+ case CS_SEND_CLOSE:
+ b = set_cmd_header(b, port, 10);
+ if (ch->ch_otype == OTYPE_IMMEDIATE)
+ *b++ = 3;
+ else
+ *b++ = 4;
+
+ ch->ch_state = CS_WAIT_CLOSE;
+ break;
+
+ /*
+ * Wait for a previous server request.
+ */
+
+ case CS_WAIT_OPEN:
+ case CS_WAIT_CANCEL:
+ case CS_WAIT_FAIL:
+ case CS_WAIT_QUERY:
+ case CS_WAIT_CLOSE:
+ break;
+
+ default:
+ pr_info("%s - unexpected channel state (%i)\n",
+ __func__, ch->ch_state);
+ }
+ }
+
+ /*
+ * If a module select code is needed, drop one in. If space
+ * was reserved for one, but none is needed, recover the space.
+ */
+
+ if (mod != nd->nd_tx_module) {
+ if (b != mbuf) {
+ mbuf[-1] = 0xf0 | mod;
+ nd->nd_tx_module = mod;
+ } else {
+ b--;
+ }
+ }
+ }
+
+ /*
+ * Adjust "tmax" so that under worst case conditions we do
+ * not overflow either the daemon buffer or the internal
+ * buffer in the loop that follows. Leave a safe area
+ * of 64 bytes so we start getting asserts before we start
+ * losing data or clobbering memory.
+ */
+
+ n = UIO_MAX - UIO_BASE;
+
+ if (tmax > n)
+ tmax = n;
+
+ tmax -= 64;
+
+ tsafe = tmax;
+
+ /*
+ * Allocate space for 5 Module Selects, 1 Sequence Request,
+ * and 1 Set TREQ for each active channel.
+ */
+
+ tmax -= 5 + 3 + 4 * nd->nd_chan_count;
+
+ /*
+ * Further reduce "tmax" to the available transmit credit.
+ * Note that this is a soft constraint; The transmit credit
+ * can go negative for a time and then recover.
+ */
+
+ n = nd->nd_tx_deposit - nd->nd_tx_charge - nd->nd_link.lk_header_size;
+
+ if (tmax > n)
+ tmax = n;
+
+ /*
+ * Finally reduce tmax by the number of bytes already in
+ * the buffer.
+ */
+
+ tmax -= b - buf;
+
+ /*
+ * Suspend data transmit unless every ready channel can send
+ * at least 1 character.
+ */
+ if (tmax < 2 * nd->nd_chan_count) {
+ tsend = 1;
+
+ } else if (tchan > 1 && ttotal > tmax) {
+
+ /*
+ * If transmit is limited by the credit budget, find the
+ * largest number of characters we can send without driving
+ * the credit negative.
+ */
+
+ long tm = tmax;
+ int tc = tchan;
+ int try;
+
+ tsend = tm / tc;
+
+ for (try = 0; try < 3; try++) {
+ int i;
+ int c = 0;
+
+ for (i = 0; i < tc; i++) {
+ if (tsend < tdata[i])
+ tdata[c++] = tdata[i];
+ else
+ tm -= tdata[i];
+ }
+
+ if (c == tc)
+ break;
+
+ tsend = tm / c;
+
+ if (c == 1)
+ break;
+
+ tc = c;
+ }
+
+ tsend = tm / nd->nd_chan_count;
+
+ if (tsend < 2)
+ tsend = 1;
+
+ } else {
+ /*
+ * If no budgetary constraints, or only one channel ready
+ * to send, set the character limit to the remaining
+ * buffer size.
+ */
+
+ tsend = tmax;
+ }
+
+ tsend -= (tsend <= 9) ? 1 : (tsend <= 257) ? 2 : 3;
+
+ /*
+ * Loop over all channels, sending queued data.
+ */
+
+ port = 0;
+ ch = nd->nd_chan;
+ used_buffer = tmax;
+
+ for (mod = 0; port < nd->nd_chan_count; mod++) {
+ /*
+ * If this is not the current module, enter a module select
+ * code in the buffer.
+ */
+
+ if (mod != nd->nd_tx_module)
+ mbuf = ++b;
+
+ /*
+ * Loop to process one module.
+ */
+
+ maxport = port + 16;
+
+ if (maxport > nd->nd_chan_count)
+ maxport = nd->nd_chan_count;
+
+ for (; port < maxport; port++, ch++) {
+ if (ch->ch_state != CS_READY)
+ continue;
+
+ lastport = port;
+
+ n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+
+ /*
+ * If there is data that can be sent, send it.
+ */
+
+ if (n != 0 && used_buffer > 0) {
+ t = (ch->ch_s_tsize + ch->ch_s_tpos - ch->ch_s_tin) & 0xffff;
+
+ if (n > t)
+ n = t;
+
+ if (n > tsend) {
+ work = 1;
+ n = tsend;
+ }
+
+ if (n > used_buffer) {
+ work = 1;
+ n = used_buffer;
+ }
+
+ if (n <= 0)
+ continue;
+
+ /*
+ * Create the correct size transmit header,
+ * depending on the amount of data to transmit.
+ */
+
+ if (n <= 8) {
+
+ b[0] = ((n - 1) << 4) + (port & 0xf);
+ b += 1;
+
+ } else if (n <= 255) {
+
+ b[0] = 0x80 + (port & 0xf);
+ b[1] = n;
+ b += 2;
+
+ } else {
+
+ b[0] = 0x90 + (port & 0xf);
+ put_unaligned_be16(n, b + 1);
+ b += 3;
+ }
+
+ ch->ch_s_tin = (ch->ch_s_tin + n) & 0xffff;
+
+ /*
+ * Copy transmit data to the packet.
+ */
+
+ t = TBUF_MAX - ch->ch_tout;
+
+ if (n >= t) {
+ memcpy(b, ch->ch_tbuf + ch->ch_tout, t);
+ b += t;
+ n -= t;
+ used_buffer -= t;
+ ch->ch_tout = 0;
+ }
+
+ memcpy(b, ch->ch_tbuf + ch->ch_tout, n);
+ b += n;
+ used_buffer -= n;
+ ch->ch_tout += n;
+ n = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+ }
+
+ /*
+ * Wake any terminal unit process waiting in the
+ * dgrp_write routine for low water.
+ */
+
+ if (n > TBUF_LOW)
+ continue;
+
+ if ((ch->ch_flag & CH_LOW) != 0) {
+ ch->ch_flag &= ~CH_LOW;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+ /* selwakeup tty_sel */
+ if (ch->ch_tun.un_open_count) {
+ struct tty_struct *tty = (ch->ch_tun.un_tty);
+
+ if (waitqueue_active(&tty->write_wait))
+ wake_up_interruptible(&tty->write_wait);
+
+ tty_wakeup(tty);
+ }
+
+ if (ch->ch_pun.un_open_count) {
+ struct tty_struct *tty = (ch->ch_pun.un_tty);
+
+ if (waitqueue_active(&tty->write_wait))
+ wake_up_interruptible(&tty->write_wait);
+
+ tty_wakeup(tty);
+ }
+
+ /*
+ * Do EMPTY processing.
+ */
+
+ if (n != 0)
+ continue;
+
+ if ((ch->ch_flag & (CH_EMPTY | CH_DRAIN)) != 0 ||
+ (ch->ch_pun.un_flag & UN_EMPTY) != 0) {
+ /*
+ * If there is still data in the server, ask the server
+ * to notify us when its all gone.
+ */
+
+ if (ch->ch_s_treq != ch->ch_s_tin) {
+ b = set_cmd_header(b, port, 43);
+
+ ch->ch_s_treq = ch->ch_s_tin;
+ put_unaligned_be16(ch->ch_s_treq,
+ b);
+ b += 2;
+ }
+
+ /*
+ * If there is a thread waiting for buffer empty,
+ * and we are truly empty, wake the thread.
+ */
+
+ else if ((ch->ch_flag & CH_EMPTY) != 0 &&
+ (ch->ch_send & RR_TX_BREAK) == 0) {
+ ch->ch_flag &= ~CH_EMPTY;
+
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+ }
+ }
+
+ /*
+ * If a module select code is needed, drop one in. If space
+ * was reserved for one, but none is needed, recover the space.
+ */
+
+ if (mod != nd->nd_tx_module) {
+ if (b != mbuf) {
+ mbuf[-1] = 0xf0 | mod;
+ nd->nd_tx_module = mod;
+ } else {
+ b--;
+ }
+ }
+ }
+
+ /*
+ * Send a synchronization sequence associated with the last open
+ * channel that sent data, and remember the time when the data was
+ * sent.
+ */
+
+ in = nd->nd_seq_in;
+
+ if ((send_sync || nd->nd_seq_wait[in] != 0) && lastport >= 0) {
+ u8 *bb = b;
+
+ /*
+ * Attempt the use the port that really wanted the sync.
+ * This gets around a race condition where the "lastport" is in
+ * the middle of the close() routine, and by the time we
+ * send this command, it will have already acked the close, and
+ * thus not send the sync response.
+ */
+ if (wanted_sync_port >= 0)
+ lastport = wanted_sync_port;
+ /*
+ * Set a flag just in case the port is in the middle of a close,
+ * it will not be permitted to actually close until we get an
+ * sync response, and clear the flag there.
+ */
+ ch = nd->nd_chan + lastport;
+ ch->ch_flag |= CH_WAITING_SYNC;
+
+ mod = lastport >> 4;
+
+ if (mod != nd->nd_tx_module) {
+ bb[0] = 0xf0 + mod;
+ bb += 1;
+
+ nd->nd_tx_module = mod;
+ }
+
+ bb = set_cmd_header(bb, lastport, 12);
+ *bb++ = in;
+
+ nd->nd_seq_size[in] = bb - buf;
+ nd->nd_seq_time[in] = jiffies;
+
+ if (++in >= SEQ_MAX)
+ in = 0;
+
+ if (in != nd->nd_seq_out) {
+ b = bb;
+ nd->nd_seq_in = in;
+ nd->nd_unack += b - buf;
+ }
+ }
+
+ /*
+ * If there are no open ports, a sync cannot be sent.
+ * There is nothing left to wait for anyway, so wake any
+ * thread waiting for an acknowledgement.
+ */
+
+ else if (nd->nd_seq_wait[in] != 0) {
+ nd->nd_seq_wait[in] = 0;
+
+ wake_up_interruptible(&nd->nd_seq_wque[in]);
+ }
+
+ /*
+ * If there is no traffic for an interval of IDLE_MAX, then
+ * send a single byte packet.
+ */
+
+ if (b != buf) {
+ nd->nd_tx_time = jiffies;
+ } else if ((ulong)(jiffies - nd->nd_tx_time) >= IDLE_MAX) {
+ *b++ = 0xf0 | nd->nd_tx_module;
+ nd->nd_tx_time = jiffies;
+ }
+
+ n = b - buf;
+
+ if (n >= tsafe)
+ pr_info("%s - n(%i) >= tsafe(%i)\n",
+ __func__, n, tsafe);
+
+ if (tsend < 0)
+ dgrp_dump(buf, n);
+
+ nd->nd_tx_work = work;
+
+ return n;
+}
+
+/*
+ * dgrp_net_read()
+ * Data to be sent TO the PortServer from the "async." half of the driver.
+ */
+static ssize_t dgrp_net_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct nd_struct *nd;
+ long n;
+ u8 *local_buf;
+ u8 *b;
+ ssize_t rtn;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ return -ENXIO;
+
+ if (count < UIO_MIN)
+ return -EINVAL;
+
+ /*
+ * Only one read/write operation may be in progress at
+ * any given time.
+ */
+
+ /*
+ * Grab the NET lock.
+ */
+ down(&nd->nd_net_semaphore);
+
+ nd->nd_read_count++;
+
+ nd->nd_tx_ready = 0;
+
+ /*
+ * Determine the effective size of the buffer.
+ */
+
+ if (nd->nd_remain > UIO_BASE)
+ pr_info_ratelimited("%s - nd_remain(%i) > UIO_BASE\n",
+ __func__, nd->nd_remain);
+
+ b = local_buf = nd->nd_iobuf + UIO_BASE;
+
+ /*
+ * Generate data according to the node state.
+ */
+
+ switch (nd->nd_state) {
+ /*
+ * Initialize the connection.
+ */
+
+ case NS_IDLE:
+ if (nd->nd_mon_buf)
+ dgrp_monitor_reset(nd);
+
+ /*
+ * Request a Product ID Packet.
+ */
+
+ b[0] = 0xfb;
+ b[1] = 0x01;
+ b += 2;
+
+ nd->nd_expect |= NR_IDENT;
+
+ /*
+ * Request a Server Capability ID Response.
+ */
+
+ b[0] = 0xfb;
+ b[1] = 0x02;
+ b += 2;
+
+ nd->nd_expect |= NR_CAPABILITY;
+
+ /*
+ * Request a Server VPD Response.
+ */
+
+ b[0] = 0xfb;
+ b[1] = 0x18;
+ b += 2;
+
+ nd->nd_expect |= NR_VPD;
+
+ nd->nd_state = NS_WAIT_QUERY;
+ break;
+
+ /*
+ * We do serious communication with the server only in
+ * the READY state.
+ */
+
+ case NS_READY:
+ b = dgrp_send(nd, count) + local_buf;
+ break;
+
+ /*
+ * Send off an error after receiving a bogus message
+ * from the server.
+ */
+
+ case NS_SEND_ERROR:
+ n = strlen(nd->nd_error);
+
+ b[0] = 0xff;
+ b[1] = n;
+ memcpy(b + 2, nd->nd_error, n);
+ b += 2 + n;
+
+ dgrp_net_idle(nd);
+ /*
+ * Set the active port count to zero.
+ */
+ dgrp_chan_count(nd, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ n = b - local_buf;
+
+ if (n != 0) {
+ nd->nd_send_count++;
+
+ nd->nd_tx_byte += n + nd->nd_link.lk_header_size;
+ nd->nd_tx_charge += n + nd->nd_link.lk_header_size;
+ }
+
+ rtn = copy_to_user((void __user *)buf, local_buf, n);
+ if (rtn) {
+ rtn = -EFAULT;
+ goto done;
+ }
+
+ *ppos += n;
+
+ rtn = n;
+
+ if (nd->nd_mon_buf)
+ dgrp_monitor_data(nd, RPDUMP_CLIENT, local_buf, n);
+
+ /*
+ * Release the NET lock.
+ */
+done:
+ up(&nd->nd_net_semaphore);
+
+ return rtn;
+}
+
+/**
+ * dgrp_receive() -- decode data packets received from the remote PortServer.
+ * @nd: pointer to a node structure
+ */
+static void dgrp_receive(struct nd_struct *nd)
+{
+ struct ch_struct *ch;
+ u8 *buf;
+ u8 *b;
+ u8 *dbuf;
+ char *error;
+ long port;
+ long dlen;
+ long plen;
+ long remain;
+ long n;
+ long mlast;
+ long elast;
+ long mstat;
+ long estat;
+
+ char ID[3];
+
+ nd->nd_tx_time = jiffies;
+
+ ID_TO_CHAR(nd->nd_ID, ID);
+
+ b = buf = nd->nd_iobuf;
+ remain = nd->nd_remain;
+
+ /*
+ * Loop to process Realport protocol packets.
+ */
+
+ while (remain > 0) {
+ int n0 = b[0] >> 4;
+ int n1 = b[0] & 0x0f;
+
+ if (n0 <= 12) {
+ port = (nd->nd_rx_module << 4) + n1;
+
+ if (port >= nd->nd_chan_count) {
+ error = "Improper Port Number";
+ goto prot_error;
+ }
+
+ ch = nd->nd_chan + port;
+ } else {
+ port = -1;
+ ch = NULL;
+ }
+
+ /*
+ * Process by major packet type.
+ */
+
+ switch (n0) {
+
+ /*
+ * Process 1-byte header data packet.
+ */
+
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ dlen = n0 + 1;
+ plen = dlen + 1;
+
+ dbuf = b + 1;
+ goto data;
+
+ /*
+ * Process 2-byte header data packet.
+ */
+
+ case 8:
+ if (remain < 3)
+ goto done;
+
+ dlen = b[1];
+ plen = dlen + 2;
+
+ dbuf = b + 2;
+ goto data;
+
+ /*
+ * Process 3-byte header data packet.
+ */
+
+ case 9:
+ if (remain < 4)
+ goto done;
+
+ dlen = get_unaligned_be16(b + 1);
+ plen = dlen + 3;
+
+ dbuf = b + 3;
+
+ /*
+ * Common packet handling code.
+ */
+
+data:
+ nd->nd_tx_work = 1;
+
+ /*
+ * Otherwise data should appear only when we are
+ * in the CS_READY state.
+ */
+
+ if (ch->ch_state < CS_READY) {
+ error = "Data received before RWIN established";
+ goto prot_error;
+ }
+
+ /*
+ * Assure that the data received is within the
+ * allowable window.
+ */
+
+ n = (ch->ch_s_rwin - ch->ch_s_rin) & 0xffff;
+
+ if (dlen > n) {
+ error = "Receive data overrun";
+ goto prot_error;
+ }
+
+ /*
+ * If we received 3 or less characters,
+ * assume it is a human typing, and set RTIME
+ * to 10 milliseconds.
+ *
+ * If we receive 10 or more characters,
+ * assume its not a human typing, and set RTIME
+ * to 100 milliseconds.
+ */
+
+ if (ch->ch_edelay != DGRP_RTIME) {
+ if (ch->ch_rtime != ch->ch_edelay) {
+ ch->ch_rtime = ch->ch_edelay;
+ ch->ch_flag |= CH_PARAM;
+ }
+ } else if (dlen <= 3) {
+ if (ch->ch_rtime != 10) {
+ ch->ch_rtime = 10;
+ ch->ch_flag |= CH_PARAM;
+ }
+ } else {
+ if (ch->ch_rtime != DGRP_RTIME) {
+ ch->ch_rtime = DGRP_RTIME;
+ ch->ch_flag |= CH_PARAM;
+ }
+ }
+
+ /*
+ * If a portion of the packet is outside the
+ * buffer, shorten the effective length of the
+ * data packet to be the amount of data received.
+ */
+
+ if (remain < plen)
+ dlen -= plen - remain;
+
+ /*
+ * Detect if receive flush is now complete.
+ */
+
+ if ((ch->ch_flag & CH_RX_FLUSH) != 0 &&
+ ((ch->ch_flush_seq - nd->nd_seq_out) & SEQ_MASK) >=
+ ((nd->nd_seq_in - nd->nd_seq_out) & SEQ_MASK)) {
+ ch->ch_flag &= ~CH_RX_FLUSH;
+ }
+
+ /*
+ * If we are ready to receive, move the data into
+ * the receive buffer.
+ */
+
+ ch->ch_s_rin = (ch->ch_s_rin + dlen) & 0xffff;
+
+ if (ch->ch_state == CS_READY &&
+ (ch->ch_tun.un_open_count != 0) &&
+ (ch->ch_tun.un_flag & UN_CLOSING) == 0 &&
+ (ch->ch_cflag & CF_CREAD) != 0 &&
+ (ch->ch_flag & (CH_BAUD0 | CH_RX_FLUSH)) == 0 &&
+ (ch->ch_send & RR_RX_FLUSH) == 0) {
+
+ if (ch->ch_rin + dlen >= RBUF_MAX) {
+ n = RBUF_MAX - ch->ch_rin;
+
+ memcpy(ch->ch_rbuf + ch->ch_rin, dbuf, n);
+
+ ch->ch_rin = 0;
+ dbuf += n;
+ dlen -= n;
+ }
+
+ memcpy(ch->ch_rbuf + ch->ch_rin, dbuf, dlen);
+
+ ch->ch_rin += dlen;
+
+
+ /*
+ * If we are not in fastcook mode, or
+ * if there is a fastcook thread
+ * waiting for data, send the data to
+ * the line discipline.
+ */
+
+ if ((ch->ch_flag & CH_FAST_READ) == 0 ||
+ ch->ch_inwait != 0) {
+ dgrp_input(ch);
+ }
+
+ /*
+ * If there is a read thread waiting
+ * in select, and we are in fastcook
+ * mode, wake him up.
+ */
+
+ if (waitqueue_active(&ch->ch_tun.un_tty->read_wait) &&
+ (ch->ch_flag & CH_FAST_READ) != 0)
+ wake_up_interruptible(&ch->ch_tun.un_tty->read_wait);
+
+ /*
+ * Wake any thread waiting in the
+ * fastcook loop.
+ */
+
+ if ((ch->ch_flag & CH_INPUT) != 0) {
+ ch->ch_flag &= ~CH_INPUT;
+
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+ }
+
+ /*
+ * Fabricate and insert a data packet header to
+ * preced the remaining data when it comes in.
+ */
+
+ if (remain < plen) {
+ dlen = plen - remain;
+ b = buf;
+
+ b[0] = 0x90 + n1;
+ put_unaligned_be16(dlen, b + 1);
+
+ remain = 3;
+ goto done;
+ }
+ break;
+
+ /*
+ * Handle Window Sequence packets.
+ */
+
+ case 10:
+ plen = 3;
+ if (remain < plen)
+ goto done;
+
+ nd->nd_tx_work = 1;
+
+ {
+ ushort tpos = get_unaligned_be16(b + 1);
+
+ ushort ack = (tpos - ch->ch_s_tpos) & 0xffff;
+ ushort unack = (ch->ch_s_tin - ch->ch_s_tpos) & 0xffff;
+ ushort notify = (ch->ch_s_treq - ch->ch_s_tpos) & 0xffff;
+
+ if (ch->ch_state < CS_READY || ack > unack) {
+ error = "Improper Window Sequence";
+ goto prot_error;
+ }
+
+ ch->ch_s_tpos = tpos;
+
+ if (notify <= ack)
+ ch->ch_s_treq = tpos;
+ }
+ break;
+
+ /*
+ * Handle Command response packets.
+ */
+
+ case 11:
+
+ /*
+ * RealPort engine fix - 03/11/2004
+ *
+ * This check did not used to be here.
+ *
+ * We were using b[1] without verifying that the data
+ * is actually there and valid. On a split packet, it
+ * might not be yet.
+ *
+ * NOTE: I have never actually seen the failure happen
+ * under Linux, but since I have seen it occur
+ * under both Solaris and HP-UX, the assumption
+ * is that it *could* happen here as well...
+ */
+ if (remain < 2)
+ goto done;
+
+
+ switch (b[1]) {
+
+ /*
+ * Handle Open Response.
+ */
+
+ case 11:
+ plen = 6;
+ if (remain < plen)
+ goto done;
+
+ nd->nd_tx_work = 1;
+
+ {
+ int req = b[2];
+ int resp = b[3];
+ port = get_unaligned_be16(b + 4);
+
+ if (port >= nd->nd_chan_count) {
+ error = "Open channel number out of range";
+ goto prot_error;
+ }
+
+ ch = nd->nd_chan + port;
+
+ /*
+ * How we handle an open response depends primarily
+ * on our current channel state.
+ */
+
+ switch (ch->ch_state) {
+ case CS_IDLE:
+
+ /*
+ * Handle a delayed open.
+ */
+
+ if (ch->ch_otype_waiting != 0 &&
+ req == ch->ch_otype_waiting &&
+ resp == 0) {
+ ch->ch_otype = req;
+ ch->ch_otype_waiting = 0;
+ ch->ch_state = CS_SEND_QUERY;
+ break;
+ }
+ goto open_error;
+
+ case CS_WAIT_OPEN:
+
+ /*
+ * Handle the open response.
+ */
+
+ if (req == ch->ch_otype) {
+ switch (resp) {
+
+ /*
+ * On successful response, open the
+ * port and proceed normally.
+ */
+
+ case 0:
+ ch->ch_state = CS_SEND_QUERY;
+ break;
+
+ /*
+ * On a busy response to a persistent open,
+ * remember that the open is pending.
+ */
+
+ case 1:
+ case 2:
+ if (req != OTYPE_IMMEDIATE) {
+ ch->ch_otype_waiting = req;
+ ch->ch_state = CS_IDLE;
+ break;
+ }
+
+ /*
+ * Otherwise the server open failed. If
+ * the Unix port is open, hang it up.
+ */
+
+ default:
+ if (ch->ch_open_count != 0) {
+ ch->ch_flag |= CH_HANGUP;
+ dgrp_carrier(ch);
+ ch->ch_state = CS_IDLE;
+ break;
+ }
+
+ ch->ch_open_error = resp;
+ ch->ch_state = CS_IDLE;
+
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+ break;
+ }
+
+ /*
+ * Handle delayed response arrival preceding
+ * the open response we are waiting for.
+ */
+
+ if (ch->ch_otype_waiting != 0 &&
+ req == ch->ch_otype_waiting &&
+ resp == 0) {
+ ch->ch_otype = ch->ch_otype_waiting;
+ ch->ch_otype_waiting = 0;
+ ch->ch_state = CS_WAIT_FAIL;
+ break;
+ }
+ goto open_error;
+
+
+ case CS_WAIT_FAIL:
+
+ /*
+ * Handle response to immediate open arriving
+ * after a delayed open success.
+ */
+
+ if (req == OTYPE_IMMEDIATE) {
+ ch->ch_state = CS_SEND_QUERY;
+ break;
+ }
+ goto open_error;
+
+
+ case CS_WAIT_CANCEL:
+ /*
+ * Handle delayed open response arriving before
+ * the cancel response.
+ */
+
+ if (req == ch->ch_otype_waiting &&
+ resp == 0) {
+ ch->ch_otype_waiting = 0;
+ break;
+ }
+
+ /*
+ * Handle cancel response.
+ */
+
+ if (req == 4 && resp == 0) {
+ ch->ch_otype_waiting = 0;
+ ch->ch_state = CS_IDLE;
+ break;
+ }
+ goto open_error;
+
+
+ case CS_WAIT_CLOSE:
+ /*
+ * Handle a successful response to a port
+ * close.
+ */
+
+ if (req >= 3) {
+ ch->ch_state = CS_IDLE;
+ break;
+ }
+ goto open_error;
+
+open_error:
+ default:
+ {
+ error = "Improper Open Response";
+ goto prot_error;
+ }
+ }
+ }
+ break;
+
+ /*
+ * Handle Synchronize Response.
+ */
+
+ case 13:
+ plen = 3;
+ if (remain < plen)
+ goto done;
+ {
+ int seq = b[2];
+ int s;
+
+ /*
+ * If channel was waiting for this sync response,
+ * unset the flag, and wake up anyone waiting
+ * on the event.
+ */
+ if (ch->ch_flag & CH_WAITING_SYNC) {
+ ch->ch_flag &= ~(CH_WAITING_SYNC);
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+ if (((seq - nd->nd_seq_out) & SEQ_MASK) >=
+ ((nd->nd_seq_in - nd->nd_seq_out) & SEQ_MASK)) {
+ break;
+ }
+
+ for (s = nd->nd_seq_out;; s = (s + 1) & SEQ_MASK) {
+ if (nd->nd_seq_wait[s] != 0) {
+ nd->nd_seq_wait[s] = 0;
+
+ wake_up_interruptible(&nd->nd_seq_wque[s]);
+ }
+
+ nd->nd_unack -= nd->nd_seq_size[s];
+
+ if (s == seq)
+ break;
+ }
+
+ nd->nd_seq_out = (seq + 1) & SEQ_MASK;
+ }
+ break;
+
+ /*
+ * Handle Sequence Response.
+ */
+
+ case 15:
+ plen = 6;
+ if (remain < plen)
+ goto done;
+
+ {
+ /* Record that we have received the Sequence
+ * Response, but we aren't interested in the
+ * sequence numbers. We were using RIN like it
+ * was ROUT and that was causing problems,
+ * fixed 7-13-2001 David Fries. See comment in
+ * drp.h for ch_s_rin variable.
+ int rin = get_unaligned_be16(b + 2);
+ int tpos = get_unaligned_be16(b + 4);
+ */
+
+ ch->ch_send &= ~RR_SEQUENCE;
+ ch->ch_expect &= ~RR_SEQUENCE;
+ }
+ goto check_query;
+
+ /*
+ * Handle Status Response.
+ */
+
+ case 17:
+ plen = 5;
+ if (remain < plen)
+ goto done;
+
+ {
+ ch->ch_s_elast = get_unaligned_be16(b + 2);
+ ch->ch_s_mlast = b[4];
+
+ ch->ch_expect &= ~RR_STATUS;
+ ch->ch_send &= ~RR_STATUS;
+
+ /*
+ * CH_PHYS_CD is cleared because something _could_ be
+ * waiting for the initial sense of carrier... and if
+ * carrier is high immediately, we want to be sure to
+ * wake them as soon as possible.
+ */
+ ch->ch_flag &= ~CH_PHYS_CD;
+
+ dgrp_carrier(ch);
+ }
+ goto check_query;
+
+ /*
+ * Handle Line Error Response.
+ */
+
+ case 19:
+ plen = 14;
+ if (remain < plen)
+ goto done;
+
+ break;
+
+ /*
+ * Handle Buffer Response.
+ */
+
+ case 21:
+ plen = 6;
+ if (remain < plen)
+ goto done;
+
+ {
+ ch->ch_s_rsize = get_unaligned_be16(b + 2);
+ ch->ch_s_tsize = get_unaligned_be16(b + 4);
+
+ ch->ch_send &= ~RR_BUFFER;
+ ch->ch_expect &= ~RR_BUFFER;
+ }
+ goto check_query;
+
+ /*
+ * Handle Port Capability Response.
+ */
+
+ case 23:
+ plen = 32;
+ if (remain < plen)
+ goto done;
+
+ {
+ ch->ch_send &= ~RR_CAPABILITY;
+ ch->ch_expect &= ~RR_CAPABILITY;
+ }
+
+ /*
+ * When all queries are complete, set those parameters
+ * derived from the query results, then transition
+ * to the READY state.
+ */
+
+check_query:
+ if (ch->ch_state == CS_WAIT_QUERY &&
+ (ch->ch_expect & (RR_SEQUENCE |
+ RR_STATUS |
+ RR_BUFFER |
+ RR_CAPABILITY)) == 0) {
+ ch->ch_tmax = ch->ch_s_tsize / 4;
+
+ if (ch->ch_edelay == DGRP_TTIME)
+ ch->ch_ttime = DGRP_TTIME;
+ else
+ ch->ch_ttime = ch->ch_edelay;
+
+ ch->ch_rmax = ch->ch_s_rsize / 4;
+
+ if (ch->ch_edelay == DGRP_RTIME)
+ ch->ch_rtime = DGRP_RTIME;
+ else
+ ch->ch_rtime = ch->ch_edelay;
+
+ ch->ch_rlow = 2 * ch->ch_s_rsize / 8;
+ ch->ch_rhigh = 6 * ch->ch_s_rsize / 8;
+
+ ch->ch_state = CS_READY;
+
+ nd->nd_tx_work = 1;
+ wake_up_interruptible(&ch->ch_flag_wait);
+
+ }
+ break;
+
+ default:
+ goto decode_error;
+ }
+ break;
+
+ /*
+ * Handle Events.
+ */
+
+ case 12:
+ plen = 4;
+ if (remain < plen)
+ goto done;
+
+ mlast = ch->ch_s_mlast;
+ elast = ch->ch_s_elast;
+
+ mstat = ch->ch_s_mlast = b[1];
+ estat = ch->ch_s_elast = get_unaligned_be16(b + 2);
+
+ /*
+ * Handle modem changes.
+ */
+
+ if (((mstat ^ mlast) & DM_CD) != 0)
+ dgrp_carrier(ch);
+
+
+ /*
+ * Handle received break.
+ */
+
+ if ((estat & ~elast & EV_RXB) != 0 &&
+ (ch->ch_tun.un_open_count != 0) &&
+ I_BRKINT(ch->ch_tun.un_tty) &&
+ !(I_IGNBRK(ch->ch_tun.un_tty))) {
+
+ tty_buffer_request_room(&ch->port, 1);
+ tty_insert_flip_char(&ch->port, 0, TTY_BREAK);
+ tty_flip_buffer_push(&ch->port);
+
+ }
+
+ /*
+ * On transmit break complete, if more break traffic
+ * is waiting then send it. Otherwise wake any threads
+ * waiting for transmitter empty.
+ */
+
+ if ((~estat & elast & EV_TXB) != 0 &&
+ (ch->ch_expect & RR_TX_BREAK) != 0) {
+
+ nd->nd_tx_work = 1;
+
+ ch->ch_expect &= ~RR_TX_BREAK;
+
+ if (ch->ch_break_time != 0) {
+ ch->ch_send |= RR_TX_BREAK;
+ } else {
+ ch->ch_send &= ~RR_TX_BREAK;
+ ch->ch_flag &= ~CH_TX_BREAK;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+ }
+ break;
+
+ case 13:
+ case 14:
+ error = "Unrecognized command";
+ goto prot_error;
+
+ /*
+ * Decode Special Codes.
+ */
+
+ case 15:
+ switch (n1) {
+ /*
+ * One byte module select.
+ */
+
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ plen = 1;
+ nd->nd_rx_module = n1;
+ break;
+
+ /*
+ * Two byte module select.
+ */
+
+ case 8:
+ plen = 2;
+ if (remain < plen)
+ goto done;
+
+ nd->nd_rx_module = b[1];
+ break;
+
+ /*
+ * ID Request packet.
+ */
+
+ case 11:
+ if (remain < 4)
+ goto done;
+
+ plen = get_unaligned_be16(b + 2);
+
+ if (plen < 12 || plen > 1000) {
+ error = "Response Packet length error";
+ goto prot_error;
+ }
+
+ nd->nd_tx_work = 1;
+
+ switch (b[1]) {
+ /*
+ * Echo packet.
+ */
+
+ case 0:
+ nd->nd_send |= NR_ECHO;
+ break;
+
+ /*
+ * ID Response packet.
+ */
+
+ case 1:
+ nd->nd_send |= NR_IDENT;
+ break;
+
+ /*
+ * ID Response packet.
+ */
+
+ case 32:
+ nd->nd_send |= NR_PASSWORD;
+ break;
+
+ }
+ break;
+
+ /*
+ * Various node-level response packets.
+ */
+
+ case 12:
+ if (remain < 4)
+ goto done;
+
+ plen = get_unaligned_be16(b + 2);
+
+ if (plen < 4 || plen > 1000) {
+ error = "Response Packet length error";
+ goto prot_error;
+ }
+
+ nd->nd_tx_work = 1;
+
+ switch (b[1]) {
+ /*
+ * Echo packet.
+ */
+
+ case 0:
+ nd->nd_expect &= ~NR_ECHO;
+ break;
+
+ /*
+ * Product Response Packet.
+ */
+
+ case 1:
+ {
+ int desclen;
+
+ nd->nd_hw_ver = (b[8] << 8) | b[9];
+ nd->nd_sw_ver = (b[10] << 8) | b[11];
+ nd->nd_hw_id = b[6];
+ desclen = (plen - 12 > MAX_DESC_LEN - 1) ? MAX_DESC_LEN - 1 :
+ plen - 12;
+
+ if (desclen <= 0) {
+ error = "Response Packet desclen error";
+ goto prot_error;
+ }
+
+ strncpy(nd->nd_ps_desc, b + 12, desclen);
+ nd->nd_ps_desc[desclen] = 0;
+ }
+
+ nd->nd_expect &= ~NR_IDENT;
+ break;
+
+ /*
+ * Capability Response Packet.
+ */
+
+ case 2:
+ {
+ int nn = get_unaligned_be16(b + 4);
+
+ if (nn > CHAN_MAX)
+ nn = CHAN_MAX;
+
+ dgrp_chan_count(nd, nn);
+ }
+
+ nd->nd_expect &= ~NR_CAPABILITY;
+ break;
+
+ /*
+ * VPD Response Packet.
+ */
+
+ case 15:
+ /*
+ * NOTE: case 15 is here ONLY because the EtherLite
+ * is broken, and sends a response to 24 back as 15.
+ * To resolve this, the EtherLite firmware is now
+ * fixed to send back 24 correctly, but, for backwards
+ * compatibility, we now have reserved 15 for the
+ * bad EtherLite response to 24 as well.
+ */
+
+ /* Fallthru! */
+
+ case 24:
+
+ /*
+ * If the product doesn't support VPD,
+ * it will send back a null IDRESP,
+ * which is a length of 4 bytes.
+ */
+ if (plen > 4) {
+ memcpy(nd->nd_vpd, b + 4, min(plen - 4, (long) VPDSIZE));
+ nd->nd_vpd_len = min(plen - 4, (long) VPDSIZE);
+ }
+
+ nd->nd_expect &= ~NR_VPD;
+ break;
+
+ default:
+ goto decode_error;
+ }
+
+ if (nd->nd_expect == 0 &&
+ nd->nd_state == NS_WAIT_QUERY) {
+ nd->nd_state = NS_READY;
+ }
+ break;
+
+ /*
+ * Debug packet.
+ */
+
+ case 14:
+ if (remain < 4)
+ goto done;
+
+ plen = get_unaligned_be16(b + 2) + 4;
+
+ if (plen > 1000) {
+ error = "Debug Packet too large";
+ goto prot_error;
+ }
+
+ if (remain < plen)
+ goto done;
+ break;
+
+ /*
+ * Handle reset packet.
+ */
+
+ case 15:
+ if (remain < 2)
+ goto done;
+
+ plen = 2 + b[1];
+
+ if (remain < plen)
+ goto done;
+
+ nd->nd_tx_work = 1;
+
+ n = b[plen];
+ b[plen] = 0;
+
+ b[plen] = n;
+
+ error = "Client Reset Acknowledge";
+ goto prot_error;
+
+ default:
+ goto decode_error;
+ }
+ break;
+
+ default:
+ goto decode_error;
+ }
+
+ b += plen;
+ remain -= plen;
+ }
+
+ /*
+ * When the buffer is exhausted, copy any data left at the
+ * top of the buffer back down to the bottom for the next
+ * read request.
+ */
+
+done:
+ if (remain > 0 && b != buf)
+ memcpy(buf, b, remain);
+
+ nd->nd_remain = remain;
+ return;
+
+/*
+ * Handle a decode error.
+ */
+
+decode_error:
+ error = "Protocol decode error";
+
+/*
+ * Handle a general protocol error.
+ */
+
+prot_error:
+ nd->nd_remain = 0;
+ nd->nd_state = NS_SEND_ERROR;
+ nd->nd_error = error;
+}
+
+/*
+ * dgrp_net_write() -- write data to the network device.
+ *
+ * A zero byte write indicates that the connection to the RealPort
+ * device has been broken.
+ *
+ * A non-zero write indicates data from the RealPort device.
+ */
+static ssize_t dgrp_net_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct nd_struct *nd;
+ ssize_t rtn = 0;
+ long n;
+ long total = 0;
+
+ /*
+ * Get the node pointer, and quit if it doesn't exist.
+ */
+ nd = (struct nd_struct *)(file->private_data);
+ if (!nd)
+ return -ENXIO;
+
+ /*
+ * Grab the NET lock.
+ */
+ down(&nd->nd_net_semaphore);
+
+ nd->nd_write_count++;
+
+ /*
+ * Handle disconnect.
+ */
+
+ if (count == 0) {
+ dgrp_net_idle(nd);
+ /*
+ * Set the active port count to zero.
+ */
+ dgrp_chan_count(nd, 0);
+ goto unlock;
+ }
+
+ /*
+ * Loop to process entire receive packet.
+ */
+
+ while (count > 0) {
+ n = UIO_MAX - nd->nd_remain;
+
+ if (n > count)
+ n = count;
+
+ nd->nd_rx_byte += n + nd->nd_link.lk_header_size;
+
+ rtn = copy_from_user(nd->nd_iobuf + nd->nd_remain,
+ (void __user *) buf + total, n);
+ if (rtn) {
+ rtn = -EFAULT;
+ goto unlock;
+ }
+
+ *ppos += n;
+
+ total += n;
+
+ count -= n;
+
+ if (nd->nd_mon_buf)
+ dgrp_monitor_data(nd, RPDUMP_SERVER,
+ nd->nd_iobuf + nd->nd_remain, n);
+
+ nd->nd_remain += n;
+
+ dgrp_receive(nd);
+ }
+
+ rtn = total;
+
+unlock:
+ /*
+ * Release the NET lock.
+ */
+ up(&nd->nd_net_semaphore);
+
+ return rtn;
+}
+
+
+/*
+ * dgrp_net_select()
+ * Determine whether a device is ready to be read or written to, and
+ * sleep if not.
+ */
+static unsigned int dgrp_net_select(struct file *file,
+ struct poll_table_struct *table)
+{
+ unsigned int retval = 0;
+ struct nd_struct *nd = file->private_data;
+
+ poll_wait(file, &nd->nd_tx_waitq, table);
+
+ if (nd->nd_tx_ready)
+ retval |= POLLIN | POLLRDNORM; /* Conditionally readable */
+
+ retval |= POLLOUT | POLLWRNORM; /* Always writeable */
+
+ return retval;
+}
+
+/*
+ * dgrp_net_ioctl
+ *
+ * Implement those functions which allow the network daemon to control
+ * the network parameters in the driver. The ioctls include ones to
+ * get and set the link speed parameters for the PortServer.
+ */
+static long dgrp_net_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct nd_struct *nd;
+ int rtn = 0;
+ long size = _IOC_SIZE(cmd);
+ struct link_struct link;
+
+ nd = file->private_data;
+
+ if (_IOC_DIR(cmd) & _IOC_READ)
+ rtn = access_ok(VERIFY_WRITE, (void __user *) arg, size);
+ else if (_IOC_DIR(cmd) & _IOC_WRITE)
+ rtn = access_ok(VERIFY_READ, (void __user *) arg, size);
+
+ if (!rtn)
+ return rtn;
+
+ switch (cmd) {
+ case DIGI_SETLINK:
+ if (size != sizeof(struct link_struct))
+ return -EINVAL;
+
+ if (copy_from_user(&link, (void __user *)arg, size))
+ return -EFAULT;
+
+ if (link.lk_fast_rate < 9600)
+ link.lk_fast_rate = 9600;
+
+ if (link.lk_slow_rate < 2400)
+ link.lk_slow_rate = 2400;
+
+ if (link.lk_fast_rate > 10000000)
+ link.lk_fast_rate = 10000000;
+
+ if (link.lk_slow_rate > link.lk_fast_rate)
+ link.lk_slow_rate = link.lk_fast_rate;
+
+ if (link.lk_fast_delay > 2000)
+ link.lk_fast_delay = 2000;
+
+ if (link.lk_slow_delay > 10000)
+ link.lk_slow_delay = 10000;
+
+ if (link.lk_fast_delay < 60)
+ link.lk_fast_delay = 60;
+
+ if (link.lk_slow_delay < link.lk_fast_delay)
+ link.lk_slow_delay = link.lk_fast_delay;
+
+ if (link.lk_header_size < 2)
+ link.lk_header_size = 2;
+
+ if (link.lk_header_size > 128)
+ link.lk_header_size = 128;
+
+ link.lk_fast_rate /= 8 * 1000 / dgrp_poll_tick;
+ link.lk_slow_rate /= 8 * 1000 / dgrp_poll_tick;
+
+ link.lk_fast_delay /= dgrp_poll_tick;
+ link.lk_slow_delay /= dgrp_poll_tick;
+
+ nd->nd_link = link;
+
+ break;
+
+ case DIGI_GETLINK:
+ if (size != sizeof(struct link_struct))
+ return -EINVAL;
+
+ if (copy_to_user((void __user *)arg, (void *)(&nd->nd_link),
+ size))
+ return -EFAULT;
+
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+/**
+ * dgrp_poll_handler() -- handler for poll timer
+ *
+ * As each timer expires, it determines (a) whether the "transmit"
+ * waiter needs to be woken up, and (b) whether the poller needs to
+ * be rescheduled.
+ */
+void dgrp_poll_handler(unsigned long arg)
+{
+ struct dgrp_poll_data *poll_data;
+ struct nd_struct *nd;
+ struct link_struct *lk;
+ ulong time;
+ ulong poll_time;
+ ulong freq;
+ ulong lock_flags;
+
+ poll_data = (struct dgrp_poll_data *) arg;
+ freq = 1000 / poll_data->poll_tick;
+ poll_data->poll_round += 17;
+
+ if (poll_data->poll_round >= freq)
+ poll_data->poll_round -= freq;
+
+ /*
+ * Loop to process all open nodes.
+ *
+ * For each node, determine the rate at which it should
+ * be transmitting data. Then if the node should wake up
+ * and transmit data now, enable the net receive select
+ * to get the transmit going.
+ */
+
+ list_for_each_entry(nd, &nd_struct_list, list) {
+
+ lk = &nd->nd_link;
+
+ /*
+ * Decrement statistics. These are only for use with
+ * KME, so don't worry that the operations are done
+ * unlocked, and so the results are occasionally wrong.
+ */
+
+ nd->nd_read_count -= (nd->nd_read_count +
+ poll_data->poll_round) / freq;
+ nd->nd_write_count -= (nd->nd_write_count +
+ poll_data->poll_round) / freq;
+ nd->nd_send_count -= (nd->nd_send_count +
+ poll_data->poll_round) / freq;
+ nd->nd_tx_byte -= (nd->nd_tx_byte +
+ poll_data->poll_round) / freq;
+ nd->nd_rx_byte -= (nd->nd_rx_byte +
+ poll_data->poll_round) / freq;
+
+ /*
+ * Wake the daemon to transmit data only when there is
+ * enough byte credit to send data.
+ *
+ * The results are approximate because the operations
+ * are performed unlocked, and we are inspecting
+ * data asynchronously updated elsewhere. The whole
+ * thing is just approximation anyway, so that should
+ * be okay.
+ */
+
+ if (lk->lk_slow_rate >= UIO_MAX) {
+
+ nd->nd_delay = 0;
+ nd->nd_rate = UIO_MAX;
+
+ nd->nd_tx_deposit = nd->nd_tx_charge + 3 * UIO_MAX;
+ nd->nd_tx_credit = 3 * UIO_MAX;
+
+ } else {
+
+ long rate;
+ long delay;
+ long deposit;
+ long charge;
+ long size;
+ long excess;
+
+ long seq_in = nd->nd_seq_in;
+ long seq_out = nd->nd_seq_out;
+
+ /*
+ * If there are no outstanding packets, run at the
+ * fastest rate.
+ */
+
+ if (seq_in == seq_out) {
+ delay = 0;
+ rate = lk->lk_fast_rate;
+ }
+
+ /*
+ * Otherwise compute the transmit rate based on the
+ * delay since the oldest packet.
+ */
+
+ else {
+ /*
+ * The actual delay is computed as the
+ * time since the oldest unacknowledged
+ * packet was sent, minus the time it
+ * took to send that packet to the server.
+ */
+
+ delay = ((jiffies - nd->nd_seq_time[seq_out])
+ - (nd->nd_seq_size[seq_out] /
+ lk->lk_fast_rate));
+
+ /*
+ * If the delay is less than the "fast"
+ * delay, transmit full speed. If greater
+ * than the "slow" delay, transmit at the
+ * "slow" speed. In between, interpolate
+ * between the fast and slow speeds.
+ */
+
+ rate =
+ (delay <= lk->lk_fast_delay ?
+ lk->lk_fast_rate :
+ delay >= lk->lk_slow_delay ?
+ lk->lk_slow_rate :
+ (lk->lk_slow_rate +
+ (lk->lk_slow_delay - delay) *
+ (lk->lk_fast_rate - lk->lk_slow_rate) /
+ (lk->lk_slow_delay - lk->lk_fast_delay)
+ )
+ );
+ }
+
+ nd->nd_delay = delay;
+ nd->nd_rate = rate;
+
+ /*
+ * Increase the transmit credit by depositing the
+ * current transmit rate.
+ */
+
+ deposit = nd->nd_tx_deposit;
+ charge = nd->nd_tx_charge;
+
+ deposit += rate;
+
+ /*
+ * If the available transmit credit becomes too large,
+ * reduce the deposit to correct the value.
+ *
+ * Too large is the max of:
+ * 6 times the header size
+ * 3 times the current transmit rate.
+ */
+
+ size = 2 * nd->nd_link.lk_header_size;
+
+ if (size < rate)
+ size = rate;
+
+ size *= 3;
+
+ excess = deposit - charge - size;
+
+ if (excess > 0)
+ deposit -= excess;
+
+ nd->nd_tx_deposit = deposit;
+ nd->nd_tx_credit = deposit - charge;
+
+ /*
+ * Wake the transmit task only if the transmit credit
+ * is at least 3 times the transmit header size.
+ */
+
+ size = 3 * lk->lk_header_size;
+
+ if (nd->nd_tx_credit < size)
+ continue;
+ }
+
+
+ /*
+ * Enable the READ select to wake the daemon if there
+ * is useful work for the drp_read routine to perform.
+ */
+
+ if (waitqueue_active(&nd->nd_tx_waitq) &&
+ (nd->nd_tx_work != 0 ||
+ (ulong)(jiffies - nd->nd_tx_time) >= IDLE_MAX)) {
+ nd->nd_tx_ready = 1;
+
+ wake_up_interruptible(&nd->nd_tx_waitq);
+
+ /* not needed */
+ /* nd->nd_flag &= ~ND_SELECT; */
+ }
+ }
+
+
+ /*
+ * Schedule ourself back at the nominal wakeup interval.
+ */
+ spin_lock_irqsave(&poll_data->poll_lock, lock_flags);
+
+ poll_data->node_active_count--;
+ if (poll_data->node_active_count > 0) {
+ poll_data->node_active_count++;
+ poll_time = poll_data->timer.expires +
+ poll_data->poll_tick * HZ / 1000;
+
+ time = poll_time - jiffies;
+
+ if (time >= 2 * poll_data->poll_tick)
+ poll_time = jiffies + dgrp_poll_tick * HZ / 1000;
+
+ poll_data->timer.expires = poll_time;
+ add_timer(&poll_data->timer);
+ }
+
+ spin_unlock_irqrestore(&poll_data->poll_lock, lock_flags);
+}
diff --git a/drivers/staging/dgrp/dgrp_ports_ops.c b/drivers/staging/dgrp/dgrp_ports_ops.c
new file mode 100644
index 00000000000..4ce030815f2
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_ports_ops.c
@@ -0,0 +1,156 @@
+/*
+ *
+ * Copyright 1999-2000 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ * 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_ports_ops.c
+ *
+ * Description:
+ *
+ * Handle the file operations required for the /proc/dgrp/ports/...
+ * devices. Basically gathers tty status for the node and returns it.
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+
+#include "dgrp_common.h"
+
+/* File operation declarations */
+static int dgrp_ports_open(struct inode *, struct file *);
+
+const struct file_operations dgrp_ports_ops = {
+ .owner = THIS_MODULE,
+ .open = dgrp_ports_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release
+};
+
+static void *dgrp_ports_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ if (*pos == 0)
+ seq_puts(seq, "#num tty_open pr_open tot_wait MSTAT IFLAG OFLAG CFLAG BPS DIGIFLAGS\n");
+
+ return pos;
+}
+
+static void *dgrp_ports_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct nd_struct *nd = seq->private;
+
+ if (*pos >= nd->nd_chan_count)
+ return NULL;
+
+ *pos += 1;
+
+ return pos;
+}
+
+static void dgrp_ports_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+static int dgrp_ports_seq_show(struct seq_file *seq, void *v)
+{
+ loff_t *pos = v;
+ struct nd_struct *nd;
+ struct ch_struct *ch;
+ struct un_struct *tun, *pun;
+ unsigned int totcnt;
+
+ nd = seq->private;
+ if (!nd)
+ return 0;
+
+ if (*pos >= nd->nd_chan_count)
+ return 0;
+
+ ch = &nd->nd_chan[*pos];
+ tun = &ch->ch_tun;
+ pun = &ch->ch_pun;
+
+ /*
+ * If port is not open and no one is waiting to
+ * open it, the modem signal values can't be
+ * trusted, and will be zeroed.
+ */
+ totcnt = tun->un_open_count +
+ pun->un_open_count +
+ ch->ch_wait_count[0] +
+ ch->ch_wait_count[1] +
+ ch->ch_wait_count[2];
+
+ seq_printf(seq, "%02d %02d %02d %02d 0x%04X 0x%04X 0x%04X 0x%04X %-6d 0x%04X\n",
+ (int) *pos,
+ tun->un_open_count,
+ pun->un_open_count,
+ ch->ch_wait_count[0] +
+ ch->ch_wait_count[1] +
+ ch->ch_wait_count[2],
+ (totcnt ? ch->ch_s_mlast : 0),
+ ch->ch_s_iflag,
+ ch->ch_s_oflag,
+ ch->ch_s_cflag,
+ (ch->ch_s_brate ? (1843200 / ch->ch_s_brate) : 0),
+ ch->ch_digi.digi_flags);
+
+ return 0;
+}
+
+static const struct seq_operations ports_seq_ops = {
+ .start = dgrp_ports_seq_start,
+ .next = dgrp_ports_seq_next,
+ .stop = dgrp_ports_seq_stop,
+ .show = dgrp_ports_seq_show,
+};
+
+/**
+ * dgrp_ports_open -- open the /proc/dgrp/ports/... device
+ * @inode: struct inode *
+ * @file: struct file *
+ *
+ * Open function to open the /proc/dgrp/ports device for a PortServer.
+ * This is the open function for struct file_operations
+ */
+static int dgrp_ports_open(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq;
+ int rtn;
+
+ rtn = seq_open(file, &ports_seq_ops);
+ if (!rtn) {
+ seq = file->private_data;
+ seq->private = PDE_DATA(inode);
+ }
+
+ return rtn;
+}
diff --git a/drivers/staging/dgrp/dgrp_specproc.c b/drivers/staging/dgrp/dgrp_specproc.c
new file mode 100644
index 00000000000..205d80ef445
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_specproc.c
@@ -0,0 +1,541 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * James Puzzo <jamesp at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_specproc.c
+ *
+ * Description:
+ *
+ * Handle the "config" proc entry for the linux realport device driver
+ * and provide slots for the "net" and "mon" devices
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include "dgrp_common.h"
+
+static struct proc_dir_entry *dgrp_proc_dir_entry;
+
+static int dgrp_add_id(long id);
+static int dgrp_remove_nd(struct nd_struct *nd);
+static struct proc_dir_entry *add_proc_file(struct nd_struct *node,
+ struct proc_dir_entry *root,
+ const struct file_operations *fops);
+
+/* File operation declarations */
+static int parse_write_config(char *);
+
+static ssize_t dgrp_config_proc_write(struct file *file,
+ const char __user *buffer,
+ size_t count, loff_t *pos);
+
+static int dgrp_nodeinfo_proc_open(struct inode *inode, struct file *file);
+static int dgrp_info_proc_open(struct inode *inode, struct file *file);
+static int dgrp_config_proc_open(struct inode *inode, struct file *file);
+
+static const struct file_operations config_proc_file_ops = {
+ .owner = THIS_MODULE,
+ .open = dgrp_config_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+ .write = dgrp_config_proc_write,
+};
+
+static const struct file_operations info_proc_file_ops = {
+ .owner = THIS_MODULE,
+ .open = dgrp_info_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations nodeinfo_proc_file_ops = {
+ .owner = THIS_MODULE,
+ .open = dgrp_nodeinfo_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+static struct proc_dir_entry *net_entry_pointer;
+static struct proc_dir_entry *mon_entry_pointer;
+static struct proc_dir_entry *dpa_entry_pointer;
+static struct proc_dir_entry *ports_entry_pointer;
+
+static void remove_files(struct nd_struct *nd)
+{
+ char buf[3];
+ ID_TO_CHAR(nd->nd_ID, buf);
+ dgrp_remove_node_class_sysfs_files(nd);
+ if (nd->nd_net_de)
+ remove_proc_entry(buf, net_entry_pointer);
+ if (nd->nd_mon_de)
+ remove_proc_entry(buf, mon_entry_pointer);
+ if (nd->nd_dpa_de)
+ remove_proc_entry(buf, dpa_entry_pointer);
+ if (nd->nd_ports_de)
+ remove_proc_entry(buf, ports_entry_pointer);
+}
+
+void dgrp_unregister_proc(void)
+{
+ net_entry_pointer = NULL;
+ mon_entry_pointer = NULL;
+ dpa_entry_pointer = NULL;
+ ports_entry_pointer = NULL;
+
+ if (dgrp_proc_dir_entry) {
+ struct nd_struct *nd;
+ list_for_each_entry(nd, &nd_struct_list, list)
+ remove_files(nd);
+ remove_proc_entry("dgrp/config", NULL);
+ remove_proc_entry("dgrp/info", NULL);
+ remove_proc_entry("dgrp/nodeinfo", NULL);
+ remove_proc_entry("dgrp/net", NULL);
+ remove_proc_entry("dgrp/mon", NULL);
+ remove_proc_entry("dgrp/dpa", NULL);
+ remove_proc_entry("dgrp/ports", NULL);
+ remove_proc_entry("dgrp", NULL);
+ dgrp_proc_dir_entry = NULL;
+ }
+}
+
+void dgrp_register_proc(void)
+{
+ /*
+ * Register /proc/dgrp
+ */
+ dgrp_proc_dir_entry = proc_mkdir("dgrp", NULL);
+ if (!dgrp_proc_dir_entry)
+ return;
+ proc_create("dgrp/config", 0644, NULL, &config_proc_file_ops);
+ proc_create("dgrp/info", 0644, NULL, &info_proc_file_ops);
+ proc_create("dgrp/nodeinfo", 0644, NULL, &nodeinfo_proc_file_ops);
+ net_entry_pointer = proc_mkdir_mode("dgrp/net", 0500, NULL);
+ mon_entry_pointer = proc_mkdir_mode("dgrp/mon", 0500, NULL);
+ dpa_entry_pointer = proc_mkdir_mode("dgrp/dpa", 0500, NULL);
+ ports_entry_pointer = proc_mkdir_mode("dgrp/ports", 0500, NULL);
+}
+
+static void *dgrp_config_proc_start(struct seq_file *m, loff_t *pos)
+{
+ return seq_list_start_head(&nd_struct_list, *pos);
+}
+
+static void *dgrp_config_proc_next(struct seq_file *p, void *v, loff_t *pos)
+{
+ return seq_list_next(v, &nd_struct_list, pos);
+}
+
+static void dgrp_config_proc_stop(struct seq_file *m, void *v)
+{
+}
+
+static int dgrp_config_proc_show(struct seq_file *m, void *v)
+{
+ struct nd_struct *nd;
+ char tmp_id[4];
+
+ if (v == &nd_struct_list) {
+ seq_puts(m, "#-----------------------------------------------------------------------------\n");
+ seq_puts(m, "# Avail\n");
+ seq_puts(m, "# ID Major State Ports\n");
+ return 0;
+ }
+
+ nd = list_entry(v, struct nd_struct, list);
+
+ ID_TO_CHAR(nd->nd_ID, tmp_id);
+
+ seq_printf(m, " %-2.2s %-5ld %-10.10s %-5d\n",
+ tmp_id,
+ nd->nd_major,
+ ND_STATE_STR(nd->nd_state),
+ nd->nd_chan_count);
+
+ return 0;
+}
+
+static const struct seq_operations proc_config_ops = {
+ .start = dgrp_config_proc_start,
+ .next = dgrp_config_proc_next,
+ .stop = dgrp_config_proc_stop,
+ .show = dgrp_config_proc_show,
+};
+
+static int dgrp_config_proc_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &proc_config_ops);
+}
+
+
+/*
+ * When writing configuration information, each "record" (i.e. each
+ * write) is treated as an independent request. See the "parse"
+ * description for more details.
+ */
+static ssize_t dgrp_config_proc_write(struct file *file,
+ const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ ssize_t retval;
+ char *inbuf, *sp;
+ char *line, *ldelim;
+
+ if (count > 32768)
+ return -EINVAL;
+
+ inbuf = sp = vzalloc(count + 1);
+ if (!inbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(inbuf, buffer, count)) {
+ retval = -EFAULT;
+ goto done;
+ }
+
+ inbuf[count] = 0;
+
+ ldelim = "\n";
+
+ line = strpbrk(sp, ldelim);
+ while (line) {
+ *line = 0;
+ retval = parse_write_config(sp);
+ if (retval)
+ goto done;
+
+ sp = line + 1;
+ line = strpbrk(sp, ldelim);
+ }
+
+ retval = count;
+done:
+ vfree(inbuf);
+ return retval;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ * The following are the functions to parse input
+ *
+ * ------------------------------------------------------------------------
+ */
+static inline char *skip_past_ws(const char *str)
+{
+ while ((*str) && !isspace(*str))
+ ++str;
+
+ return skip_spaces(str);
+}
+
+static int parse_id(char **c, char *cID)
+{
+ int tmp = **c;
+
+ if (isalnum(tmp) || (tmp == '_'))
+ cID[0] = tmp;
+ else
+ return -EINVAL;
+
+ (*c)++; tmp = **c;
+
+ if (isalnum(tmp) || (tmp == '_')) {
+ cID[1] = tmp;
+ (*c)++;
+ } else
+ cID[1] = 0;
+
+ return 0;
+}
+
+static int parse_add_config(char *buf)
+{
+ char *c = buf;
+ int retval;
+ char cID[2];
+ long ID;
+
+ c = skip_past_ws(c);
+
+ retval = parse_id(&c, cID);
+ if (retval < 0)
+ return retval;
+
+ ID = CHAR_TO_ID(cID);
+
+ c = skip_past_ws(c);
+
+ return dgrp_add_id(ID);
+}
+
+static int parse_del_config(char *buf)
+{
+ char *c = buf;
+ int retval;
+ struct nd_struct *nd;
+ char cID[2];
+ long ID;
+ long major;
+
+ c = skip_past_ws(c);
+
+ retval = parse_id(&c, cID);
+ if (retval < 0)
+ return retval;
+
+ ID = CHAR_TO_ID(cID);
+
+ c = skip_past_ws(c);
+
+ retval = kstrtol(c, 10, &major);
+ if (retval)
+ return retval;
+
+ nd = nd_struct_get(major);
+ if (!nd)
+ return -EINVAL;
+
+ if ((nd->nd_major != major) || (nd->nd_ID != ID))
+ return -EINVAL;
+
+ return dgrp_remove_nd(nd);
+}
+
+static int parse_chg_config(char *buf)
+{
+ return -EINVAL;
+}
+
+/*
+ * The passed character buffer represents a single configuration request.
+ * If the first character is a "+", it is parsed as a request to add a
+ * PortServer
+ * If the first character is a "-", it is parsed as a request to delete a
+ * PortServer
+ * If the first character is a "*", it is parsed as a request to change a
+ * PortServer
+ * Any other character (including whitespace) causes the record to be
+ * ignored.
+ */
+static int parse_write_config(char *buf)
+{
+ int retval;
+
+ switch (buf[0]) {
+ case '+':
+ retval = parse_add_config(buf);
+ break;
+ case '-':
+ retval = parse_del_config(buf);
+ break;
+ case '*':
+ retval = parse_chg_config(buf);
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+static int dgrp_info_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "version: %s\n", DIGI_VERSION);
+ seq_puts(m, "register_with_sysfs: 1\n");
+ seq_printf(m, "pollrate: 0x%08x\t(%d)\n",
+ dgrp_poll_tick, dgrp_poll_tick);
+
+ return 0;
+}
+
+static int dgrp_info_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dgrp_info_proc_show, NULL);
+}
+
+
+static void *dgrp_nodeinfo_start(struct seq_file *m, loff_t *pos)
+{
+ return seq_list_start_head(&nd_struct_list, *pos);
+}
+
+static void *dgrp_nodeinfo_next(struct seq_file *p, void *v, loff_t *pos)
+{
+ return seq_list_next(v, &nd_struct_list, pos);
+}
+
+static void dgrp_nodeinfo_stop(struct seq_file *m, void *v)
+{
+}
+
+static int dgrp_nodeinfo_show(struct seq_file *m, void *v)
+{
+ struct nd_struct *nd;
+ char hwver[8];
+ char swver[8];
+ char tmp_id[4];
+
+ if (v == &nd_struct_list) {
+ seq_puts(m, "#-----------------------------------------------------------------------------\n");
+ seq_puts(m, "# HW HW SW\n");
+ seq_puts(m, "# ID State Version ID Version Description\n");
+ return 0;
+ }
+
+ nd = list_entry(v, struct nd_struct, list);
+
+ ID_TO_CHAR(nd->nd_ID, tmp_id);
+
+ if (nd->nd_state == NS_READY) {
+ sprintf(hwver, "%d.%d", (nd->nd_hw_ver >> 8) & 0xff,
+ nd->nd_hw_ver & 0xff);
+ sprintf(swver, "%d.%d", (nd->nd_sw_ver >> 8) & 0xff,
+ nd->nd_sw_ver & 0xff);
+ seq_printf(m, " %-2.2s %-10.10s %-7.7s %-3d %-7.7s %-35.35s\n",
+ tmp_id,
+ ND_STATE_STR(nd->nd_state),
+ hwver,
+ nd->nd_hw_id,
+ swver,
+ nd->nd_ps_desc);
+
+ } else {
+ seq_printf(m, " %-2.2s %-10.10s\n",
+ tmp_id,
+ ND_STATE_STR(nd->nd_state));
+ }
+
+ return 0;
+}
+
+
+static const struct seq_operations nodeinfo_ops = {
+ .start = dgrp_nodeinfo_start,
+ .next = dgrp_nodeinfo_next,
+ .stop = dgrp_nodeinfo_stop,
+ .show = dgrp_nodeinfo_show,
+};
+
+static int dgrp_nodeinfo_proc_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &nodeinfo_ops);
+}
+
+/**
+ * dgrp_add_id() -- creates new nd struct and adds it to list
+ * @id: id of device to add
+ */
+static int dgrp_add_id(long id)
+{
+ struct nd_struct *nd;
+ int ret;
+ int i;
+
+ nd = kzalloc(sizeof(struct nd_struct), GFP_KERNEL);
+ if (!nd)
+ return -ENOMEM;
+
+ nd->nd_major = 0;
+ nd->nd_ID = id;
+
+ spin_lock_init(&nd->nd_lock);
+
+ init_waitqueue_head(&nd->nd_tx_waitq);
+ init_waitqueue_head(&nd->nd_mon_wqueue);
+ init_waitqueue_head(&nd->nd_dpa_wqueue);
+ sema_init(&nd->nd_mon_semaphore, 1);
+ sema_init(&nd->nd_net_semaphore, 1);
+ spin_lock_init(&nd->nd_dpa_lock);
+ nd->nd_state = NS_CLOSED;
+ for (i = 0; i < SEQ_MAX; i++)
+ init_waitqueue_head(&nd->nd_seq_wque[i]);
+
+ /* setup the structures to get the major number */
+ ret = dgrp_tty_init(nd);
+ if (ret)
+ goto error_out;
+
+ nd->nd_major = nd->nd_serial_ttdriver->major;
+
+ ret = nd_struct_add(nd);
+ if (ret)
+ goto error_out;
+
+ dgrp_create_node_class_sysfs_files(nd);
+ nd->nd_net_de = add_proc_file(nd, net_entry_pointer, &dgrp_net_ops);
+ nd->nd_mon_de = add_proc_file(nd, mon_entry_pointer, &dgrp_mon_ops);
+ nd->nd_dpa_de = add_proc_file(nd, dpa_entry_pointer, &dgrp_dpa_ops);
+ nd->nd_ports_de = add_proc_file(nd, ports_entry_pointer,
+ &dgrp_ports_ops);
+ return 0;
+
+ /* FIXME this guy should free the tty driver stored in nd and destroy
+ * all channel ports */
+error_out:
+ kfree(nd);
+ return ret;
+
+}
+
+static int dgrp_remove_nd(struct nd_struct *nd)
+{
+ int ret;
+
+ /* Check to see if the selected structure is in use */
+ if (nd->nd_tty_ref_cnt)
+ return -EBUSY;
+
+ remove_files(nd);
+
+ dgrp_tty_uninit(nd);
+
+ ret = nd_struct_del(nd);
+ if (ret)
+ return ret;
+
+ kfree(nd);
+ return 0;
+}
+
+static struct proc_dir_entry *add_proc_file(struct nd_struct *node,
+ struct proc_dir_entry *root,
+ const struct file_operations *fops)
+{
+ char buf[3];
+ ID_TO_CHAR(node->nd_ID, buf);
+ return proc_create_data(buf, 0600, root, fops, node);
+}
diff --git a/drivers/staging/dgrp/dgrp_sysfs.c b/drivers/staging/dgrp/dgrp_sysfs.c
new file mode 100644
index 00000000000..2f9345ff0ab
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_sysfs.c
@@ -0,0 +1,558 @@
+/*
+ * Copyright 2004 Digi International (www.digi.com)
+ * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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 "dgrp_common.h"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/serial_reg.h>
+#include <linux/pci.h>
+#include <linux/kdev_t.h>
+
+
+#define PORTSERVER_DIVIDEND 1843200
+#define SERIAL_TYPE_NORMAL 1
+#define SERIAL_TYPE_CALLOUT 2
+#define SERIAL_TYPE_XPRINT 3
+
+
+static struct class *dgrp_class;
+static struct device *dgrp_class_nodes_dev;
+static struct device *dgrp_class_global_settings_dev;
+
+
+static ssize_t dgrp_class_version_show(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%s\n", DIGI_VERSION);
+}
+static CLASS_ATTR(driver_version, 0400, dgrp_class_version_show, NULL);
+
+
+static ssize_t dgrp_class_register_with_sysfs_show(struct device *c,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "1\n");
+}
+static DEVICE_ATTR(register_with_sysfs, 0400,
+ dgrp_class_register_with_sysfs_show, NULL);
+
+
+static ssize_t dgrp_class_pollrate_show(struct device *c,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", dgrp_poll_tick);
+}
+
+static ssize_t dgrp_class_pollrate_store(struct device *c,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (sscanf(buf, "0x%x\n", &dgrp_poll_tick) != 1)
+ return -EINVAL;
+
+ return count;
+}
+static DEVICE_ATTR(pollrate, 0600, dgrp_class_pollrate_show,
+ dgrp_class_pollrate_store);
+
+static struct attribute *dgrp_sysfs_global_settings_entries[] = {
+ &dev_attr_pollrate.attr,
+ &dev_attr_register_with_sysfs.attr,
+ NULL
+};
+
+
+static struct attribute_group dgrp_global_settings_attribute_group = {
+ .name = NULL,
+ .attrs = dgrp_sysfs_global_settings_entries,
+};
+
+
+
+int dgrp_create_class_sysfs_files(void)
+{
+ int ret = 0;
+ int max_majors = 1U << (32 - MINORBITS);
+
+ dgrp_class = class_create(THIS_MODULE, "digi_realport");
+ if (IS_ERR(dgrp_class))
+ return PTR_ERR(dgrp_class);
+ ret = class_create_file(dgrp_class, &class_attr_driver_version);
+ if (ret)
+ goto err_class;
+
+ dgrp_class_global_settings_dev = device_create(dgrp_class, NULL,
+ MKDEV(0, max_majors + 1), NULL, "driver_settings");
+ if (IS_ERR(dgrp_class_global_settings_dev)) {
+ ret = PTR_ERR(dgrp_class_global_settings_dev);
+ goto err_file;
+ }
+ ret = sysfs_create_group(&dgrp_class_global_settings_dev->kobj,
+ &dgrp_global_settings_attribute_group);
+ if (ret) {
+ pr_alert("%s: failed to create sysfs global settings device attributes.\n",
+ __func__);
+ goto err_dev1;
+ }
+
+ dgrp_class_nodes_dev = device_create(dgrp_class, NULL,
+ MKDEV(0, max_majors + 2), NULL, "nodes");
+ if (IS_ERR(dgrp_class_nodes_dev)) {
+ ret = PTR_ERR(dgrp_class_nodes_dev);
+ goto err_group;
+ }
+
+ return 0;
+err_group:
+ sysfs_remove_group(&dgrp_class_global_settings_dev->kobj,
+ &dgrp_global_settings_attribute_group);
+err_dev1:
+ device_destroy(dgrp_class, MKDEV(0, max_majors + 1));
+err_file:
+ class_remove_file(dgrp_class, &class_attr_driver_version);
+err_class:
+ class_destroy(dgrp_class);
+ return ret;
+}
+
+
+void dgrp_remove_class_sysfs_files(void)
+{
+ struct nd_struct *nd;
+ int max_majors = 1U << (32 - MINORBITS);
+
+ list_for_each_entry(nd, &nd_struct_list, list)
+ dgrp_remove_node_class_sysfs_files(nd);
+
+ sysfs_remove_group(&dgrp_class_global_settings_dev->kobj,
+ &dgrp_global_settings_attribute_group);
+
+ class_remove_file(dgrp_class, &class_attr_driver_version);
+
+ device_destroy(dgrp_class, MKDEV(0, max_majors + 1));
+ device_destroy(dgrp_class, MKDEV(0, max_majors + 2));
+ class_destroy(dgrp_class);
+}
+
+static ssize_t dgrp_node_state_show(struct device *c,
+ struct device_attribute *attr, char *buf)
+{
+ struct nd_struct *nd;
+
+ if (!c)
+ return 0;
+ nd = dev_get_drvdata(c);
+ if (!nd)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", ND_STATE_STR(nd->nd_state));
+}
+
+static DEVICE_ATTR(state, 0600, dgrp_node_state_show, NULL);
+
+static ssize_t dgrp_node_description_show(struct device *c,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nd_struct *nd;
+
+ if (!c)
+ return 0;
+ nd = dev_get_drvdata(c);
+ if (!nd)
+ return 0;
+
+ if (nd->nd_state == NS_READY)
+ return snprintf(buf, PAGE_SIZE, "%s\n", nd->nd_ps_desc);
+ return 0;
+}
+static DEVICE_ATTR(description_info, 0600, dgrp_node_description_show, NULL);
+
+static ssize_t dgrp_node_hw_version_show(struct device *c,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nd_struct *nd;
+
+ if (!c)
+ return 0;
+ nd = dev_get_drvdata(c);
+ if (!nd)
+ return 0;
+
+ if (nd->nd_state == NS_READY)
+ return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+ (nd->nd_hw_ver >> 8) & 0xff,
+ nd->nd_hw_ver & 0xff);
+
+ return 0;
+}
+static DEVICE_ATTR(hw_version_info, 0600, dgrp_node_hw_version_show, NULL);
+
+static ssize_t dgrp_node_hw_id_show(struct device *c,
+ struct device_attribute *attr, char *buf)
+{
+ struct nd_struct *nd;
+
+ if (!c)
+ return 0;
+ nd = dev_get_drvdata(c);
+ if (!nd)
+ return 0;
+
+
+ if (nd->nd_state == NS_READY)
+ return snprintf(buf, PAGE_SIZE, "%d\n", nd->nd_hw_id);
+ return 0;
+}
+static DEVICE_ATTR(hw_id_info, 0600, dgrp_node_hw_id_show, NULL);
+
+static ssize_t dgrp_node_sw_version_show(struct device *c,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct nd_struct *nd;
+
+ if (!c)
+ return 0;
+
+ nd = dev_get_drvdata(c);
+ if (!nd)
+ return 0;
+
+ if (nd->nd_state == NS_READY)
+ return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+ (nd->nd_sw_ver >> 8) & 0xff,
+ nd->nd_sw_ver & 0xff);
+
+ return 0;
+}
+static DEVICE_ATTR(sw_version_info, 0600, dgrp_node_sw_version_show, NULL);
+
+
+static struct attribute *dgrp_sysfs_node_entries[] = {
+ &dev_attr_state.attr,
+ &dev_attr_description_info.attr,
+ &dev_attr_hw_version_info.attr,
+ &dev_attr_hw_id_info.attr,
+ &dev_attr_sw_version_info.attr,
+ NULL
+};
+
+
+static struct attribute_group dgrp_node_attribute_group = {
+ .name = NULL,
+ .attrs = dgrp_sysfs_node_entries,
+};
+
+
+void dgrp_create_node_class_sysfs_files(struct nd_struct *nd)
+{
+ int ret;
+ char name[10];
+
+ if (nd->nd_ID)
+ ID_TO_CHAR(nd->nd_ID, name);
+ else
+ sprintf(name, "node%ld", nd->nd_major);
+
+ nd->nd_class_dev = device_create(dgrp_class, dgrp_class_nodes_dev,
+ MKDEV(0, nd->nd_major), NULL, "%s", name);
+
+ ret = sysfs_create_group(&nd->nd_class_dev->kobj,
+ &dgrp_node_attribute_group);
+
+ if (ret) {
+ pr_alert("%s: failed to create sysfs node device attributes.\n",
+ __func__);
+ sysfs_remove_group(&nd->nd_class_dev->kobj,
+ &dgrp_node_attribute_group);
+ return;
+ }
+
+ dev_set_drvdata(nd->nd_class_dev, nd);
+
+}
+
+
+void dgrp_remove_node_class_sysfs_files(struct nd_struct *nd)
+{
+ if (nd->nd_class_dev) {
+ sysfs_remove_group(&nd->nd_class_dev->kobj,
+ &dgrp_node_attribute_group);
+
+ device_destroy(dgrp_class, MKDEV(0, nd->nd_major));
+ nd->nd_class_dev = NULL;
+ }
+}
+
+
+
+static ssize_t dgrp_tty_state_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ un->un_open_count ? "Open" : "Closed");
+}
+static DEVICE_ATTR(state_info, 0600, dgrp_tty_state_show, NULL);
+
+static ssize_t dgrp_tty_baud_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ un->un_open_count ? (PORTSERVER_DIVIDEND / ch->ch_s_brate) : 0);
+}
+static DEVICE_ATTR(baud_info, 0400, dgrp_tty_baud_show, NULL);
+
+
+static ssize_t dgrp_tty_msignals_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+
+ if (ch->ch_open_count) {
+ return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n",
+ (ch->ch_s_mlast & DM_RTS) ? "RTS" : "",
+ (ch->ch_s_mlast & DM_CTS) ? "CTS" : "",
+ (ch->ch_s_mlast & DM_DTR) ? "DTR" : "",
+ (ch->ch_s_mlast & DM_DSR) ? "DSR" : "",
+ (ch->ch_s_mlast & DM_CD) ? "DCD" : "",
+ (ch->ch_s_mlast & DM_RI) ? "RI" : "");
+ }
+ return 0;
+}
+static DEVICE_ATTR(msignals_info, 0400, dgrp_tty_msignals_show, NULL);
+
+
+static ssize_t dgrp_tty_iflag_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_iflag);
+}
+static DEVICE_ATTR(iflag_info, 0600, dgrp_tty_iflag_show, NULL);
+
+
+static ssize_t dgrp_tty_cflag_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_cflag);
+}
+static DEVICE_ATTR(cflag_info, 0600, dgrp_tty_cflag_show, NULL);
+
+
+static ssize_t dgrp_tty_oflag_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_oflag);
+}
+static DEVICE_ATTR(oflag_info, 0600, dgrp_tty_oflag_show, NULL);
+
+
+static ssize_t dgrp_tty_digi_flag_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags);
+}
+static DEVICE_ATTR(digi_flag_info, 0600, dgrp_tty_digi_flag_show, NULL);
+
+
+static ssize_t dgrp_tty_rxcount_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_rxcount);
+}
+static DEVICE_ATTR(rxcount_info, 0600, dgrp_tty_rxcount_show, NULL);
+
+
+static ssize_t dgrp_tty_txcount_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_txcount);
+}
+static DEVICE_ATTR(txcount_info, 0600, dgrp_tty_txcount_show, NULL);
+
+
+static ssize_t dgrp_tty_name_show(struct device *d,
+ struct device_attribute *attr, char *buf)
+{
+ struct nd_struct *nd;
+ struct ch_struct *ch;
+ struct un_struct *un;
+ char name[10];
+
+ if (!d)
+ return 0;
+ un = dev_get_drvdata(d);
+ if (!un)
+ return 0;
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+ nd = ch->ch_nd;
+ if (!nd)
+ return 0;
+
+ ID_TO_CHAR(nd->nd_ID, name);
+
+ return snprintf(buf, PAGE_SIZE, "%s%s%02d\n",
+ un->un_type == SERIAL_TYPE_XPRINT ? "pr" : "tty",
+ name, ch->ch_portnum);
+}
+static DEVICE_ATTR(custom_name, 0600, dgrp_tty_name_show, NULL);
+
+
+static struct attribute *dgrp_sysfs_tty_entries[] = {
+ &dev_attr_state_info.attr,
+ &dev_attr_baud_info.attr,
+ &dev_attr_msignals_info.attr,
+ &dev_attr_iflag_info.attr,
+ &dev_attr_cflag_info.attr,
+ &dev_attr_oflag_info.attr,
+ &dev_attr_digi_flag_info.attr,
+ &dev_attr_rxcount_info.attr,
+ &dev_attr_txcount_info.attr,
+ &dev_attr_custom_name.attr,
+ NULL
+};
+
+
+static struct attribute_group dgrp_tty_attribute_group = {
+ .name = NULL,
+ .attrs = dgrp_sysfs_tty_entries,
+};
+
+
+void dgrp_create_tty_sysfs(struct un_struct *un, struct device *c)
+{
+ int ret;
+
+ ret = sysfs_create_group(&c->kobj, &dgrp_tty_attribute_group);
+ if (ret) {
+ pr_alert("%s: failed to create sysfs tty device attributes.\n",
+ __func__);
+ sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group);
+ return;
+ }
+
+ dev_set_drvdata(c, un);
+
+}
+
+
+void dgrp_remove_tty_sysfs(struct device *c)
+{
+ sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group);
+}
diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c
new file mode 100644
index 00000000000..30d26029b21
--- /dev/null
+++ b/drivers/staging/dgrp/dgrp_tty.c
@@ -0,0 +1,3337 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * Gene Olson <Gene_Olson at digi dot com>
+ * James Puzzo <jamesp at digi dot com>
+ * Jeff Randall
+ * Scott Kilau <scottk at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/*
+ *
+ * Filename:
+ *
+ * dgrp_tty.c
+ *
+ * Description:
+ *
+ * This file implements the tty driver functionality for the
+ * RealPort driver software.
+ *
+ * Author:
+ *
+ * James A. Puzzo
+ * Ann-Marie Westgate
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+
+#include "dgrp_common.h"
+
+#ifndef _POSIX_VDISABLE
+#define _POSIX_VDISABLE ('\0')
+#endif
+
+/*
+ * forward declarations
+ */
+
+static void drp_param(struct ch_struct *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+
+/* ioctl helper functions */
+static int set_modem_info(struct ch_struct *, unsigned int, unsigned int *);
+static int get_modem_info(struct ch_struct *, unsigned int *);
+static void dgrp_set_custom_speed(struct ch_struct *, int);
+static int dgrp_tty_digigetedelay(struct tty_struct *, int *);
+static int dgrp_tty_digisetedelay(struct tty_struct *, int *);
+static int dgrp_send_break(struct ch_struct *, int);
+
+static ushort tty_to_ch_flags(struct tty_struct *, char);
+static tcflag_t ch_to_tty_flags(unsigned short, char);
+
+static void dgrp_tty_input_start(struct tty_struct *);
+static void dgrp_tty_input_stop(struct tty_struct *);
+
+static void drp_wmove(struct ch_struct *, int, void*, int);
+
+static int dgrp_tty_open(struct tty_struct *, struct file *);
+static void dgrp_tty_close(struct tty_struct *, struct file *);
+static int dgrp_tty_write(struct tty_struct *, const unsigned char *, int);
+static int dgrp_tty_write_room(struct tty_struct *);
+static void dgrp_tty_flush_buffer(struct tty_struct *);
+static int dgrp_tty_chars_in_buffer(struct tty_struct *);
+static int dgrp_tty_ioctl(struct tty_struct *, unsigned int, unsigned long);
+static void dgrp_tty_set_termios(struct tty_struct *, struct ktermios *);
+static void dgrp_tty_stop(struct tty_struct *);
+static void dgrp_tty_start(struct tty_struct *);
+static void dgrp_tty_throttle(struct tty_struct *);
+static void dgrp_tty_unthrottle(struct tty_struct *);
+static void dgrp_tty_hangup(struct tty_struct *);
+static int dgrp_tty_put_char(struct tty_struct *, unsigned char);
+static int dgrp_tty_tiocmget(struct tty_struct *);
+static int dgrp_tty_tiocmset(struct tty_struct *, unsigned int, unsigned int);
+static int dgrp_tty_send_break(struct tty_struct *, int);
+static void dgrp_tty_send_xchar(struct tty_struct *, char);
+
+/*
+ * tty defines
+ */
+#define SERIAL_TYPE_NORMAL 1
+#define SERIAL_TYPE_CALLOUT 2
+#define SERIAL_TYPE_XPRINT 3
+
+
+/*
+ * tty globals/statics
+ */
+
+
+#define PORTSERVER_DIVIDEND 1843200
+
+/*
+ * Default transparent print information.
+ */
+static struct digi_struct digi_init = {
+ .digi_flags = DIGI_COOK, /* Flags */
+ .digi_maxcps = 100, /* Max CPS */
+ .digi_maxchar = 50, /* Max chars in print queue */
+ .digi_bufsize = 100, /* Printer buffer size */
+ .digi_onlen = 4, /* size of printer on string */
+ .digi_offlen = 4, /* size of printer off string */
+ .digi_onstr = "\033[5i", /* ANSI printer on string */
+ .digi_offstr = "\033[4i", /* ANSI printer off string */
+ .digi_term = "ansi" /* default terminal type */
+};
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially.
+ *
+ * This defines a raw port at 9600 baud, 8 data bits, no parity,
+ * 1 stop bit.
+ */
+static struct ktermios DefaultTermios = {
+ .c_iflag = (ICRNL | IXON),
+ .c_oflag = (OPOST | ONLCR),
+ .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+ .c_lflag = (ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL
+ | ECHOKE | IEXTEN),
+ .c_cc = INIT_C_CC,
+ .c_line = 0,
+};
+
+/* Define our tty operations struct */
+static const struct tty_operations dgrp_tty_ops = {
+ .open = dgrp_tty_open,
+ .close = dgrp_tty_close,
+ .write = dgrp_tty_write,
+ .write_room = dgrp_tty_write_room,
+ .flush_buffer = dgrp_tty_flush_buffer,
+ .chars_in_buffer = dgrp_tty_chars_in_buffer,
+ .flush_chars = NULL,
+ .ioctl = dgrp_tty_ioctl,
+ .set_termios = dgrp_tty_set_termios,
+ .stop = dgrp_tty_stop,
+ .start = dgrp_tty_start,
+ .throttle = dgrp_tty_throttle,
+ .unthrottle = dgrp_tty_unthrottle,
+ .hangup = dgrp_tty_hangup,
+ .put_char = dgrp_tty_put_char,
+ .tiocmget = dgrp_tty_tiocmget,
+ .tiocmset = dgrp_tty_tiocmset,
+ .break_ctl = dgrp_tty_send_break,
+ .send_xchar = dgrp_tty_send_xchar
+};
+
+
+static int calc_baud_rate(struct un_struct *un)
+{
+ int i;
+ int brate;
+
+ struct baud_rates {
+ unsigned int rate;
+ unsigned int cflag;
+ };
+
+ static struct baud_rates baud_rates[] = {
+ { 921600, B921600 },
+ { 460800, B460800 },
+ { 230400, B230400 },
+ { 115200, B115200 },
+ { 57600, B57600 },
+ { 38400, B38400 },
+ { 19200, B19200 },
+ { 9600, B9600 },
+ { 4800, B4800 },
+ { 2400, B2400 },
+ { 1200, B1200 },
+ { 600, B600 },
+ { 300, B300 },
+ { 200, B200 },
+ { 150, B150 },
+ { 134, B134 },
+ { 110, B110 },
+ { 75, B75 },
+ { 50, B50 },
+ { 0, B9600 }
+ };
+
+ brate = C_BAUD(un->un_tty);
+
+ for (i = 0; baud_rates[i].rate; i++) {
+ if (baud_rates[i].cflag == brate)
+ break;
+ }
+
+ return baud_rates[i].rate;
+}
+
+static int calc_fastbaud_rate(struct un_struct *un, struct ktermios *uts)
+{
+ int i;
+ int brate;
+
+ ulong bauds[2][16] = {
+ { /* fastbaud*/
+ 0, 57600, 76800, 115200,
+ 131657, 153600, 230400, 460800,
+ 921600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 },
+ { /* fastbaud & CBAUDEX */
+ 0, 57600, 115200, 230400,
+ 460800, 150, 200, 921600,
+ 600, 1200, 1800, 2400,
+ 4800, 9600, 19200, 38400 }
+ };
+
+ brate = C_BAUD(un->un_tty) & 0xff;
+
+ i = (uts->c_cflag & CBAUDEX) ? 1 : 0;
+
+
+ if ((i >= 0) && (i < 2) && (brate >= 0) && (brate < 16))
+ brate = bauds[i][brate];
+ else
+ brate = 0;
+
+ return brate;
+}
+
+/**
+ * drp_param() -- send parameter values to be sent to the node
+ * @ch: channel structure of port to modify
+ *
+ * Interprets the tty and modem changes made by an application
+ * program (by examining the termios structures) and sets up
+ * parameter values to be sent to the node.
+ */
+static void drp_param(struct ch_struct *ch)
+{
+ struct nd_struct *nd;
+ struct un_struct *un;
+ int brate;
+ int mflow;
+ int xflag;
+ int iflag;
+ struct ktermios *tts, *pts, *uts;
+
+ nd = ch->ch_nd;
+
+ /*
+ * If the terminal device is open, use it to set up all tty
+ * modes and functions. Otherwise use the printer device.
+ */
+
+ if (ch->ch_tun.un_open_count) {
+
+ un = &ch->ch_tun;
+ tts = &ch->ch_tun.un_tty->termios;
+
+ /*
+ * If both devices are open, copy critical line
+ * parameters from the tty device to the printer,
+ * so that if the tty is closed, the printer will
+ * continue without disruption.
+ */
+
+ if (ch->ch_pun.un_open_count) {
+
+ pts = &ch->ch_pun.un_tty->termios;
+
+ pts->c_cflag ^=
+ (pts->c_cflag ^ tts->c_cflag) &
+ (CBAUD | CSIZE | CSTOPB | CREAD | PARENB |
+ PARODD | HUPCL | CLOCAL);
+
+ pts->c_iflag ^=
+ (pts->c_iflag ^ tts->c_iflag) &
+ (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK |
+ ISTRIP | IXON | IXANY | IXOFF);
+
+ pts->c_cc[VSTART] = tts->c_cc[VSTART];
+ pts->c_cc[VSTOP] = tts->c_cc[VSTOP];
+ }
+ } else if (ch->ch_pun.un_open_count == 0) {
+ pr_warn("%s - ch_pun.un_open_count shouldn't be 0\n",
+ __func__);
+ return;
+ } else {
+ un = &ch->ch_pun;
+ }
+
+ uts = &un->un_tty->termios;
+
+ /*
+ * Determine if FAST writes can be performed.
+ */
+
+ if ((ch->ch_digi.digi_flags & DIGI_COOK) != 0 &&
+ (ch->ch_tun.un_open_count != 0) &&
+ !((un->un_tty)->ldisc->ops->flags & LDISC_FLAG_DEFINED) &&
+ !(L_XCASE(un->un_tty))) {
+ ch->ch_flag |= CH_FAST_WRITE;
+ } else {
+ ch->ch_flag &= ~CH_FAST_WRITE;
+ }
+
+ /*
+ * If FAST writes can be performed, and OPOST is on in the
+ * terminal device, do OPOST handling in the server.
+ */
+
+ if ((ch->ch_flag & CH_FAST_WRITE) &&
+ O_OPOST(un->un_tty) != 0) {
+ int oflag = tty_to_ch_flags(un->un_tty, 'o');
+
+ /* add to ch_ocook any processing flags set in the termio */
+ ch->ch_ocook |= oflag & (OF_OLCUC |
+ OF_ONLCR |
+ OF_OCRNL |
+ OF_ONLRET |
+ OF_TABDLY);
+
+ /*
+ * the hpux driver clears any flags set in ch_ocook
+ * from the termios oflag. It is STILL reported though
+ * by a TCGETA
+ */
+
+ oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+ uts->c_oflag &= ~oflag;
+
+ } else {
+ /* clear the ch->ch_ocook flag */
+ int oflag = ch_to_tty_flags(ch->ch_ocook, 'o');
+ uts->c_oflag |= oflag;
+ ch->ch_ocook = 0;
+ }
+
+ ch->ch_oflag = ch->ch_ocook;
+
+
+ ch->ch_flag &= ~CH_FAST_READ;
+
+ /*
+ * Generate channel flags
+ */
+
+ if (C_BAUD(un->un_tty) == B0) {
+ if (!(ch->ch_flag & CH_BAUD0)) {
+ /* TODO : the HPUX driver flushes line */
+ /* TODO : discipline, I assume I don't have to */
+
+ ch->ch_tout = ch->ch_tin;
+ ch->ch_rout = ch->ch_rin;
+
+ ch->ch_break_time = 0;
+
+ ch->ch_send |= RR_TX_FLUSH | RR_RX_FLUSH;
+
+ ch->ch_mout &= ~(DM_DTR | DM_RTS);
+
+ ch->ch_flag |= CH_BAUD0;
+ }
+ } else if (ch->ch_custom_speed) {
+ ch->ch_brate = PORTSERVER_DIVIDEND / ch->ch_custom_speed;
+
+ if (ch->ch_flag & CH_BAUD0) {
+ ch->ch_mout |= DM_DTR | DM_RTS;
+
+ ch->ch_flag &= ~CH_BAUD0;
+ }
+ } else {
+ /*
+ * Baud rate mapping.
+ *
+ * If FASTBAUD isn't on, we can scan the new baud rate list
+ * as required.
+ *
+ * However, if FASTBAUD is on, we must go to the old
+ * baud rate mapping that existed many many moons ago,
+ * for compatibility reasons.
+ */
+
+ if (!(ch->ch_digi.digi_flags & DIGI_FAST))
+ brate = calc_baud_rate(un);
+ else
+ brate = calc_fastbaud_rate(un, uts);
+
+ if (brate == 0)
+ brate = 9600;
+
+ ch->ch_brate = PORTSERVER_DIVIDEND / brate;
+
+ if (ch->ch_flag & CH_BAUD0) {
+ ch->ch_mout |= DM_DTR | DM_RTS;
+
+ ch->ch_flag &= ~CH_BAUD0;
+ }
+ }
+
+ /*
+ * Generate channel cflags from the termio.
+ */
+
+ ch->ch_cflag = tty_to_ch_flags(un->un_tty, 'c');
+
+ /*
+ * Generate channel iflags from the termio.
+ */
+
+ iflag = (int) tty_to_ch_flags(un->un_tty, 'i');
+
+ if (START_CHAR(un->un_tty) == _POSIX_VDISABLE ||
+ STOP_CHAR(un->un_tty) == _POSIX_VDISABLE) {
+ iflag &= ~(IF_IXON | IF_IXANY | IF_IXOFF);
+ }
+
+ ch->ch_iflag = iflag;
+
+ /*
+ * Generate flow control characters
+ */
+
+ /*
+ * From the POSIX.1 spec (7.1.2.6): "If {_POSIX_VDISABLE}
+ * is defined for the terminal device file, and the value
+ * of one of the changeable special control characters (see
+ * 7.1.1.9) is {_POSIX_VDISABLE}, that function shall be
+ * disabled, that is, no input data shall be recognized as
+ * the disabled special character."
+ *
+ * OK, so we don't ever assign S/DXB XON or XOFF to _POSIX_VDISABLE.
+ */
+
+ if (uts->c_cc[VSTART] != _POSIX_VDISABLE)
+ ch->ch_xon = uts->c_cc[VSTART];
+ if (uts->c_cc[VSTOP] != _POSIX_VDISABLE)
+ ch->ch_xoff = uts->c_cc[VSTOP];
+
+ ch->ch_lnext = (uts->c_cc[VLNEXT] == _POSIX_VDISABLE ? 0 :
+ uts->c_cc[VLNEXT]);
+
+ /*
+ * Also, if either c_cc[START] or c_cc[STOP] is set to
+ * _POSIX_VDISABLE, we can't really do software flow
+ * control--in either direction--so we turn it off as
+ * far as S/DXB is concerned. In essence, if you disable
+ * one, you disable the other too.
+ */
+ if ((uts->c_cc[VSTART] == _POSIX_VDISABLE) ||
+ (uts->c_cc[VSTOP] == _POSIX_VDISABLE))
+ ch->ch_iflag &= ~(IF_IXOFF | IF_IXON);
+
+ /*
+ * Update xflags.
+ */
+
+ xflag = 0;
+
+ if (ch->ch_digi.digi_flags & DIGI_AIXON)
+ xflag = XF_XIXON;
+
+ if ((ch->ch_xxon == _POSIX_VDISABLE) ||
+ (ch->ch_xxoff == _POSIX_VDISABLE))
+ xflag &= ~XF_XIXON;
+
+ ch->ch_xflag = xflag;
+
+
+ /*
+ * Figure effective DCD value.
+ */
+
+ if (C_CLOCAL(un->un_tty))
+ ch->ch_flag |= CH_CLOCAL;
+ else
+ ch->ch_flag &= ~CH_CLOCAL;
+
+ /*
+ * Check modem signals
+ */
+
+ dgrp_carrier(ch);
+
+ /*
+ * Get hardware handshake value.
+ */
+
+ mflow = 0;
+
+ if (C_CRTSCTS(un->un_tty))
+ mflow |= (DM_RTS | DM_CTS);
+
+ if (ch->ch_digi.digi_flags & RTSPACE)
+ mflow |= DM_RTS;
+
+ if (ch->ch_digi.digi_flags & DTRPACE)
+ mflow |= DM_DTR;
+
+ if (ch->ch_digi.digi_flags & CTSPACE)
+ mflow |= DM_CTS;
+
+ if (ch->ch_digi.digi_flags & DSRPACE)
+ mflow |= DM_DSR;
+
+ if (ch->ch_digi.digi_flags & DCDPACE)
+ mflow |= DM_CD;
+
+ if (ch->ch_digi.digi_flags & DIGI_RTS_TOGGLE)
+ mflow |= DM_RTS_TOGGLE;
+
+ ch->ch_mflow = mflow;
+
+ /*
+ * Send the changes to the server.
+ */
+
+ ch->ch_flag |= CH_PARAM;
+ (ch->ch_nd)->nd_tx_work = 1;
+
+ if (waitqueue_active(&ch->ch_flag_wait))
+ wake_up_interruptible(&ch->ch_flag_wait);
+}
+
+/*
+ * This function is just used as a callback for timeouts
+ * waiting on the ch_sleep flag.
+ */
+static void wake_up_drp_sleep_timer(unsigned long ptr)
+{
+ struct ch_struct *ch = (struct ch_struct *) ptr;
+ if (ch)
+ wake_up(&ch->ch_sleep);
+}
+
+
+/*
+ * Set up our own sleep that can't be cancelled
+ * until our timeout occurs.
+ */
+static void drp_my_sleep(struct ch_struct *ch)
+{
+ struct timer_list drp_wakeup_timer;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * First make sure we're ready to receive the wakeup.
+ */
+
+ add_wait_queue(&ch->ch_sleep, &wait);
+ current->state = TASK_UNINTERRUPTIBLE;
+
+ /*
+ * Since we are uninterruptible, set a timer to
+ * unset the uninterruptable state in 1 second.
+ */
+
+ init_timer(&drp_wakeup_timer);
+ drp_wakeup_timer.function = wake_up_drp_sleep_timer;
+ drp_wakeup_timer.data = (unsigned long) ch;
+ drp_wakeup_timer.expires = jiffies + (1 * HZ);
+ add_timer(&drp_wakeup_timer);
+
+ schedule();
+
+ del_timer(&drp_wakeup_timer);
+
+ remove_wait_queue(&ch->ch_sleep, &wait);
+}
+
+/*
+ * dgrp_tty_open()
+ *
+ * returns:
+ * -EBUSY - this is a callout device and the normal device is active
+ * - there is an error in opening the tty
+ * -ENODEV - the channel does not exist
+ * -EAGAIN - we are in the middle of hanging up or closing
+ * - IMMEDIATE_OPEN fails
+ * -ENXIO or -EAGAIN
+ * - if the port is outside physical range
+ * -EINTR - the open is interrupted
+ *
+ */
+static int dgrp_tty_open(struct tty_struct *tty, struct file *file)
+{
+ int retval = 0;
+ struct nd_struct *nd;
+ struct ch_struct *ch;
+ struct un_struct *un;
+ int port;
+ int delay_error;
+ int otype;
+ int unf;
+ int wait_carrier;
+ int category;
+ int counts_were_incremented = 0;
+ ulong lock_flags;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * Do some initial checks to see if the node and port exist
+ */
+
+ nd = nd_struct_get(MAJOR(tty_devnum(tty)));
+ port = PORT_NUM(MINOR(tty_devnum(tty)));
+ category = OPEN_CATEGORY(MINOR(tty_devnum(tty)));
+
+ if (!nd)
+ return -ENODEV;
+
+ if (port >= CHAN_MAX)
+ return -ENODEV;
+
+ /*
+ * The channel exists.
+ */
+
+ ch = nd->nd_chan + port;
+
+ un = IS_PRINT(MINOR(tty_devnum(tty))) ? &ch->ch_pun : &ch->ch_tun;
+ un->un_tty = tty;
+ tty->driver_data = un;
+
+ /*
+ * If we are in the middle of hanging up,
+ * then return an error
+ */
+ if (tty_hung_up_p(file)) {
+ retval = ((un->un_flag & UN_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ goto done;
+ }
+
+ /*
+ * If the port is in the middle of closing, then block
+ * until it is done, then try again.
+ */
+ retval = wait_event_interruptible(un->un_close_wait,
+ ((un->un_flag & UN_CLOSING) == 0));
+
+ if (retval)
+ goto done;
+
+ /*
+ * If the port is in the middle of a reopen after a network disconnect,
+ * wait until it is done, then try again.
+ */
+ retval = wait_event_interruptible(ch->ch_flag_wait,
+ ((ch->ch_flag & CH_PORT_GONE) == 0));
+
+ if (retval)
+ goto done;
+
+ /*
+ * If this is a callout device, then just make sure the normal
+ * device isn't being used.
+ */
+
+ if (tty->driver->subtype == SERIAL_TYPE_CALLOUT) {
+ if (un->un_flag & UN_NORMAL_ACTIVE) {
+ retval = -EBUSY;
+ goto done;
+ } else {
+ un->un_flag |= UN_CALLOUT_ACTIVE;
+ }
+ }
+
+ /*
+ * Loop waiting until the open can be successfully completed.
+ */
+
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+ nd->nd_tx_work = 1;
+
+ for (;;) {
+ wait_carrier = 0;
+
+ /*
+ * Determine the open type from the flags provided.
+ */
+
+ /*
+ * If the port is not enabled, then exit
+ */
+ if (test_bit(TTY_IO_ERROR, &tty->flags)) {
+ /* there was an error in opening the tty */
+ if (un->un_flag & UN_CALLOUT_ACTIVE)
+ retval = -EBUSY;
+ else
+ un->un_flag |= UN_NORMAL_ACTIVE;
+ goto unlock;
+ }
+
+ if (file->f_flags & O_NONBLOCK) {
+
+ /*
+ * if the O_NONBLOCK is set, errors on read and write
+ * must return -EAGAIN immediately and NOT sleep
+ * on the waitqs.
+ */
+ otype = OTYPE_IMMEDIATE;
+ delay_error = -EAGAIN;
+
+ } else if (!OPEN_WAIT_AVAIL(category) ||
+ (file->f_flags & O_NDELAY) != 0) {
+ otype = OTYPE_IMMEDIATE;
+ delay_error = -EBUSY;
+
+ } else if (!OPEN_WAIT_CARRIER(category) ||
+ ((ch->ch_digi.digi_flags & DIGI_FORCEDCD) != 0) ||
+ C_CLOCAL(tty)) {
+ otype = OTYPE_PERSISTENT;
+ delay_error = 0;
+
+ } else {
+ otype = OTYPE_INCOMING;
+ delay_error = 0;
+ }
+
+ /*
+ * Handle port currently outside physical port range.
+ */
+
+ if (port >= nd->nd_chan_count) {
+ if (otype == OTYPE_IMMEDIATE) {
+ retval = (nd->nd_state == NS_READY) ?
+ -ENXIO : -EAGAIN;
+ goto unlock;
+ }
+ }
+
+ /*
+ * Handle port not currently open.
+ */
+
+ else if (ch->ch_open_count == 0) {
+ /*
+ * Return an error when an Incoming Open
+ * response indicates the port is busy.
+ */
+
+ if (ch->ch_open_error != 0 && otype == ch->ch_otype) {
+ retval = (ch->ch_open_error <= 2) ?
+ delay_error : -ENXIO;
+ goto unlock;
+ }
+
+ /*
+ * Fail any new Immediate open if we do not have
+ * a normal connection to the server.
+ */
+
+ if (nd->nd_state != NS_READY &&
+ otype == OTYPE_IMMEDIATE) {
+ retval = -EAGAIN;
+ goto unlock;
+ }
+
+ /*
+ * If a Realport open of the correct type has
+ * succeeded, complete the open.
+ */
+
+ if (ch->ch_state == CS_READY && ch->ch_otype == otype)
+ break;
+ }
+
+ /*
+ * Handle port already open and active as a device
+ * of same category.
+ */
+
+ else if ((ch->ch_category == category) ||
+ IS_PRINT(MINOR(tty_devnum(tty)))) {
+ /*
+ * Fail if opening the device now would
+ * violate exclusive use.
+ */
+ unf = ch->ch_tun.un_flag | ch->ch_pun.un_flag;
+
+ if ((file->f_flags & O_EXCL) || (unf & UN_EXCL)) {
+ retval = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * If the open device is in the hangup state, all
+ * system calls fail except close().
+ */
+
+ /* TODO : check on hangup_p calls */
+
+ if (ch->ch_flag & CH_HANGUP) {
+ retval = -ENXIO;
+ goto unlock;
+ }
+
+ /*
+ * If the port is ready, and carrier is ignored
+ * or present, then complete the open.
+ */
+
+ if (ch->ch_state == CS_READY &&
+ (otype != OTYPE_INCOMING ||
+ ch->ch_flag & CH_VIRT_CD))
+ break;
+
+ wait_carrier = 1;
+ }
+
+ /*
+ * Handle port active with a different category device.
+ */
+
+ else {
+ if (otype == OTYPE_IMMEDIATE) {
+ retval = delay_error;
+ goto unlock;
+ }
+ }
+
+ /*
+ * Wait until conditions change, then take another
+ * try at the open.
+ */
+
+ ch->ch_wait_count[otype]++;
+
+ if (wait_carrier)
+ ch->ch_wait_carrier++;
+
+ /*
+ * Prepare the task to accept the wakeup, then
+ * release our locks and release control.
+ */
+
+ add_wait_queue(&ch->ch_flag_wait, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+ /*
+ * Give up control, we'll come back if we're
+ * interrupted or are woken up.
+ */
+ schedule();
+ remove_wait_queue(&ch->ch_flag_wait, &wait);
+
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+ current->state = TASK_RUNNING;
+
+ ch->ch_wait_count[otype]--;
+
+ if (wait_carrier)
+ ch->ch_wait_carrier--;
+
+ nd->nd_tx_work = 1;
+
+ if (signal_pending(current)) {
+ retval = -EINTR;
+ goto unlock;
+ }
+ } /* end for(;;) */
+
+ /*
+ * The open has succeeded. No turning back.
+ */
+ counts_were_incremented = 1;
+ un->un_open_count++;
+ ch->ch_open_count++;
+
+ /*
+ * Initialize the channel, if it's not already open.
+ */
+
+ if (ch->ch_open_count == 1) {
+ ch->ch_flag = 0;
+ ch->ch_inwait = 0;
+ ch->ch_category = category;
+ ch->ch_pscan_state = 0;
+
+ /* TODO : find out what PS-1 bug Gene was referring to */
+ /* TODO : in the following comment. */
+
+ ch->ch_send = RR_TX_START | RR_RX_START; /* PS-1 bug */
+
+ if (C_CLOCAL(tty) ||
+ ch->ch_s_mlast & DM_CD ||
+ ch->ch_digi.digi_flags & DIGI_FORCEDCD)
+ ch->ch_flag |= CH_VIRT_CD;
+ else if (OPEN_FORCES_CARRIER(category))
+ ch->ch_flag |= CH_VIRT_CD;
+
+ }
+
+ /*
+ * Initialize the unit, if it is not already open.
+ */
+
+ if (un->un_open_count == 1) {
+ /*
+ * Since all terminal options are always sticky in Linux,
+ * we don't need the UN_STICKY flag to be handled specially.
+ */
+ /* clears all the digi flags, leaves serial flags */
+ un->un_flag &= ~UN_DIGI_MASK;
+
+ if (file->f_flags & O_EXCL)
+ un->un_flag |= UN_EXCL;
+
+ /* TODO : include "session" and "pgrp" */
+
+ /*
+ * In Linux, all terminal parameters are intended to be sticky.
+ * as a result, we "remove" the code which once reset the ports
+ * to sane values.
+ */
+
+ drp_param(ch);
+
+ }
+
+ un->un_flag |= UN_INITIALIZED;
+
+ retval = 0;
+
+unlock:
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+done:
+ /*
+ * Linux does a close for every open, even failed ones!
+ */
+ if (!counts_were_incremented) {
+ un->un_open_count++;
+ ch->ch_open_count++;
+ }
+
+ if (retval)
+ dev_err(tty->dev, "tty open bad return (%i)\n", retval);
+
+ return retval;
+}
+
+
+
+
+/*
+ * dgrp_tty_close() -- close function for tty_operations
+ */
+static void dgrp_tty_close(struct tty_struct *tty, struct file *file)
+{
+ struct ch_struct *ch;
+ struct un_struct *un;
+ struct nd_struct *nd;
+ int tpos;
+ int port;
+ int err = 0;
+ int s = 0;
+ ulong waketime;
+ ulong lock_flags;
+ int sent_printer_offstr = 0;
+
+ port = PORT_NUM(MINOR(tty_devnum(tty)));
+
+ un = tty->driver_data;
+
+ if (!un)
+ return;
+
+ ch = un->un_ch;
+
+ if (!ch)
+ return;
+
+ nd = ch->ch_nd;
+
+ if (!nd)
+ return;
+
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+
+ /* Used to be on channel basis, now we check on a unit basis. */
+ if (un->un_open_count != 1)
+ goto unlock;
+
+ /*
+ * OK, its the last close on the unit
+ */
+ un->un_flag |= UN_CLOSING;
+
+ /*
+ * Notify the discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+
+ /*
+ * Wait for output to drain only if this is
+ * the last close against the channel
+ */
+
+ if (ch->ch_open_count == 1) {
+ /*
+ * If its the print device, we need to ensure at all costs that
+ * the offstr will fit. If it won't, flush our tbuf.
+ */
+ if (IS_PRINT(MINOR(tty_devnum(tty))) &&
+ (((ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK) <
+ ch->ch_digi.digi_offlen))
+ ch->ch_tin = ch->ch_tout;
+
+ /*
+ * Turn off the printer. Don't bother checking to see if its
+ * IS_PRINT... Since this is the last close the flag is going
+ * to be cleared, so we MUST make sure the offstr gets inserted
+ * into tbuf.
+ */
+
+ if ((ch->ch_flag & CH_PRON) != 0) {
+ drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+ ch->ch_digi.digi_offlen);
+ ch->ch_flag &= ~CH_PRON;
+ sent_printer_offstr = 1;
+ }
+ }
+
+ /*
+ * Wait until either the output queue has drained, or we see
+ * absolutely no progress for 15 seconds.
+ */
+
+ tpos = ch->ch_s_tpos;
+
+ waketime = jiffies + 15 * HZ;
+
+ for (;;) {
+
+ /*
+ * Make sure the port still exists.
+ */
+
+ if (port >= nd->nd_chan_count) {
+ err = 1;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ err = 1;
+ break;
+ }
+
+ /*
+ * If the port is idle (not opened on the server), we have
+ * no way of draining/flushing/closing the port on that server.
+ * So break out of loop.
+ */
+ if (ch->ch_state == CS_IDLE)
+ break;
+
+ nd->nd_tx_work = 1;
+
+ /*
+ * Exit if the queues for this unit are empty,
+ * and either the other unit is still open or all
+ * data has drained.
+ */
+
+ if ((un->un_tty)->ops->chars_in_buffer ?
+ ((un->un_tty)->ops->chars_in_buffer)(un->un_tty) == 0 : 1) {
+
+ /*
+ * We don't need to wait for a buffer to drain
+ * if the other unit is open.
+ */
+
+ if (ch->ch_open_count != un->un_open_count)
+ break;
+
+ /*
+ * The wait is complete when all queues are
+ * drained, and any break in progress is complete.
+ */
+
+ if (ch->ch_tin == ch->ch_tout &&
+ ch->ch_s_tin == ch->ch_s_tpos &&
+ (ch->ch_send & RR_TX_BREAK) == 0) {
+ break;
+ }
+ }
+
+ /*
+ * Flush TX data and exit the wait if NDELAY is set,
+ * or this is not a DIGI printer, and the close timeout
+ * expires.
+ */
+
+ if ((file->f_flags & (O_NDELAY | O_NONBLOCK)) ||
+ ((long)(jiffies - waketime) >= 0 &&
+ (ch->ch_digi.digi_flags & DIGI_PRINTER) == 0)) {
+
+ /*
+ * If we sent the printer off string, we cannot
+ * flush our internal buffers, or we might lose
+ * the offstr.
+ */
+ if (!sent_printer_offstr)
+ dgrp_tty_flush_buffer(tty);
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+ tty_ldisc_flush(tty);
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+ break;
+ }
+
+ /*
+ * Otherwise take a short nap.
+ */
+
+ ch->ch_flag |= CH_DRAIN;
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+ schedule_timeout_interruptible(1);
+ s = signal_pending(current);
+
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+
+ if (s) {
+ /*
+ * If we had sent the printer off string, we now have
+ * some problems.
+ *
+ * The system won't let us sleep since we got an error
+ * back from sleep, presumably because the user did
+ * a ctrl-c...
+ * But we need to ensure that the offstr gets sent!
+ * Thus, we have to do something else besides sleeping.
+ * The plan:
+ * 1) Make this task uninterruptable.
+ * 2) Set up a timer to go off in 1 sec.
+ * 3) Act as tho we just got out of the sleep above.
+ *
+ * Thankfully, in the real world, this just
+ * never happens.
+ */
+
+ if (sent_printer_offstr) {
+ spin_unlock_irqrestore(&nd->nd_lock,
+ lock_flags);
+ drp_my_sleep(ch);
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+ } else {
+ err = 1;
+ break;
+ }
+ }
+
+ /*
+ * Restart the wait if any progress is seen.
+ */
+
+ if (ch->ch_s_tpos != tpos) {
+ tpos = ch->ch_s_tpos;
+
+ /* TODO: this gives us timeout problems with nist ?? */
+ waketime = jiffies + 15 * HZ;
+ }
+ }
+
+ /*
+ * Close the line discipline
+ */
+
+ /* this is done in tty_io.c */
+ /* if ((un->un_tty)->ldisc.close)
+ * ((un->un_tty)->ldisc.close)(un->un_tty);
+ */
+
+ /* don't do this here */
+ /* un->un_flag = 0; */
+
+ /*
+ * Flush the receive buffer on terminal unit close only.
+ */
+
+ if (!IS_PRINT(MINOR(tty_devnum(tty))))
+ ch->ch_rout = ch->ch_rin;
+
+
+ /*
+ * Don't permit the close to happen until we get any pending
+ * sync request responses.
+ * There could be other ports depending upon the response as well.
+ *
+ * Also, don't permit the close to happen until any parameter
+ * changes have been sent out from the state machine as well.
+ * This is required because of a ditty -a race with -HUPCL
+ * We MUST make sure all channel parameters have been sent to the
+ * Portserver before sending a close.
+ */
+
+ if ((err != 1) && (ch->ch_state != CS_IDLE)) {
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+ s = wait_event_interruptible(ch->ch_flag_wait,
+ ((ch->ch_flag & (CH_WAITING_SYNC | CH_PARAM)) == 0));
+ spin_lock_irqsave(&nd->nd_lock, lock_flags);
+ }
+
+ /*
+ * Cleanup the channel if last unit open.
+ */
+
+ if (ch->ch_open_count == 1) {
+ ch->ch_flag = 0;
+ ch->ch_category = 0;
+ ch->ch_send = 0;
+ ch->ch_expect = 0;
+ ch->ch_tout = ch->ch_tin;
+ /* (un->un_tty)->device = 0; */
+
+ if (ch->ch_state == CS_READY)
+ ch->ch_state = CS_SEND_CLOSE;
+ }
+
+ /*
+ * Send the changes to the server
+ */
+ if (ch->ch_state != CS_IDLE) {
+ ch->ch_flag |= CH_PARAM;
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+ nd->nd_tx_work = 1;
+ nd->nd_tx_ready = 1;
+
+unlock:
+ tty->closing = 0;
+
+ if (ch->ch_open_count <= 0)
+ dev_info(tty->dev,
+ "%s - unexpected value for ch->ch_open_count: %i\n",
+ __func__, ch->ch_open_count);
+ else
+ ch->ch_open_count--;
+
+ if (un->un_open_count <= 0)
+ dev_info(tty->dev,
+ "%s - unexpected value for un->un_open_count: %i\n",
+ __func__, un->un_open_count);
+ else
+ un->un_open_count--;
+
+ un->un_flag &= ~(UN_NORMAL_ACTIVE | UN_CALLOUT_ACTIVE | UN_CLOSING);
+ if (waitqueue_active(&un->un_close_wait))
+ wake_up_interruptible(&un->un_close_wait);
+
+ spin_unlock_irqrestore(&nd->nd_lock, lock_flags);
+
+ return;
+
+}
+
+static void drp_wmove(struct ch_struct *ch, int from_user, void *buf, int count)
+{
+ int n;
+ int ret = 0;
+
+ ch->ch_nd->nd_tx_work = 1;
+
+ n = TBUF_MAX - ch->ch_tin;
+
+ if (count >= n) {
+ if (from_user)
+ ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+ (void __user *) buf, n);
+ else
+ memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+
+ buf = (char *) buf + n;
+ count -= n;
+ ch->ch_tin = 0;
+ }
+
+ if (from_user)
+ ret = copy_from_user(ch->ch_tbuf + ch->ch_tin,
+ (void __user *) buf, count);
+ else
+ memcpy(ch->ch_tbuf + ch->ch_tin, buf, count);
+
+ ch->ch_tin += count;
+}
+
+
+static int dgrp_calculate_txprint_bounds(struct ch_struct *ch, int space,
+ int *un_flag)
+{
+ clock_t tt;
+ clock_t mt;
+ unsigned short tmax = 0;
+
+ /*
+ * If the terminal device is busy, reschedule when
+ * the terminal device becomes idle.
+ */
+
+ if (ch->ch_tun.un_open_count != 0 &&
+ ch->ch_tun.un_tty->ops->chars_in_buffer &&
+ ((ch->ch_tun.un_tty->ops->chars_in_buffer)
+ (ch->ch_tun.un_tty) != 0)) {
+ *un_flag = UN_PWAIT;
+ return 0;
+ }
+
+ /*
+ * Assure that whenever there is printer data in the output
+ * buffer, there always remains enough space after it to
+ * turn the printer off.
+ */
+ space -= ch->ch_digi.digi_offlen;
+
+ if (space <= 0) {
+ *un_flag = UN_EMPTY;
+ return 0;
+ }
+
+ /*
+ * We measure printer CPS speed by incrementing
+ * ch_cpstime by (HZ / digi_maxcps) for every
+ * character we output, restricting output so
+ * that ch_cpstime never exceeds lbolt.
+ *
+ * However if output has not been done for some
+ * time, lbolt will grow to very much larger than
+ * ch_cpstime, which would allow essentially
+ * unlimited amounts of output until ch_cpstime
+ * finally caught up. To avoid this, we adjust
+ * cps_time when necessary so the difference
+ * between lbolt and ch_cpstime never results
+ * in sending more than digi_bufsize characters.
+ *
+ * This nicely models a printer with an internal
+ * buffer of digi_bufsize characters.
+ *
+ * Get the time between lbolt and ch->ch_cpstime;
+ */
+
+ tt = jiffies - ch->ch_cpstime;
+
+ /*
+ * Compute the time required to send digi_bufsize
+ * characters.
+ */
+
+ mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+ /*
+ * Compute the number of characters that can be sent
+ * without violating the time constraint. If the
+ * direct calculation of this number is bigger than
+ * digi_bufsize, limit the number to digi_bufsize,
+ * and adjust cpstime to match.
+ */
+
+ if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+ tmax = ch->ch_digi.digi_bufsize;
+ ch->ch_cpstime = jiffies - mt;
+ } else {
+ tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+ }
+
+ /*
+ * If the time constraint now binds, limit the transmit
+ * count accordingly, and tentatively arrange to be
+ * rescheduled based on time.
+ */
+
+ if (tmax < space) {
+ *un_flag = UN_TIME;
+ space = tmax;
+ }
+
+ /*
+ * Compute the total number of characters we can
+ * output before the total number of characters known
+ * to be in the output queue exceeds digi_maxchar.
+ */
+
+ tmax = (ch->ch_digi.digi_maxchar -
+ ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+ ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+ /*
+ * If the digi_maxchar constraint now holds, limit
+ * the transmit count accordingly, and arrange to
+ * be rescheduled when the queue becomes empty.
+ */
+
+ if (space > tmax) {
+ *un_flag = UN_EMPTY;
+ space = tmax;
+ }
+
+ if (space <= 0)
+ *un_flag |= UN_EMPTY;
+
+ return space;
+}
+
+
+static int dgrp_tty_write(struct tty_struct *tty,
+ const unsigned char *buf,
+ int count)
+{
+ struct nd_struct *nd;
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int space;
+ int n;
+ int t;
+ int sendcount;
+ int un_flag;
+ ulong lock_flags;
+
+ if (tty == NULL)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+
+ nd = ch->ch_nd;
+ if (!nd)
+ return 0;
+
+ /*
+ * Ignore the request if the channel is not ready.
+ */
+ if (ch->ch_state != CS_READY)
+ return 0;
+
+ spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+ /*
+ * Ignore the request if output is blocked.
+ */
+ if ((un->un_flag & (UN_EMPTY | UN_LOW | UN_TIME | UN_PWAIT)) != 0) {
+ count = 0;
+ goto out;
+ }
+
+ /*
+ * Also ignore the request if DPA has this port open,
+ * and is flow controlled on reading more data.
+ */
+ if (nd->nd_dpa_debug && nd->nd_dpa_flag & DPA_WAIT_SPACE &&
+ nd->nd_dpa_port == MINOR(tty_devnum(ch->ch_tun.un_tty))) {
+ count = 0;
+ goto out;
+ }
+
+ /*
+ * Limit amount we will write to the amount of space
+ * available in the channel buffer.
+ */
+ sendcount = 0;
+
+ space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+ /*
+ * Handle the printer device.
+ */
+
+ un_flag = UN_LOW;
+
+ if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+ clock_t tt;
+ clock_t mt;
+ unsigned short tmax = 0;
+
+ /*
+ * If the terminal device is busy, reschedule when
+ * the terminal device becomes idle.
+ */
+
+ if (ch->ch_tun.un_open_count != 0 &&
+ ((ch->ch_tun.un_tty->ops->chars_in_buffer)
+ (ch->ch_tun.un_tty) != 0)) {
+ un->un_flag |= UN_PWAIT;
+ count = 0;
+ goto out;
+ }
+
+ /*
+ * Assure that whenever there is printer data in the output
+ * buffer, there always remains enough space after it to
+ * turn the printer off.
+ */
+ space -= ch->ch_digi.digi_offlen;
+
+ /*
+ * Output the printer on string.
+ */
+
+ if ((ch->ch_flag & CH_PRON) == 0) {
+ space -= ch->ch_digi.digi_onlen;
+
+ if (space < 0) {
+ un->un_flag |= UN_EMPTY;
+ (ch->ch_nd)->nd_tx_work = 1;
+ count = 0;
+ goto out;
+ }
+
+ drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+ ch->ch_digi.digi_onlen);
+
+ ch->ch_flag |= CH_PRON;
+ }
+
+ /*
+ * We measure printer CPS speed by incrementing
+ * ch_cpstime by (HZ / digi_maxcps) for every
+ * character we output, restricting output so
+ * that ch_cpstime never exceeds lbolt.
+ *
+ * However if output has not been done for some
+ * time, lbolt will grow to very much larger than
+ * ch_cpstime, which would allow essentially
+ * unlimited amounts of output until ch_cpstime
+ * finally caught up. To avoid this, we adjust
+ * cps_time when necessary so the difference
+ * between lbolt and ch_cpstime never results
+ * in sending more than digi_bufsize characters.
+ *
+ * This nicely models a printer with an internal
+ * buffer of digi_bufsize characters.
+ *
+ * Get the time between lbolt and ch->ch_cpstime;
+ */
+
+ tt = jiffies - ch->ch_cpstime;
+
+ /*
+ * Compute the time required to send digi_bufsize
+ * characters.
+ */
+
+ mt = HZ * ch->ch_digi.digi_bufsize / ch->ch_digi.digi_maxcps;
+
+ /*
+ * Compute the number of characters that can be sent
+ * without violating the time constraint. If the
+ * direct calculation of this number is bigger than
+ * digi_bufsize, limit the number to digi_bufsize,
+ * and adjust cpstime to match.
+ */
+
+ if ((clock_t)(tt + HZ) > (clock_t)(mt + HZ)) {
+ tmax = ch->ch_digi.digi_bufsize;
+ ch->ch_cpstime = jiffies - mt;
+ } else {
+ tmax = ch->ch_digi.digi_maxcps * tt / HZ;
+ }
+
+ /*
+ * If the time constraint now binds, limit the transmit
+ * count accordingly, and tentatively arrange to be
+ * rescheduled based on time.
+ */
+
+ if (tmax < space) {
+ space = tmax;
+ un_flag = UN_TIME;
+ }
+
+ /*
+ * Compute the total number of characters we can
+ * output before the total number of characters known
+ * to be in the output queue exceeds digi_maxchar.
+ */
+
+ tmax = (ch->ch_digi.digi_maxchar -
+ ((ch->ch_tin - ch->ch_tout) & TBUF_MASK) -
+ ((ch->ch_s_tin - ch->ch_s_tpos) & 0xffff));
+
+
+ /*
+ * If the digi_maxchar constraint now holds, limit
+ * the transmit count accordingly, and arrange to
+ * be rescheduled when the queue becomes empty.
+ */
+
+ if (space > tmax) {
+ space = tmax;
+ un_flag = UN_EMPTY;
+ }
+
+ }
+ /*
+ * Handle the terminal device.
+ */
+ else {
+
+ /*
+ * If the printer device is on, turn it off.
+ */
+
+ if ((ch->ch_flag & CH_PRON) != 0) {
+
+ space -= ch->ch_digi.digi_offlen;
+
+ drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+ ch->ch_digi.digi_offlen);
+
+ ch->ch_flag &= ~CH_PRON;
+ }
+ }
+
+ /*
+ * If space is 0 and its because the ch->tbuf
+ * is full, then Linux will handle a callback when queue
+ * space becomes available.
+ * tty_write returns count = 0
+ */
+
+ if (space <= 0) {
+ /* the linux tty_io.c handles this if we return 0 */
+ /* if (fp->flags & O_NONBLOCK) return -EAGAIN; */
+
+ un->un_flag |= UN_EMPTY;
+ (ch->ch_nd)->nd_tx_work = 1;
+ count = 0;
+ goto out;
+ }
+
+ count = min(count, space);
+
+ if (count > 0) {
+
+ un->un_tbusy++;
+
+ /*
+ * Copy the buffer contents to the ch_tbuf
+ * being careful to wrap around the circular queue
+ */
+
+ t = TBUF_MAX - ch->ch_tin;
+ n = count;
+
+ if (n >= t) {
+ memcpy(ch->ch_tbuf + ch->ch_tin, buf, t);
+ if (nd->nd_dpa_debug && nd->nd_dpa_port ==
+ PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+ dgrp_dpa_data(nd, 0, (char *) buf, t);
+ buf += t;
+ n -= t;
+ ch->ch_tin = 0;
+ sendcount += n;
+ }
+
+ memcpy(ch->ch_tbuf + ch->ch_tin, buf, n);
+ if (nd->nd_dpa_debug && nd->nd_dpa_port ==
+ PORT_NUM(MINOR(tty_devnum(un->un_tty))))
+ dgrp_dpa_data(nd, 0, (char *) buf, n);
+ buf += n;
+ ch->ch_tin += n;
+ sendcount += n;
+
+ un->un_tbusy--;
+ (ch->ch_nd)->nd_tx_work = 1;
+ if (ch->ch_edelay != DGRP_RTIME) {
+ (ch->ch_nd)->nd_tx_ready = 1;
+ wake_up_interruptible(&nd->nd_tx_waitq);
+ }
+ }
+
+ ch->ch_txcount += count;
+
+ if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+ /*
+ * Adjust ch_cpstime to account
+ * for the characters just output.
+ */
+
+ if (sendcount > 0) {
+ int cc = HZ * sendcount + ch->ch_cpsrem;
+
+ ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+ ch->ch_cpsrem = cc % ch->ch_digi.digi_maxcps;
+ }
+
+ /*
+ * If we are now waiting on time, schedule ourself
+ * back when we'll be able to send a block of
+ * digi_maxchar characters.
+ */
+
+ if ((un_flag & UN_TIME) != 0) {
+ ch->ch_waketime = (ch->ch_cpstime +
+ (ch->ch_digi.digi_maxchar * HZ /
+ ch->ch_digi.digi_maxcps));
+ }
+ }
+
+ /*
+ * If the printer unit is waiting for completion
+ * of terminal output, get him going again.
+ */
+
+ if ((ch->ch_pun.un_flag & UN_PWAIT) != 0)
+ (ch->ch_nd)->nd_tx_work = 1;
+
+out:
+ spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+
+ return count;
+}
+
+
+/*
+ * Put a character into ch->ch_buf
+ *
+ * - used by the line discipline for OPOST processing
+ */
+
+static int dgrp_tty_put_char(struct tty_struct *tty, unsigned char new_char)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ ulong lock_flags;
+ int space;
+ int retval = 0;
+
+ if (tty == NULL)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+
+ if (ch->ch_state != CS_READY)
+ return 0;
+
+ spin_lock_irqsave(&dgrp_poll_data.poll_lock, lock_flags);
+
+
+ /*
+ * If space is 0 and its because the ch->tbuf
+ * Warn and dump the character, there isn't anything else
+ * we can do about it. David_Fries@digi.com
+ */
+
+ space = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+ un->un_tbusy++;
+
+ /*
+ * Output the printer on string if device is TXPrint.
+ */
+ if (IS_PRINT(MINOR(tty_devnum(tty))) && (ch->ch_flag & CH_PRON) == 0) {
+ if (space < ch->ch_digi.digi_onlen) {
+ un->un_tbusy--;
+ goto out;
+ }
+ space -= ch->ch_digi.digi_onlen;
+ drp_wmove(ch, 0, ch->ch_digi.digi_onstr,
+ ch->ch_digi.digi_onlen);
+ ch->ch_flag |= CH_PRON;
+ }
+
+ /*
+ * Output the printer off string if device is NOT TXPrint.
+ */
+
+ if (!IS_PRINT(MINOR(tty_devnum(tty))) &&
+ ((ch->ch_flag & CH_PRON) != 0)) {
+ if (space < ch->ch_digi.digi_offlen) {
+ un->un_tbusy--;
+ goto out;
+ }
+
+ space -= ch->ch_digi.digi_offlen;
+ drp_wmove(ch, 0, ch->ch_digi.digi_offstr,
+ ch->ch_digi.digi_offlen);
+ ch->ch_flag &= ~CH_PRON;
+ }
+
+ if (!space) {
+ un->un_tbusy--;
+ goto out;
+ }
+
+ /*
+ * Copy the character to the ch_tbuf being
+ * careful to wrap around the circular queue
+ */
+ ch->ch_tbuf[ch->ch_tin] = new_char;
+ ch->ch_tin = (1 + ch->ch_tin) & TBUF_MASK;
+
+ if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+
+ /*
+ * Adjust ch_cpstime to account
+ * for the character just output.
+ */
+
+ int cc = HZ + ch->ch_cpsrem;
+
+ ch->ch_cpstime += cc / ch->ch_digi.digi_maxcps;
+ ch->ch_cpsrem = cc % ch->ch_digi.digi_maxcps;
+
+ /*
+ * If we are now waiting on time, schedule ourself
+ * back when we'll be able to send a block of
+ * digi_maxchar characters.
+ */
+
+ ch->ch_waketime = (ch->ch_cpstime +
+ (ch->ch_digi.digi_maxchar * HZ /
+ ch->ch_digi.digi_maxcps));
+ }
+
+
+ un->un_tbusy--;
+ (ch->ch_nd)->nd_tx_work = 1;
+
+ retval = 1;
+out:
+ spin_unlock_irqrestore(&dgrp_poll_data.poll_lock, lock_flags);
+ return retval;
+}
+
+
+
+/*
+ * Flush TX buffer (make in == out)
+ *
+ * check tty_ioctl.c -- this is called after TCOFLUSH
+ */
+static void dgrp_tty_flush_buffer(struct tty_struct *tty)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+ un = tty->driver_data;
+ if (!un)
+ return;
+
+ ch = un->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_tout = ch->ch_tin;
+ /* do NOT do this here! */
+ /* ch->ch_s_tpos = ch->ch_s_tin = 0; */
+
+ /* send the flush output command now */
+ ch->ch_send |= RR_TX_FLUSH;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ (ch->ch_nd)->nd_tx_work = 1;
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+ if (waitqueue_active(&tty->write_wait))
+ wake_up_interruptible(&tty->write_wait);
+
+ tty_wakeup(tty);
+
+}
+
+/*
+ * Return space available in Tx buffer
+ * count = ( ch->ch_tout - ch->ch_tin ) mod (TBUF_MAX - 1)
+ */
+static int dgrp_tty_write_room(struct tty_struct *tty)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int count;
+
+ if (!tty)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+
+ count = (ch->ch_tout - ch->ch_tin - 1) & TBUF_MASK;
+
+ /* We *MUST* check this, and return 0 if the Printer Unit cannot
+ * take any more data within its time constraints... If we don't
+ * return 0 and the printer has hit it time constraint, the ld will
+ * call us back doing a put_char, which cannot be rejected!!!
+ */
+ if (IS_PRINT(MINOR(tty_devnum(tty)))) {
+ int un_flag = 0;
+ count = dgrp_calculate_txprint_bounds(ch, count, &un_flag);
+ if (count <= 0)
+ count = 0;
+
+ ch->ch_pun.un_flag |= un_flag;
+ (ch->ch_nd)->nd_tx_work = 1;
+ }
+
+ return count;
+}
+
+/*
+ * Return number of characters that have not been transmitted yet.
+ * chars_in_buffer = ( ch->ch_tin - ch->ch_tout ) mod (TBUF_MAX - 1)
+ * + ( ch->ch_s_tin - ch->ch_s_tout ) mod (0xffff)
+ * = number of characters "in transit"
+ *
+ * Remember that sequence number math is always with a sixteen bit
+ * mask, not the TBUF_MASK.
+ */
+
+static int dgrp_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int count;
+ int count1;
+
+ if (!tty)
+ return 0;
+
+ un = tty->driver_data;
+ if (!un)
+ return 0;
+
+ ch = un->un_ch;
+ if (!ch)
+ return 0;
+
+ count1 = count = (ch->ch_tin - ch->ch_tout) & TBUF_MASK;
+ count += (ch->ch_s_tin - ch->ch_s_tpos) & 0xffff;
+ /* one for tbuf, one for the PS */
+
+ /*
+ * If we are busy transmitting add 1
+ */
+ count += un->un_tbusy;
+
+ return count;
+}
+
+
+/*****************************************************************************
+ *
+ * Helper applications for dgrp_tty_ioctl()
+ *
+ *****************************************************************************
+ */
+
+
+/**
+ * ch_to_tty_flags() -- convert channel flags to termio flags
+ * @ch_flag: Digi channel flags
+ * @flagtype: type of ch_flag (iflag, oflag or cflag)
+ *
+ * take the channel flags of the specified type and return the
+ * corresponding termio flag
+ */
+static tcflag_t ch_to_tty_flags(ushort ch_flag, char flagtype)
+{
+ tcflag_t retval = 0;
+
+ switch (flagtype) {
+ case 'i':
+ retval = ((ch_flag & IF_IGNBRK) ? IGNBRK : 0)
+ | ((ch_flag & IF_BRKINT) ? BRKINT : 0)
+ | ((ch_flag & IF_IGNPAR) ? IGNPAR : 0)
+ | ((ch_flag & IF_PARMRK) ? PARMRK : 0)
+ | ((ch_flag & IF_INPCK) ? INPCK : 0)
+ | ((ch_flag & IF_ISTRIP) ? ISTRIP : 0)
+ | ((ch_flag & IF_IXON) ? IXON : 0)
+ | ((ch_flag & IF_IXANY) ? IXANY : 0)
+ | ((ch_flag & IF_IXOFF) ? IXOFF : 0);
+ break;
+
+ case 'o':
+ retval = ((ch_flag & OF_OLCUC) ? OLCUC : 0)
+ | ((ch_flag & OF_ONLCR) ? ONLCR : 0)
+ | ((ch_flag & OF_OCRNL) ? OCRNL : 0)
+ | ((ch_flag & OF_ONOCR) ? ONOCR : 0)
+ | ((ch_flag & OF_ONLRET) ? ONLRET : 0)
+ /* | ((ch_flag & OF_OTAB3) ? OFILL : 0) */
+ | ((ch_flag & OF_TABDLY) ? TABDLY : 0);
+ break;
+
+ case 'c':
+ retval = ((ch_flag & CF_CSTOPB) ? CSTOPB : 0)
+ | ((ch_flag & CF_CREAD) ? CREAD : 0)
+ | ((ch_flag & CF_PARENB) ? PARENB : 0)
+ | ((ch_flag & CF_PARODD) ? PARODD : 0)
+ | ((ch_flag & CF_HUPCL) ? HUPCL : 0);
+
+ switch (ch_flag & CF_CSIZE) {
+ case CF_CS5:
+ retval |= CS5;
+ break;
+ case CF_CS6:
+ retval |= CS6;
+ break;
+ case CF_CS7:
+ retval |= CS7;
+ break;
+ case CF_CS8:
+ retval |= CS8;
+ break;
+ default:
+ retval |= CS8;
+ break;
+ }
+ break;
+ case 'x':
+ break;
+ case 'l':
+ break;
+ default:
+ return 0;
+ }
+
+ return retval;
+}
+
+
+/**
+ * tty_to_ch_flags() -- convert termio flags to digi channel flags
+ * @tty: pointer to a TTY structure holding flag to be converted
+ * @flagtype: identifies which flag (iflags, oflags, or cflags) should
+ * be converted
+ *
+ * take the termio flag of the specified type and return the
+ * corresponding Digi version of the flags
+ */
+static ushort tty_to_ch_flags(struct tty_struct *tty, char flagtype)
+{
+ ushort retval = 0;
+ tcflag_t tflag = 0;
+
+ switch (flagtype) {
+ case 'i':
+ tflag = tty->termios.c_iflag;
+ retval = (I_IGNBRK(tty) ? IF_IGNBRK : 0)
+ | (I_BRKINT(tty) ? IF_BRKINT : 0)
+ | (I_IGNPAR(tty) ? IF_IGNPAR : 0)
+ | (I_PARMRK(tty) ? IF_PARMRK : 0)
+ | (I_INPCK(tty) ? IF_INPCK : 0)
+ | (I_ISTRIP(tty) ? IF_ISTRIP : 0)
+ | (I_IXON(tty) ? IF_IXON : 0)
+ | (I_IXANY(tty) ? IF_IXANY : 0)
+ | (I_IXOFF(tty) ? IF_IXOFF : 0);
+ break;
+ case 'o':
+ tflag = tty->termios.c_oflag;
+ /*
+ * If OPOST is set, then do the post processing in the
+ * firmware by setting all the processing flags on.
+ * If ~OPOST, then make sure we are not doing any
+ * output processing!!
+ */
+ if (!O_OPOST(tty))
+ retval = 0;
+ else
+ retval = (O_OLCUC(tty) ? OF_OLCUC : 0)
+ | (O_ONLCR(tty) ? OF_ONLCR : 0)
+ | (O_OCRNL(tty) ? OF_OCRNL : 0)
+ | (O_ONOCR(tty) ? OF_ONOCR : 0)
+ | (O_ONLRET(tty) ? OF_ONLRET : 0)
+ /* | (O_OFILL(tty) ? OF_TAB3 : 0) */
+ | (O_TABDLY(tty) ? OF_TABDLY : 0);
+ break;
+ case 'c':
+ tflag = tty->termios.c_cflag;
+ retval = (C_CSTOPB(tty) ? CF_CSTOPB : 0)
+ | (C_CREAD(tty) ? CF_CREAD : 0)
+ | (C_PARENB(tty) ? CF_PARENB : 0)
+ | (C_PARODD(tty) ? CF_PARODD : 0)
+ | (C_HUPCL(tty) ? CF_HUPCL : 0);
+ switch (C_CSIZE(tty)) {
+ case CS8:
+ retval |= CF_CS8;
+ break;
+ case CS7:
+ retval |= CF_CS7;
+ break;
+ case CS6:
+ retval |= CF_CS6;
+ break;
+ case CS5:
+ retval |= CF_CS5;
+ break;
+ default:
+ retval |= CF_CS8;
+ break;
+ }
+ break;
+ case 'x':
+ break;
+ case 'l':
+ break;
+ default:
+ return 0;
+ }
+
+ return retval;
+}
+
+
+static int dgrp_tty_send_break(struct tty_struct *tty, int msec)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int ret = -EIO;
+
+ if (!tty)
+ return ret;
+
+ un = tty->driver_data;
+ if (!un)
+ return ret;
+
+ ch = un->un_ch;
+ if (!ch)
+ return ret;
+
+ dgrp_send_break(ch, msec);
+ return 0;
+}
+
+
+/*
+ * This routine sends a break character out the serial port.
+ *
+ * duration is in 1/1000's of a second
+ */
+static int dgrp_send_break(struct ch_struct *ch, int msec)
+{
+ ulong x;
+
+ wait_event_interruptible(ch->ch_flag_wait,
+ ((ch->ch_flag & CH_TX_BREAK) == 0));
+ ch->ch_break_time += max(msec, 250);
+ ch->ch_send |= RR_TX_BREAK;
+ ch->ch_flag |= CH_TX_BREAK;
+ (ch->ch_nd)->nd_tx_work = 1;
+
+ x = (msec * HZ) / 1000;
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+ return 0;
+}
+
+
+/*
+ * Return modem signals to ld.
+ */
+static int dgrp_tty_tiocmget(struct tty_struct *tty)
+{
+ unsigned int mlast;
+ struct un_struct *un = tty->driver_data;
+ struct ch_struct *ch;
+
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+ (ch->ch_mout & (DM_RTS | DM_DTR)));
+
+ /* defined in /usr/include/asm/termios.h */
+ mlast = ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+ | ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+ | ((mlast & DM_CD) ? TIOCM_CAR : 0)
+ | ((mlast & DM_RI) ? TIOCM_RNG : 0)
+ | ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+ | ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+
+ return mlast;
+}
+
+
+/*
+ * Set modem lines
+ */
+static int dgrp_tty_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ ulong lock_flags;
+ struct un_struct *un = tty->driver_data;
+ struct ch_struct *ch;
+
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ if (set & TIOCM_RTS)
+ ch->ch_mout |= DM_RTS;
+
+ if (set & TIOCM_DTR)
+ ch->ch_mout |= DM_DTR;
+
+ if (clear & TIOCM_RTS)
+ ch->ch_mout &= ~(DM_RTS);
+
+ if (clear & TIOCM_DTR)
+ ch->ch_mout &= ~(DM_DTR);
+
+ spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+ ch->ch_flag |= CH_PARAM;
+ (ch->ch_nd)->nd_tx_work = 1;
+ wake_up_interruptible(&ch->ch_flag_wait);
+
+ spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+ return 0;
+}
+
+
+
+/*
+ * Get current modem status
+ */
+static int get_modem_info(struct ch_struct *ch, unsigned int *value)
+{
+ unsigned int mlast;
+
+ mlast = ((ch->ch_s_mlast & ~(DM_RTS | DM_DTR)) |
+ (ch->ch_mout & (DM_RTS | DM_DTR)));
+
+ /* defined in /usr/include/asm/termios.h */
+ mlast = ((mlast & DM_RTS) ? TIOCM_RTS : 0)
+ | ((mlast & DM_DTR) ? TIOCM_DTR : 0)
+ | ((mlast & DM_CD) ? TIOCM_CAR : 0)
+ | ((mlast & DM_RI) ? TIOCM_RNG : 0)
+ | ((mlast & DM_DSR) ? TIOCM_DSR : 0)
+ | ((mlast & DM_CTS) ? TIOCM_CTS : 0);
+ return put_user(mlast, (unsigned int __user *) value);
+}
+
+/*
+ * Set modem lines
+ */
+static int set_modem_info(struct ch_struct *ch, unsigned int command,
+ unsigned int *value)
+{
+ int error;
+ unsigned int arg;
+ int mval = 0;
+ ulong lock_flags;
+
+ error = access_ok(VERIFY_READ, (void __user *) value, sizeof(int));
+ if (error == 0)
+ return -EFAULT;
+
+ if (get_user(arg, (unsigned int __user *) value))
+ return -EFAULT;
+ mval |= ((arg & TIOCM_RTS) ? DM_RTS : 0)
+ | ((arg & TIOCM_DTR) ? DM_DTR : 0);
+
+ switch (command) {
+ case TIOCMBIS: /* set flags */
+ ch->ch_mout |= mval;
+ break;
+ case TIOCMBIC: /* clear flags */
+ ch->ch_mout &= ~mval;
+ break;
+ case TIOCMSET:
+ ch->ch_mout = mval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&(ch->ch_nd)->nd_lock, lock_flags);
+
+ ch->ch_flag |= CH_PARAM;
+ (ch->ch_nd)->nd_tx_work = 1;
+ wake_up_interruptible(&ch->ch_flag_wait);
+
+ spin_unlock_irqrestore(&(ch->ch_nd)->nd_lock, lock_flags);
+
+ return 0;
+}
+
+
+/*
+ * Assign the custom baud rate to the channel structure
+ */
+static void dgrp_set_custom_speed(struct ch_struct *ch, int newrate)
+{
+ int testdiv;
+ int testrate_high;
+ int testrate_low;
+
+ int deltahigh, deltalow;
+
+ if (newrate < 0)
+ newrate = 0;
+
+ /*
+ * Since the divisor is stored in a 16-bit integer, we make sure
+ * we don't allow any rates smaller than a 16-bit integer would allow.
+ * And of course, rates above the dividend won't fly.
+ */
+ if (newrate && newrate < ((PORTSERVER_DIVIDEND / 0xFFFF) + 1))
+ newrate = ((PORTSERVER_DIVIDEND / 0xFFFF) + 1);
+ if (newrate && newrate > PORTSERVER_DIVIDEND)
+ newrate = PORTSERVER_DIVIDEND;
+
+ while (newrate > 0) {
+ testdiv = PORTSERVER_DIVIDEND / newrate;
+
+ /*
+ * If we try to figure out what rate the PortServer would use
+ * with the test divisor, it will be either equal or higher
+ * than the requested baud rate. If we then determine the
+ * rate with a divisor one higher, we will get the next lower
+ * supported rate below the requested.
+ */
+ testrate_high = PORTSERVER_DIVIDEND / testdiv;
+ testrate_low = PORTSERVER_DIVIDEND / (testdiv + 1);
+
+ /*
+ * If the rate for the requested divisor is correct, just
+ * use it and be done.
+ */
+ if (testrate_high == newrate)
+ break;
+
+ /*
+ * Otherwise, pick the rate that is closer (i.e. whichever rate
+ * has a smaller delta).
+ */
+ deltahigh = testrate_high - newrate;
+ deltalow = newrate - testrate_low;
+
+ if (deltahigh < deltalow)
+ newrate = testrate_high;
+ else
+ newrate = testrate_low;
+
+ break;
+ }
+
+ ch->ch_custom_speed = newrate;
+
+ drp_param(ch);
+
+ return;
+}
+
+
+/*
+ # dgrp_tty_digiseta()
+ *
+ * Ioctl to set the information from ditty.
+ *
+ * NOTE: DIGI_IXON, DSRPACE, DCDPACE, and DTRPACE are unsupported. JAR 990922
+ */
+static int dgrp_tty_digiseta(struct tty_struct *tty,
+ struct digi_struct *new_info)
+{
+ struct un_struct *un = tty->driver_data;
+ struct ch_struct *ch;
+
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ if (copy_from_user(&ch->ch_digi, (void __user *) new_info,
+ sizeof(struct digi_struct)))
+ return -EFAULT;
+
+ if ((ch->ch_digi.digi_flags & RTSPACE) ||
+ (ch->ch_digi.digi_flags & CTSPACE))
+ tty->termios.c_cflag |= CRTSCTS;
+ else
+ tty->termios.c_cflag &= ~CRTSCTS;
+
+ if (ch->ch_digi.digi_maxcps < 1)
+ ch->ch_digi.digi_maxcps = 1;
+
+ if (ch->ch_digi.digi_maxcps > 10000)
+ ch->ch_digi.digi_maxcps = 10000;
+
+ if (ch->ch_digi.digi_bufsize < 10)
+ ch->ch_digi.digi_bufsize = 10;
+
+ if (ch->ch_digi.digi_maxchar < 1)
+ ch->ch_digi.digi_maxchar = 1;
+
+ if (ch->ch_digi.digi_maxchar > ch->ch_digi.digi_bufsize)
+ ch->ch_digi.digi_maxchar = ch->ch_digi.digi_bufsize;
+
+ if (ch->ch_digi.digi_onlen > DIGI_PLEN)
+ ch->ch_digi.digi_onlen = DIGI_PLEN;
+
+ if (ch->ch_digi.digi_offlen > DIGI_PLEN)
+ ch->ch_digi.digi_offlen = DIGI_PLEN;
+
+ /* make the changes now */
+ drp_param(ch);
+
+ return 0;
+}
+
+
+
+/*
+ * dgrp_tty_digigetedelay()
+ *
+ * Ioctl to get the current edelay setting.
+ *
+ *
+ *
+ */
+static int dgrp_tty_digigetedelay(struct tty_struct *tty, int *retinfo)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int tmp;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ tmp = ch->ch_edelay;
+
+ if (copy_to_user((void __user *) retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+
+/*
+ * dgrp_tty_digisetedelay()
+ *
+ * Ioctl to set the EDELAY setting
+ *
+ */
+static int dgrp_tty_digisetedelay(struct tty_struct *tty, int *new_info)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int new_digi;
+
+ if (!tty || tty->magic != TTY_MAGIC)
+ return -EFAULT;
+
+ un = tty->driver_data;
+
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ if (copy_from_user(&new_digi, (void __user *)new_info, sizeof(int)))
+ return -EFAULT;
+
+ ch->ch_edelay = new_digi;
+
+ /* make the changes now */
+ drp_param(ch);
+
+ return 0;
+}
+
+
+/*
+ * The usual assortment of ioctl's
+ *
+ * note: use tty_check_change to make sure that we are not
+ * changing the state of a terminal when we are not a process
+ * in the forground. See tty_io.c
+ * rc = tty_check_change(tty);
+ * if (rc) return rc;
+ */
+static int dgrp_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
+ unsigned long arg)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+ int rc;
+ struct digiflow_struct dflow;
+
+ if (!tty)
+ return -ENODEV;
+
+ un = tty->driver_data;
+ if (!un)
+ return -ENODEV;
+
+ ch = un->un_ch;
+ if (!ch)
+ return -ENODEV;
+
+ switch (cmd) {
+
+ /*
+ * Here are all the standard ioctl's that we MUST implement
+ */
+
+ case TCSBRK:
+ /*
+ * TCSBRK is SVID version: non-zero arg --> no break
+ * this behaviour is exploited by tcdrain().
+ *
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds
+ */
+
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+ tty_wait_until_sent(tty, 0);
+
+ if (!arg)
+ rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+ if (dgrp_tty_chars_in_buffer(tty) != 0)
+ return -EINTR;
+
+ return 0;
+
+ case TCSBRKP:
+ /* support for POSIX tcsendbreak()
+ *
+ * According to POSIX.1 spec (7.2.2.1.2) breaks should be
+ * between 0.25 and 0.5 seconds so we'll ask for something
+ * in the middle: 0.375 seconds.
+ */
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+ tty_wait_until_sent(tty, 0);
+
+ rc = dgrp_send_break(ch, arg ? arg*250 : 250);
+
+ if (dgrp_tty_chars_in_buffer(tty) != 0)
+ return -EINTR;
+ return 0;
+
+ case TIOCSBRK:
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+ tty_wait_until_sent(tty, 0);
+
+ /*
+ * RealPort doesn't support turning on a break unconditionally.
+ * The RealPort device will stop sending a break automatically
+ * after the specified time value that we send in.
+ */
+ rc = dgrp_send_break(ch, 250); /* 1/4 second */
+
+ if (dgrp_tty_chars_in_buffer(tty) != 0)
+ return -EINTR;
+ return 0;
+
+ case TIOCCBRK:
+ /*
+ * RealPort doesn't support turning off a break unconditionally.
+ * The RealPort device will stop sending a break automatically
+ * after the specified time value that was sent when turning on
+ * the break.
+ */
+ return 0;
+
+ case TIOCMGET:
+ rc = access_ok(VERIFY_WRITE, (void __user *) arg,
+ sizeof(unsigned int));
+ if (rc == 0)
+ return -EFAULT;
+ return get_modem_info(ch, (unsigned int *) arg);
+
+ case TIOCMBIS:
+ case TIOCMBIC:
+ case TIOCMSET:
+ return set_modem_info(ch, cmd, (unsigned int *) arg);
+
+ /*
+ * Here are any additional ioctl's that we want to implement
+ */
+
+ case TCFLSH:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+
+ switch (arg) {
+ case TCIFLUSH:
+ case TCIOFLUSH:
+ /* only flush input if this is the only open unit */
+ if (!IS_PRINT(MINOR(tty_devnum(tty)))) {
+ ch->ch_rout = ch->ch_rin;
+ ch->ch_send |= RR_RX_FLUSH;
+ (ch->ch_nd)->nd_tx_work = 1;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ wake_up_interruptible(
+ &(ch->ch_nd)->nd_tx_waitq);
+ }
+ if (arg == TCIFLUSH)
+ break;
+
+ case TCOFLUSH: /* flush output, or the receive buffer */
+ /*
+ * This is handled in the tty_ioctl.c code
+ * calling tty_flush_buffer
+ */
+ break;
+
+ default:
+ /* POSIX.1 says return EINVAL if we got a bad arg */
+ return -EINVAL;
+ }
+ /* pretend we didn't recognize this IOCTL */
+ return -ENOIOCTLCMD;
+
+#ifdef TIOCGETP
+ case TIOCGETP:
+#endif
+ /*****************************************
+ Linux HPUX Function
+ TCSETA TCSETA - set the termios
+ TCSETAF TCSETAF - wait for drain first, then set termios
+ TCSETAW TCSETAW - wait for drain,
+ flush the input queue, then set termios
+ - looking at the tty_ioctl code, these command all call our
+ tty_set_termios at the driver's end, when a TCSETA* is sent,
+ it is expecting the tty to have a termio structure,
+ NOT a termios structure. These two structures differ in size
+ and the tty_ioctl code does a conversion before processing them both.
+ - we should treat the TCSETAW TCSETAF ioctls the same, and let
+ the tty_ioctl code do the conversion stuff.
+
+ TCSETS
+ TCSETSF (none)
+ TCSETSW
+ - the associated tty structure has a termios structure.
+ *****************************************/
+
+ case TCGETS:
+ case TCGETA:
+ return -ENOIOCTLCMD;
+
+ case TCSETAW:
+ case TCSETAF:
+ case TCSETSF:
+ case TCSETSW:
+ /*
+ * The linux tty driver doesn't have a flush
+ * input routine for the driver, assuming all backed
+ * up data is in the line disc. buffers. However,
+ * we all know that's not the case. Here, we
+ * act on the ioctl, but then lie and say we didn't
+ * so the line discipline will process the flush
+ * also.
+ */
+
+ /*
+ * Also, now that we have TXPrint, we have to check
+ * if this is the TXPrint device and the terminal
+ * device is open. If so, do NOT run check_change,
+ * as the terminal device is ALWAYS the parent.
+ */
+ if (!IS_PRINT(MINOR(tty_devnum(tty))) ||
+ !ch->ch_tun.un_open_count) {
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+ }
+
+ /* wait for all the characters in tbuf to drain */
+ tty_wait_until_sent(tty, 0);
+
+ if ((cmd == TCSETSF) || (cmd == TCSETAF)) {
+ /* flush the contents of the rbuf queue */
+ /* TODO: check if this is print device? */
+ ch->ch_send |= RR_RX_FLUSH;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ (ch->ch_nd)->nd_tx_work = 1;
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+ /* do we need to do this? just to be safe! */
+ ch->ch_rout = ch->ch_rin;
+ }
+
+ /* pretend we didn't recognize this */
+ return -ENOIOCTLCMD;
+
+ case TCXONC:
+ /*
+ * The Linux Line Discipline (LD) would do this for us if we
+ * let it, but we have the special firmware options to do this
+ * the "right way" regardless of hardware or software flow
+ * control so we'll do it outselves instead of letting the LD
+ * do it.
+ */
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+
+ switch (arg) {
+ case TCOON:
+ dgrp_tty_start(tty);
+ return 0;
+ case TCOOFF:
+ dgrp_tty_stop(tty);
+ return 0;
+ case TCION:
+ dgrp_tty_input_start(tty);
+ return 0;
+ case TCIOFF:
+ dgrp_tty_input_stop(tty);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ case DIGI_GETA:
+ /* get information for ditty */
+ if (copy_to_user((struct digi_struct __user *) arg,
+ &ch->ch_digi, sizeof(struct digi_struct)))
+ return -EFAULT;
+ break;
+
+ case DIGI_SETAW:
+ case DIGI_SETAF:
+ /* wait for all the characters in tbuf to drain */
+ tty_wait_until_sent(tty, 0);
+
+ if (cmd == DIGI_SETAF) {
+ /* flush the contents of the rbuf queue */
+ /* send down a packet with RR_RX_FLUSH set */
+ ch->ch_send |= RR_RX_FLUSH;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ (ch->ch_nd)->nd_tx_work = 1;
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+ /* do we need to do this? just to be safe! */
+ ch->ch_rout = ch->ch_rin;
+ }
+
+ /* pretend we didn't recognize this */
+ /* fall-through */
+
+ case DIGI_SETA:
+ return dgrp_tty_digiseta(tty, (struct digi_struct *) arg);
+
+ case DIGI_SEDELAY:
+ return dgrp_tty_digisetedelay(tty, (int *) arg);
+
+ case DIGI_GEDELAY:
+ return dgrp_tty_digigetedelay(tty, (int *) arg);
+
+ case DIGI_GETFLOW:
+ case DIGI_GETAFLOW:
+ if (cmd == (DIGI_GETFLOW)) {
+ dflow.startc = tty->termios.c_cc[VSTART];
+ dflow.stopc = tty->termios.c_cc[VSTOP];
+ } else {
+ dflow.startc = ch->ch_xxon;
+ dflow.stopc = ch->ch_xxoff;
+ }
+
+ if (copy_to_user((char __user *)arg, &dflow, sizeof(dflow)))
+ return -EFAULT;
+ break;
+
+ case DIGI_SETFLOW:
+ case DIGI_SETAFLOW:
+
+ if (copy_from_user(&dflow, (char __user *)arg, sizeof(dflow)))
+ return -EFAULT;
+
+ if (cmd == (DIGI_SETFLOW)) {
+ tty->termios.c_cc[VSTART] = dflow.startc;
+ tty->termios.c_cc[VSTOP] = dflow.stopc;
+ } else {
+ ch->ch_xxon = dflow.startc;
+ ch->ch_xxoff = dflow.stopc;
+ }
+ break;
+
+ case DIGI_GETCUSTOMBAUD:
+ if (put_user(ch->ch_custom_speed, (unsigned int __user *) arg))
+ return -EFAULT;
+ break;
+
+ case DIGI_SETCUSTOMBAUD:
+ {
+ int new_rate;
+
+ if (get_user(new_rate, (unsigned int __user *) arg))
+ return -EFAULT;
+ dgrp_set_custom_speed(ch, new_rate);
+
+ break;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+/*
+ * This routine allows the tty driver to be notified when
+ * the device's termios setting have changed. Note that we
+ * should be prepared to accept the case where old == NULL
+ * and try to do something rational.
+ *
+ * So we need to make sure that our copies of ch_oflag,
+ * ch_clag, and ch_iflag reflect the tty->termios flags.
+ */
+static void dgrp_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+ struct ktermios *ts;
+ struct ch_struct *ch;
+ struct un_struct *un;
+
+ /* seems silly, but we have to check all these! */
+ if (!tty)
+ return;
+
+ un = tty->driver_data;
+ if (!un)
+ return;
+
+ ts = &tty->termios;
+
+ ch = un->un_ch;
+ if (!ch)
+ return;
+
+ drp_param(ch);
+
+ /* the CLOCAL flag has just been set */
+ if (!(old->c_cflag & CLOCAL) && C_CLOCAL(tty))
+ wake_up_interruptible(&un->un_open_wait);
+}
+
+
+/*
+ * Throttle receiving data. We just set a bit and stop reading
+ * data out of the channel buffer. It will back up and the
+ * FEP will do whatever is necessary to stop the far end.
+ */
+static void dgrp_tty_throttle(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_flag |= CH_RXSTOP;
+}
+
+
+static void dgrp_tty_unthrottle(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_flag &= ~CH_RXSTOP;
+}
+
+/*
+ * Stop the transmitter
+ */
+static void dgrp_tty_stop(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_send |= RR_TX_STOP;
+ ch->ch_send &= ~RR_TX_START;
+
+ /* make the change NOW! */
+ (ch->ch_nd)->nd_tx_ready = 1;
+ if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+}
+
+/*
+ * Start the transmitter
+ */
+static void dgrp_tty_start(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ /* TODO: don't do anything if the transmitter is not stopped */
+
+ ch->ch_send |= RR_TX_START;
+ ch->ch_send &= ~RR_TX_STOP;
+
+ /* make the change NOW! */
+ (ch->ch_nd)->nd_tx_ready = 1;
+ (ch->ch_nd)->nd_tx_work = 1;
+ if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+/*
+ * Stop the receiver
+ */
+static void dgrp_tty_input_stop(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_send |= RR_RX_STOP;
+ ch->ch_send &= ~RR_RX_START;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+static void dgrp_tty_send_xchar(struct tty_struct *tty, char c)
+{
+ struct un_struct *un;
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ un = tty->driver_data;
+ if (!un)
+ return;
+
+ ch = un->un_ch;
+ if (!ch)
+ return;
+ if (c == STOP_CHAR(tty))
+ ch->ch_send |= RR_RX_STOP;
+ else if (c == START_CHAR(tty))
+ ch->ch_send |= RR_RX_START;
+
+ ch->ch_nd->nd_tx_ready = 1;
+ ch->ch_nd->nd_tx_work = 1;
+
+ return;
+}
+
+
+static void dgrp_tty_input_start(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+
+ if (!tty)
+ return;
+
+ ch = ((struct un_struct *) tty->driver_data)->un_ch;
+ if (!ch)
+ return;
+
+ ch->ch_send |= RR_RX_START;
+ ch->ch_send &= ~RR_RX_STOP;
+ (ch->ch_nd)->nd_tx_ready = 1;
+ (ch->ch_nd)->nd_tx_work = 1;
+ if (waitqueue_active(&(ch->ch_nd)->nd_tx_waitq))
+ wake_up_interruptible(&(ch->ch_nd)->nd_tx_waitq);
+
+}
+
+
+/*
+ * Hangup the port. Like a close, but don't wait for output
+ * to drain.
+ *
+ * How do we close all the channels that are open?
+ */
+static void dgrp_tty_hangup(struct tty_struct *tty)
+{
+ struct ch_struct *ch;
+ struct nd_struct *nd;
+ struct un_struct *un;
+
+ if (!tty)
+ return;
+
+ un = tty->driver_data;
+ if (!un)
+ return;
+
+ ch = un->un_ch;
+ if (!ch)
+ return;
+
+ nd = ch->ch_nd;
+
+ if (C_HUPCL(tty)) {
+ /* LOWER DTR */
+ ch->ch_mout &= ~DM_DTR;
+ /* Don't do this here */
+ /* ch->ch_flag |= CH_HANGUP; */
+ ch->ch_nd->nd_tx_ready = 1;
+ ch->ch_nd->nd_tx_work = 1;
+ if (waitqueue_active(&ch->ch_flag_wait))
+ wake_up_interruptible(&ch->ch_flag_wait);
+ }
+
+}
+
+/************************************************************************/
+/* */
+/* TTY Initialization/Cleanup Functions */
+/* */
+/************************************************************************/
+
+/*
+ * Uninitialize the TTY portion of the supplied node. Free all
+ * memory and resources associated with this node. Do it in reverse
+ * allocation order: this might possibly result in less fragmentation
+ * of memory, though I don't know this for sure.
+ */
+void
+dgrp_tty_uninit(struct nd_struct *nd)
+{
+ unsigned int i;
+ char id[3];
+
+ ID_TO_CHAR(nd->nd_ID, id);
+
+ if (nd->nd_ttdriver_flags & SERIAL_TTDRV_REG) {
+ tty_unregister_driver(nd->nd_serial_ttdriver);
+
+ kfree(nd->nd_serial_ttdriver->ttys);
+ nd->nd_serial_ttdriver->ttys = NULL;
+
+ put_tty_driver(nd->nd_serial_ttdriver);
+ nd->nd_ttdriver_flags &= ~SERIAL_TTDRV_REG;
+ }
+
+ if (nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG) {
+ tty_unregister_driver(nd->nd_callout_ttdriver);
+
+ kfree(nd->nd_callout_ttdriver->ttys);
+ nd->nd_callout_ttdriver->ttys = NULL;
+
+ put_tty_driver(nd->nd_callout_ttdriver);
+ nd->nd_ttdriver_flags &= ~CALLOUT_TTDRV_REG;
+ }
+
+ if (nd->nd_ttdriver_flags & XPRINT_TTDRV_REG) {
+ tty_unregister_driver(nd->nd_xprint_ttdriver);
+
+ kfree(nd->nd_xprint_ttdriver->ttys);
+ nd->nd_xprint_ttdriver->ttys = NULL;
+
+ put_tty_driver(nd->nd_xprint_ttdriver);
+ nd->nd_ttdriver_flags &= ~XPRINT_TTDRV_REG;
+ }
+ for (i = 0; i < CHAN_MAX; i++)
+ tty_port_destroy(&nd->nd_chan[i].port);
+}
+
+
+
+/*
+ * Initialize the TTY portion of the supplied node.
+ */
+int
+dgrp_tty_init(struct nd_struct *nd)
+{
+ char id[3];
+ int rc;
+ int i;
+
+ ID_TO_CHAR(nd->nd_ID, id);
+
+ /*
+ * Initialize the TTDRIVER structures.
+ */
+
+ nd->nd_serial_ttdriver = alloc_tty_driver(CHAN_MAX);
+ if (!nd->nd_serial_ttdriver)
+ return -ENOMEM;
+
+ sprintf(nd->nd_serial_name, "tty_dgrp_%s_", id);
+
+ nd->nd_serial_ttdriver->owner = THIS_MODULE;
+ nd->nd_serial_ttdriver->name = nd->nd_serial_name;
+ nd->nd_serial_ttdriver->name_base = 0;
+ nd->nd_serial_ttdriver->major = 0;
+ nd->nd_serial_ttdriver->minor_start = 0;
+ nd->nd_serial_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+ nd->nd_serial_ttdriver->subtype = SERIAL_TYPE_NORMAL;
+ nd->nd_serial_ttdriver->init_termios = DefaultTermios;
+ nd->nd_serial_ttdriver->driver_name = "dgrp";
+ nd->nd_serial_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_HARDWARE_BREAK);
+
+ /* The kernel wants space to store pointers to tty_structs. */
+ nd->nd_serial_ttdriver->ttys =
+ kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+ if (!nd->nd_serial_ttdriver->ttys)
+ return -ENOMEM;
+
+ tty_set_operations(nd->nd_serial_ttdriver, &dgrp_tty_ops);
+
+ if (!(nd->nd_ttdriver_flags & SERIAL_TTDRV_REG)) {
+ /*
+ * Register tty devices
+ */
+ rc = tty_register_driver(nd->nd_serial_ttdriver);
+ if (rc < 0) {
+ /*
+ * If errno is EBUSY, this means there are no more
+ * slots available to have us auto-majored.
+ * (Which is currently supported up to 256)
+ *
+ * We can still request majors above 256,
+ * we just have to do it manually.
+ */
+ if (rc == -EBUSY) {
+ int i;
+ int max_majors = 1U << (32 - MINORBITS);
+ for (i = 256; i < max_majors; i++) {
+ nd->nd_serial_ttdriver->major = i;
+ rc = tty_register_driver
+ (nd->nd_serial_ttdriver);
+ if (rc >= 0)
+ break;
+ }
+ /* Really fail now, since we ran out
+ * of majors to try. */
+ if (i == max_majors)
+ return rc;
+
+ } else {
+ return rc;
+ }
+ }
+ nd->nd_ttdriver_flags |= SERIAL_TTDRV_REG;
+ }
+
+ nd->nd_callout_ttdriver = alloc_tty_driver(CHAN_MAX);
+ if (!nd->nd_callout_ttdriver)
+ return -ENOMEM;
+
+ sprintf(nd->nd_callout_name, "cu_dgrp_%s_", id);
+
+ nd->nd_callout_ttdriver->owner = THIS_MODULE;
+ nd->nd_callout_ttdriver->name = nd->nd_callout_name;
+ nd->nd_callout_ttdriver->name_base = 0;
+ nd->nd_callout_ttdriver->major = nd->nd_serial_ttdriver->major;
+ nd->nd_callout_ttdriver->minor_start = 0x40;
+ nd->nd_callout_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+ nd->nd_callout_ttdriver->subtype = SERIAL_TYPE_CALLOUT;
+ nd->nd_callout_ttdriver->init_termios = DefaultTermios;
+ nd->nd_callout_ttdriver->driver_name = "dgrp";
+ nd->nd_callout_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_HARDWARE_BREAK);
+
+ /* The kernel wants space to store pointers to tty_structs. */
+ nd->nd_callout_ttdriver->ttys =
+ kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+ if (!nd->nd_callout_ttdriver->ttys)
+ return -ENOMEM;
+
+ tty_set_operations(nd->nd_callout_ttdriver, &dgrp_tty_ops);
+
+ if (dgrp_register_cudevices) {
+ if (!(nd->nd_ttdriver_flags & CALLOUT_TTDRV_REG)) {
+ /*
+ * Register cu devices
+ */
+ rc = tty_register_driver(nd->nd_callout_ttdriver);
+ if (rc < 0)
+ return rc;
+ nd->nd_ttdriver_flags |= CALLOUT_TTDRV_REG;
+ }
+ }
+
+
+ nd->nd_xprint_ttdriver = alloc_tty_driver(CHAN_MAX);
+ if (!nd->nd_xprint_ttdriver)
+ return -ENOMEM;
+
+ sprintf(nd->nd_xprint_name, "pr_dgrp_%s_", id);
+
+ nd->nd_xprint_ttdriver->owner = THIS_MODULE;
+ nd->nd_xprint_ttdriver->name = nd->nd_xprint_name;
+ nd->nd_xprint_ttdriver->name_base = 0;
+ nd->nd_xprint_ttdriver->major = nd->nd_serial_ttdriver->major;
+ nd->nd_xprint_ttdriver->minor_start = 0x80;
+ nd->nd_xprint_ttdriver->type = TTY_DRIVER_TYPE_SERIAL;
+ nd->nd_xprint_ttdriver->subtype = SERIAL_TYPE_XPRINT;
+ nd->nd_xprint_ttdriver->init_termios = DefaultTermios;
+ nd->nd_xprint_ttdriver->driver_name = "dgrp";
+ nd->nd_xprint_ttdriver->flags = (TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV |
+ TTY_DRIVER_HARDWARE_BREAK);
+
+ /* The kernel wants space to store pointers to tty_structs. */
+ nd->nd_xprint_ttdriver->ttys =
+ kzalloc(CHAN_MAX * sizeof(struct tty_struct *), GFP_KERNEL);
+ if (!nd->nd_xprint_ttdriver->ttys)
+ return -ENOMEM;
+
+ tty_set_operations(nd->nd_xprint_ttdriver, &dgrp_tty_ops);
+
+ if (dgrp_register_prdevices) {
+ if (!(nd->nd_ttdriver_flags & XPRINT_TTDRV_REG)) {
+ /*
+ * Register transparent print devices
+ */
+ rc = tty_register_driver(nd->nd_xprint_ttdriver);
+ if (rc < 0)
+ return rc;
+ nd->nd_ttdriver_flags |= XPRINT_TTDRV_REG;
+ }
+ }
+
+ for (i = 0; i < CHAN_MAX; i++) {
+ struct ch_struct *ch = nd->nd_chan + i;
+
+ ch->ch_nd = nd;
+ ch->ch_digi = digi_init;
+ ch->ch_edelay = 100;
+ ch->ch_custom_speed = 0;
+ ch->ch_portnum = i;
+ ch->ch_tun.un_ch = ch;
+ ch->ch_pun.un_ch = ch;
+ ch->ch_tun.un_type = SERIAL_TYPE_NORMAL;
+ ch->ch_pun.un_type = SERIAL_TYPE_XPRINT;
+
+ init_waitqueue_head(&(ch->ch_flag_wait));
+ init_waitqueue_head(&(ch->ch_sleep));
+
+ init_waitqueue_head(&(ch->ch_tun.un_open_wait));
+ init_waitqueue_head(&(ch->ch_tun.un_close_wait));
+
+ init_waitqueue_head(&(ch->ch_pun.un_open_wait));
+ init_waitqueue_head(&(ch->ch_pun.un_close_wait));
+ tty_port_init(&ch->port);
+ }
+ return 0;
+}
diff --git a/drivers/staging/dgrp/digirp.h b/drivers/staging/dgrp/digirp.h
new file mode 100644
index 00000000000..33c1394fade
--- /dev/null
+++ b/drivers/staging/dgrp/digirp.h
@@ -0,0 +1,129 @@
+/************************************************************************
+ * HP-UX Realport Daemon interface file.
+ *
+ * Copyright (C) 1998, by Digi International. All Rights Reserved.
+ ************************************************************************/
+
+#ifndef _DIGIDRP_H
+#define _DIGIDRP_H
+
+/************************************************************************
+ * This file contains defines for the ioctl() interface to
+ * the realport driver. This ioctl() interface is used by the
+ * daemon to set speed setup parameters honored by the driver.
+ ************************************************************************/
+
+struct link_struct {
+ int lk_fast_rate; /* Fast line rate to be used
+ when the delay is less-equal
+ to lk_fast_delay */
+
+ int lk_fast_delay; /* Fast line rate delay in
+ milliseconds */
+
+ int lk_slow_rate; /* Slow line rate to be used when
+ the delay is greater-equal
+ to lk_slow_delay */
+
+ int lk_slow_delay; /* Slow line rate delay in
+ milliseconds */
+
+ int lk_header_size; /* Estimated packet header size
+ when sent across the slowest
+ link. */
+};
+
+#define DIGI_GETLINK _IOW('e', 103, struct link_struct) /* Get link parameters */
+#define DIGI_SETLINK _IOW('e', 104, struct link_struct) /* Set link parameters */
+
+
+/************************************************************************
+ * This module provides application access to special Digi
+ * serial line enhancements which are not standard UNIX(tm) features.
+ ************************************************************************/
+
+struct digiflow_struct {
+ unsigned char startc; /* flow cntl start char */
+ unsigned char stopc; /* flow cntl stop char */
+};
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON 0x0001 /* Handle IXON in the FEP */
+#define DIGI_FAST 0x0002 /* Fast baud rates */
+#define RTSPACE 0x0004 /* RTS input flow control */
+#define CTSPACE 0x0008 /* CTS output flow control */
+#define DSRPACE 0x0010 /* DSR output flow control */
+#define DCDPACE 0x0020 /* DCD output flow control */
+#define DTRPACE 0x0040 /* DTR input flow control */
+#define DIGI_COOK 0x0080 /* Cooked processing done in FEP */
+#define DIGI_FORCEDCD 0x0100 /* Force carrier */
+#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+#define DIGI_PRINTER 0x0800 /* Hold port open for flow cntrl */
+#define DIGI_PP_INPUT 0x1000 /* Change parallel port to input */
+#define DIGI_422 0x4000 /* Change parallel port to input */
+#define DIGI_RTS_TOGGLE 0x8000 /* Support RTS Toggle */
+
+
+/************************************************************************
+ * Values associated with transparent print
+ ************************************************************************/
+#define DIGI_PLEN 8 /* String length */
+#define DIGI_TSIZ 10 /* Terminal string len */
+
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct {
+ unsigned short digi_flags; /* Flags (see above) */
+ unsigned short digi_maxcps; /* Max printer CPS */
+ unsigned short digi_maxchar; /* Max chars in print queue */
+ unsigned short digi_bufsize; /* Buffer size */
+ unsigned char digi_onlen; /* Length of ON string */
+ unsigned char digi_offlen; /* Length of OFF string */
+ char digi_onstr[DIGI_PLEN]; /* Printer on string */
+ char digi_offstr[DIGI_PLEN]; /* Printer off string */
+ char digi_term[DIGI_TSIZ]; /* terminal string */
+};
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+/* Read params */
+#define DIGI_GETA _IOR('e', 94, struct digi_struct)
+
+/* Set params */
+#define DIGI_SETA _IOW('e', 95, struct digi_struct)
+
+/* Drain & set params */
+#define DIGI_SETAW _IOW('e', 96, struct digi_struct)
+
+/* Drain, flush & set params */
+#define DIGI_SETAF _IOW('e', 97, struct digi_struct)
+
+/* Get startc/stopc flow control characters */
+#define DIGI_GETFLOW _IOR('e', 99, struct digiflow_struct)
+
+/* Set startc/stopc flow control characters */
+#define DIGI_SETFLOW _IOW('e', 100, struct digiflow_struct)
+
+/* Get Aux. startc/stopc flow control chars */
+#define DIGI_GETAFLOW _IOR('e', 101, struct digiflow_struct)
+
+/* Set Aux. startc/stopc flow control chars */
+#define DIGI_SETAFLOW _IOW('e', 102, struct digiflow_struct)
+
+/* Set integer baud rate */
+#define DIGI_SETCUSTOMBAUD _IOW('e', 106, int)
+
+/* Get integer baud rate */
+#define DIGI_GETCUSTOMBAUD _IOR('e', 107, int)
+
+#define DIGI_GEDELAY _IOR('d', 246, int) /* Get edelay */
+#define DIGI_SEDELAY _IOW('d', 247, int) /* Get edelay */
+
+
+#endif /* _DIGIDRP_H */
diff --git a/drivers/staging/dgrp/drp.h b/drivers/staging/dgrp/drp.h
new file mode 100644
index 00000000000..4024b488eba
--- /dev/null
+++ b/drivers/staging/dgrp/drp.h
@@ -0,0 +1,693 @@
+/*
+ *
+ * Copyright 1999 Digi International (www.digi.com)
+ * Gene Olson <gene at digi dot com>
+ * James Puzzo <jamesp at digi dot com>
+ * Scott Kilau <scottk at digi dot 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, 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.
+ *
+ */
+
+/************************************************************************
+ * Master include file for Linux Realport Driver.
+ ************************************************************************/
+
+#ifndef __DRP_H
+#define __DRP_H
+
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/semaphore.h>
+#include <linux/tty.h>
+
+
+#include "digirp.h"
+
+/************************************************************************
+ * Tuning parameters.
+ ************************************************************************/
+
+#define CHAN_MAX 64 /* Max # ports per server */
+
+#define SEQ_MAX 128 /* Max # transmit sequences (2^n) */
+#define SEQ_MASK (SEQ_MAX-1) /* Sequence buffer modulus mask */
+
+#define TBUF_MAX 4096 /* Size of transmit buffer (2^n) */
+#define RBUF_MAX 4096 /* Size of receive buffer (2^n) */
+
+#define TBUF_MASK (TBUF_MAX-1) /* Transmit buffer modulus mask */
+#define RBUF_MASK (RBUF_MAX-1) /* Receive buffer modulus mask */
+
+#define TBUF_LOW 1000 /* Transmit low water mark */
+
+#define UIO_BASE 1000 /* Base for write operations */
+#define UIO_MIN 2000 /* Minimum size application buffer */
+#define UIO_MAX 8100 /* Unix I/O buffer size */
+
+#define MON_MAX 65536 /* Monitor buffer size (2^n) */
+#define MON_MASK (MON_MAX-1) /* Monitor wrap mask */
+
+#define DPA_MAX 65536 /* DPA buffer size (2^n) */
+#define DPA_MASK (DPA_MAX-1) /* DPA wrap mask */
+#define DPA_HIGH_WATER 58000 /* Enforce flow control when
+ * over this amount
+ */
+
+#define IDLE_MAX (20 * HZ) /* Max TCP link idle time */
+
+#define MAX_DESC_LEN 100 /* Maximum length of stored PS
+ * description
+ */
+
+#define WRITEBUFLEN ((4096) + 4) /* 4 extra for alignment play space */
+
+#define VPDSIZE 512
+
+/************************************************************************
+ * Minor device decoding conventions.
+ ************************************************************************
+ *
+ * For Linux, the net and mon devices are handled via "proc", so we
+ * only have to mux the "tty" devices. Since every PortServer will
+ * have an individual major number, the PortServer number does not
+ * need to be encoded, and in fact, does not need to exist.
+ *
+ */
+
+/*
+ * Port device decoding conventions:
+ *
+ * Device 00 - 3f 64 dial-in modem devices. (tty)
+ * Device 40 - 7f 64 dial-out tty devices. (cu)
+ * Device 80 - bf 64 dial-out printer devices.
+ *
+ * IS_PRINT(dev) This is a printer device.
+ *
+ * OPEN_CATEGORY(dev) Specifies the device category. No two
+ * devices of different categories may be open
+ * at the same time.
+ *
+ * The following require the category returned by OPEN_CATEGORY().
+ *
+ * OPEN_WAIT_AVAIL(cat) Waits on open until the device becomes
+ * available. Fails if NDELAY specified.
+ *
+ * OPEN_WAIT_CARRIER(cat) Waits on open if carrier is not present.
+ * Succeeds if NDELAY is given.
+ *
+ * OPEN_FORCES_CARRIER(cat) Carrier is forced high on open.
+ *
+ */
+
+#define PORT_NUM(dev) ((dev) & 0x3f)
+
+#define OPEN_CATEGORY(dev) ((((dev) & 0x80) & 0x40))
+#define IS_PRINT(dev) (((dev) & 0xff) >= 0x80)
+
+#define OPEN_WAIT_AVAIL(cat) (((cat) & 0x40) == 0x000)
+#define OPEN_WAIT_CARRIER(cat) (((cat) & 0x40) == 0x000)
+#define OPEN_FORCES_CARRIER(cat) (((cat) & 0x40) != 0x000)
+
+
+/************************************************************************
+ * Modem signal defines for 16450/16550 compatible FEP.
+ * set in ch_mout, ch_mflow, ch_mlast etc
+ ************************************************************************/
+
+/* TODO : Re-verify that these modem signal definitions are correct */
+
+#define DM_DTR 0x01
+#define DM_RTS 0x02
+#define DM_RTS_TOGGLE 0x04
+
+#define DM_OUT1 0x04
+#define DM_OUT2 0x08
+
+#define DM_CTS 0x10
+#define DM_DSR 0x20
+#define DM_RI 0x40
+#define DM_CD 0x80 /* This is the DCD flag */
+
+
+/************************************************************************
+ * Realport Event Flags.
+ ************************************************************************/
+
+#define EV_OPU 0x0001 /* Ouput paused by client */
+#define EV_OPS 0x0002 /* Output paused by XOFF */
+#define EV_OPX 0x0004 /* Output paused by XXOFF */
+#define EV_OPH 0x0008 /* Output paused by MFLOW */
+#define EV_IPU 0x0010 /* Input paused by client */
+#define EV_IPS 0x0020 /* Input paused by hi/low water */
+#define EV_TXB 0x0040 /* Transmit break pending */
+#define EV_TXI 0x0080 /* Transmit immediate pending */
+#define EV_TXF 0x0100 /* Transmit flow control pending */
+#define EV_RXB 0x0200 /* Break received */
+
+
+/************************************************************************
+ * Realport CFLAGS.
+ ************************************************************************/
+
+#define CF_CS5 0x0000 /* 5 bit characters */
+#define CF_CS6 0x0010 /* 6 bit characters */
+#define CF_CS7 0x0020 /* 7 bit characters */
+#define CF_CS8 0x0030 /* 8 bit characters */
+#define CF_CSIZE 0x0030 /* Character size */
+#define CF_CSTOPB 0x0040 /* Two stop bits */
+#define CF_CREAD 0x0080 /* Enable receiver */
+#define CF_PARENB 0x0100 /* Enable parity */
+#define CF_PARODD 0x0200 /* Odd parity */
+#define CF_HUPCL 0x0400 /* Drop DTR on close */
+
+
+/************************************************************************
+ * Realport XFLAGS.
+ ************************************************************************/
+
+#define XF_XPAR 0x0001 /* Enable Mark/Space Parity */
+#define XF_XMODEM 0x0002 /* Enable in-band modem signalling */
+#define XF_XCASE 0x0004 /* Convert special characters */
+#define XF_XEDATA 0x0008 /* Error data in stream */
+#define XF_XTOSS 0x0010 /* Toss IXANY characters */
+#define XF_XIXON 0x0020 /* xxon/xxoff enable */
+
+
+/************************************************************************
+ * Realport IFLAGS.
+ ************************************************************************/
+
+#define IF_IGNBRK 0x0001 /* Ignore input break */
+#define IF_BRKINT 0x0002 /* Break interrupt */
+#define IF_IGNPAR 0x0004 /* Ignore error characters */
+#define IF_PARMRK 0x0008 /* Error chars marked with 0xff */
+#define IF_INPCK 0x0010 /* Input parity checking enabled */
+#define IF_ISTRIP 0x0020 /* Input chars masked with 0x7F */
+#define IF_IXON 0x0400 /* Output software flow control */
+#define IF_IXANY 0x0800 /* Restart output on any char */
+#define IF_IXOFF 0x1000 /* Input software flow control */
+#define IF_DOSMODE 0x8000 /* 16450-compatible errors */
+
+
+/************************************************************************
+ * Realport OFLAGS.
+ ************************************************************************/
+
+#define OF_OLCUC 0x0002 /* Map lower to upper case */
+#define OF_ONLCR 0x0004 /* Map NL to CR-NL */
+#define OF_OCRNL 0x0008 /* Map CR to NL */
+#define OF_ONOCR 0x0010 /* No CR output at column 0 */
+#define OF_ONLRET 0x0020 /* Assume NL does NL/CR */
+#define OF_TAB3 0x1800 /* Tabs expand to 8 spaces */
+#define OF_TABDLY 0x1800 /* Tab delay */
+
+/************************************************************************
+ * Unit flag definitions for un_flag.
+ ************************************************************************/
+
+/* These are the DIGI unit flags */
+#define UN_EXCL 0x00010000 /* Exclusive open */
+#define UN_STICKY 0x00020000 /* TTY Settings are now sticky */
+#define UN_BUSY 0x00040000 /* Some work this channel */
+#define UN_PWAIT 0x00080000 /* Printer waiting for terminal */
+#define UN_TIME 0x00100000 /* Waiting on time */
+#define UN_EMPTY 0x00200000 /* Waiting output queue empty */
+#define UN_LOW 0x00400000 /* Waiting output low water */
+#define UN_DIGI_MASK 0x00FF0000 /* Waiting output low water */
+
+/*
+ * Definitions for async_struct (and serial_struct) flags field
+ *
+ * these are the ASYNC flags copied from serial.h
+ *
+ */
+#define UN_HUP_NOTIFY 0x0001 /* Notify getty on hangups and
+ * closes on the callout port
+ */
+#define UN_FOURPORT 0x0002 /* Set OU1, OUT2 per AST Fourport settings */
+#define UN_SAK 0x0004 /* Secure Attention Key (Orange book) */
+#define UN_SPLIT_TERMIOS 0x0008 /* Separate termios for dialin/callout */
+
+#define UN_SPD_MASK 0x0030
+#define UN_SPD_HI 0x0010 /* Use 56000 instead of 38400 bps */
+#define UN_SPD_VHI 0x0020 /* Use 115200 instead of 38400 bps */
+#define UN_SPD_CUST 0x0030 /* Use user-specified divisor */
+
+#define UN_SKIP_TEST 0x0040 /* Skip UART test during autoconfiguration */
+#define UN_AUTO_IRQ 0x0080 /* Do automatic IRQ during autoconfiguration */
+
+#define UN_SESSION_LOCKOUT 0x0100 /* Lock out cua opens based on session */
+#define UN_PGRP_LOCKOUT 0x0200 /* Lock out cua opens based on pgrp */
+#define UN_CALLOUT_NOHUP 0x0400 /* Don't do hangups for cua device */
+
+#define UN_FLAGS 0x0FFF /* Possible legal async flags */
+#define UN_USR_MASK 0x0430 /* Legal flags that non-privileged
+ * users can set or reset
+ */
+
+#define UN_INITIALIZED 0x80000000 /* Serial port was initialized */
+#define UN_CALLOUT_ACTIVE 0x40000000 /* Call out device is active */
+#define UN_NORMAL_ACTIVE 0x20000000 /* Normal device is active */
+#define UN_BOOT_AUTOCONF 0x10000000 /* Autoconfigure port on bootup */
+#define UN_CLOSING 0x08000000 /* Serial port is closing */
+#define UN_CTS_FLOW 0x04000000 /* Do CTS flow control */
+#define UN_CHECK_CD 0x02000000 /* i.e., CLOCAL */
+#define UN_SHARE_IRQ 0x01000000 /* for multifunction cards */
+
+
+/************************************************************************
+ * Structure for terminal or printer unit. struct un_struct
+ *
+ * Note that in some places the code assumes the "tty_t" is placed
+ * first in the structure.
+ ************************************************************************/
+
+struct un_struct {
+ struct tty_struct *un_tty; /* System TTY struct */
+ struct ch_struct *un_ch; /* Associated channel */
+
+ ushort un_open_count; /* Successful open count */
+ int un_flag; /* Unit flags */
+ ushort un_tbusy; /* Busy transmit count */
+
+ wait_queue_head_t un_open_wait;
+ wait_queue_head_t un_close_wait;
+ ushort un_type;
+ struct device *un_sysfs;
+};
+
+
+/************************************************************************
+ * Channel State Numbers for ch_state.
+ ************************************************************************/
+
+/*
+ * The ordering is important.
+ *
+ * state <= CS_WAIT_CANCEL implies the channel is definitely closed.
+ *
+ * state >= CS_WAIT_FAIL implies the channel is definitely open.
+ *
+ * state >= CS_READY implies data is allowed on the channel.
+ */
+
+enum dgrp_ch_state_t {
+ CS_IDLE = 0, /* Channel is idle */
+ CS_WAIT_OPEN = 1, /* Waiting for Immediate Open Resp */
+ CS_WAIT_CANCEL = 2, /* Waiting for Per/Incom Cancel Resp */
+ CS_WAIT_FAIL = 3, /* Waiting for Immed Open Failure */
+ CS_SEND_QUERY = 4, /* Ready to send Port Query */
+ CS_WAIT_QUERY = 5, /* Waiting for Port Query Response */
+ CS_READY = 6, /* Ready to accept commands and data */
+ CS_SEND_CLOSE = 7, /* Ready to send Close Request */
+ CS_WAIT_CLOSE = 8 /* Waiting for Close Response */
+};
+
+/************************************************************************
+ * Device flag definitions for ch_flag.
+ ************************************************************************/
+
+/*
+ * Note that the state of the two carrier based flags is key. When
+ * we check for carrier state transitions, we look at the current
+ * physical state of the DCD line and compare it with PHYS_CD (which
+ * was the state the last time we checked), and we also determine
+ * a new virtual state (composite of the physical state, FORCEDCD,
+ * CLOCAL, etc.) and compare it with VIRT_CD.
+ *
+ * VIRTUAL transitions high will have the side effect of waking blocked
+ * opens.
+ *
+ * PHYSICAL transitions low will cause hangups to occur _IF_ the virtual
+ * state is also low. We DON'T want to hangup on a PURE virtual drop.
+ */
+
+#define CH_HANGUP 0x00002 /* Server port ready to close */
+
+#define CH_VIRT_CD 0x00004 /* Carrier was virtually present */
+#define CH_PHYS_CD 0x00008 /* Carrier was physically present */
+
+#define CH_CLOCAL 0x00010 /* CLOCAL set in cflags */
+#define CH_BAUD0 0x00020 /* Baud rate zero hangup */
+
+#define CH_FAST_READ 0x00040 /* Fast reads are enabled */
+#define CH_FAST_WRITE 0x00080 /* Fast writes are enabled */
+
+#define CH_PRON 0x00100 /* Printer on string active */
+#define CH_RX_FLUSH 0x00200 /* Flushing receive data */
+#define CH_LOW 0x00400 /* Thread waiting for LOW water */
+#define CH_EMPTY 0x00800 /* Thread waiting for EMPTY */
+#define CH_DRAIN 0x01000 /* Close is waiting to drain */
+#define CH_INPUT 0x02000 /* Thread waiting for INPUT */
+#define CH_RXSTOP 0x04000 /* Stop output to ldisc */
+#define CH_PARAM 0x08000 /* A parameter was updated */
+#define CH_WAITING_SYNC 0x10000 /* A pending sync was assigned
+ * to this port.
+ */
+#define CH_PORT_GONE 0x20000 /* Port has disappeared */
+#define CH_TX_BREAK 0x40000 /* TX Break to be sent,
+ * but has not yet.
+ */
+
+/************************************************************************
+ * Types of Open Requests for ch_otype.
+ ************************************************************************/
+
+#define OTYPE_IMMEDIATE 0 /* Immediate Open */
+#define OTYPE_PERSISTENT 1 /* Persistent Open */
+#define OTYPE_INCOMING 2 /* Incoming Open */
+
+
+/************************************************************************
+ * Request/Response flags.
+ ************************************************************************/
+
+#define RR_SEQUENCE 0x0001 /* Get server RLAST, TIN */
+#define RR_STATUS 0x0002 /* Get server MINT, EINT */
+#define RR_BUFFER 0x0004 /* Get server RSIZE, TSIZE */
+#define RR_CAPABILITY 0x0008 /* Get server port capabilities */
+
+#define RR_TX_FLUSH 0x0040 /* Flush output buffers */
+#define RR_RX_FLUSH 0x0080 /* Flush input buffers */
+
+#define RR_TX_STOP 0x0100 /* Pause output */
+#define RR_RX_STOP 0x0200 /* Pause input */
+#define RR_TX_START 0x0400 /* Start output */
+#define RR_RX_START 0x0800 /* Start input */
+
+#define RR_TX_BREAK 0x1000 /* Send BREAK */
+#define RR_TX_ICHAR 0x2000 /* Send character immediate */
+
+
+/************************************************************************
+ * Channel information structure. struct ch_struct
+ ************************************************************************/
+
+struct ch_struct {
+ struct digi_struct ch_digi; /* Digi variables */
+ int ch_edelay; /* Digi edelay */
+
+ struct tty_port port;
+ struct un_struct ch_tun; /* Terminal unit info */
+ struct un_struct ch_pun; /* Printer unit info */
+
+ struct nd_struct *ch_nd; /* Node pointer */
+ u8 *ch_tbuf; /* Local Transmit Buffer */
+ u8 *ch_rbuf; /* Local Receive Buffer */
+ ulong ch_cpstime; /* Printer CPS time */
+ ulong ch_waketime; /* Printer wake time */
+
+ ulong ch_flag; /* CH_* flags */
+
+ enum dgrp_ch_state_t ch_state; /* CS_* Protocol state */
+ ushort ch_send; /* Bit vector of RR_* requests */
+ ushort ch_expect; /* Bit vector of RR_* responses */
+ ushort ch_wait_carrier; /* Thread count waiting for carrier */
+ ushort ch_wait_count[3]; /* Thread count waiting by otype */
+
+ ushort ch_portnum; /* Port number */
+ ushort ch_open_count; /* Successful open count */
+ ushort ch_category; /* Device category */
+ ushort ch_open_error; /* Last open error number */
+ ushort ch_break_time; /* Pending break request time */
+ ushort ch_cpsrem; /* Printer CPS remainder */
+ ushort ch_ocook; /* Realport fastcook oflags */
+ ushort ch_inwait; /* Thread count in CLIST input */
+
+ ushort ch_tin; /* Local transmit buffer in ptr */
+ ushort ch_tout; /* Local transmit buffer out ptr */
+ ushort ch_s_tin; /* Realport TIN */
+ ushort ch_s_tpos; /* Realport TPOS */
+ ushort ch_s_tsize; /* Realport TSIZE */
+ ushort ch_s_treq; /* Realport TREQ */
+ ushort ch_s_elast; /* Realport ELAST */
+
+ ushort ch_rin; /* Local receive buffer in ptr */
+ ushort ch_rout; /* Local receive buffer out ptr */
+ ushort ch_s_rin; /* Realport RIN */
+ /* David Fries 7-13-2001, ch_s_rin should be renamed ch_s_rout because
+ * the variable we want to represent is the PortServer's ROUT, which is
+ * the sequence number for the next byte the PortServer will send us.
+ * RIN is the sequence number for the next byte the PortServer will
+ * receive from the uart. The port server will send data as long as
+ * ROUT is less than RWIN. What would happen is the port is opened, it
+ * receives data, it gives the value of RIN, we set the RWIN to
+ * RIN+RBUF_MAX-1, it sends us RWIN-ROUT bytes which overflows. ROUT
+ * is set to zero when the port is opened, so we start at zero and
+ * count up as data is received.
+ */
+ ushort ch_s_rwin; /* Realport RWIN */
+ ushort ch_s_rsize; /* Realport RSIZE */
+
+ ushort ch_tmax; /* Local TMAX */
+ ushort ch_ttime; /* Local TTIME */
+ ushort ch_rmax; /* Local RMAX */
+ ushort ch_rtime; /* Local RTIME */
+ ushort ch_rlow; /* Local RLOW */
+ ushort ch_rhigh; /* Local RHIGH */
+
+ ushort ch_s_tmax; /* Realport TMAX */
+ ushort ch_s_ttime; /* Realport TTIME */
+ ushort ch_s_rmax; /* Realport RMAX */
+ ushort ch_s_rtime; /* Realport RTIME */
+ ushort ch_s_rlow; /* Realport RLOW */
+ ushort ch_s_rhigh; /* Realport RHIGH */
+
+ ushort ch_brate; /* Local baud rate */
+ ushort ch_cflag; /* Local tty cflags */
+ ushort ch_iflag; /* Local tty iflags */
+ ushort ch_oflag; /* Local tty oflags */
+ ushort ch_xflag; /* Local tty xflags */
+
+ ushort ch_s_brate; /* Realport BRATE */
+ ushort ch_s_cflag; /* Realport CFLAG */
+ ushort ch_s_iflag; /* Realport IFLAG */
+ ushort ch_s_oflag; /* Realport OFLAG */
+ ushort ch_s_xflag; /* Realport XFLAG */
+
+ u8 ch_otype; /* Open request type */
+ u8 ch_pscan_savechar; /* Last character read by parity scan */
+ u8 ch_pscan_state; /* PScan State based on last 2 chars */
+ u8 ch_otype_waiting; /* Type of open pending in server */
+ u8 ch_flush_seq; /* Receive flush end sequence */
+ u8 ch_s_mlast; /* Realport MLAST */
+
+ u8 ch_mout; /* Local MOUT */
+ u8 ch_mflow; /* Local MFLOW */
+ u8 ch_mctrl; /* Local MCTRL */
+ u8 ch_xon; /* Local XON */
+ u8 ch_xoff; /* Local XOFF */
+ u8 ch_lnext; /* Local LNEXT */
+ u8 ch_xxon; /* Local XXON */
+ u8 ch_xxoff; /* Local XXOFF */
+
+ u8 ch_s_mout; /* Realport MOUT */
+ u8 ch_s_mflow; /* Realport MFLOW */
+ u8 ch_s_mctrl; /* Realport MCTRL */
+ u8 ch_s_xon; /* Realport XON */
+ u8 ch_s_xoff; /* Realport XOFF */
+ u8 ch_s_lnext; /* Realport LNEXT */
+ u8 ch_s_xxon; /* Realport XXON */
+ u8 ch_s_xxoff; /* Realport XXOFF */
+
+ wait_queue_head_t ch_flag_wait; /* Wait queue for ch_flag changes */
+ wait_queue_head_t ch_sleep; /* Wait queue for my_sleep() */
+
+ int ch_custom_speed; /* Realport custom speed */
+ int ch_txcount; /* Running TX count */
+ int ch_rxcount; /* Running RX count */
+};
+
+
+/************************************************************************
+ * Node State definitions.
+ ************************************************************************/
+
+enum dgrp_nd_state_t {
+ NS_CLOSED = 0, /* Network device is closed */
+ NS_IDLE = 1, /* Network connection inactive */
+ NS_SEND_QUERY = 2, /* Send server query */
+ NS_WAIT_QUERY = 3, /* Wait for query response */
+ NS_READY = 4, /* Network ready */
+ NS_SEND_ERROR = 5 /* Must send error hangup */
+};
+
+#define ND_STATE_STR(x) \
+ ((x) == NS_CLOSED ? "CLOSED" : \
+ ((x) == NS_IDLE ? "IDLE" : \
+ ((x) == NS_SEND_QUERY ? "SEND_QUERY" : \
+ ((x) == NS_WAIT_QUERY ? "WAIT_QUERY" : \
+ ((x) == NS_READY ? "READY" : \
+ ((x) == NS_SEND_ERROR ? "SEND_ERROR" : "UNKNOWN"))))))
+
+/************************************************************************
+ * Node Flag definitions.
+ ************************************************************************/
+
+#define ND_SELECT 0x0001 /* Multiple net read selects */
+#define ND_DEB_WAIT 0x0002 /* Debug Device waiting */
+
+
+/************************************************************************
+ * Monitoring flag definitions.
+ ************************************************************************/
+
+#define MON_WAIT_DATA 0x0001 /* Waiting for buffer data */
+#define MON_WAIT_SPACE 0x0002 /* Waiting for buffer space */
+
+/************************************************************************
+ * DPA flag definitions.
+ ************************************************************************/
+
+#define DPA_WAIT_DATA 0x0001 /* Waiting for buffer data */
+#define DPA_WAIT_SPACE 0x0002 /* Waiting for buffer space */
+
+
+/************************************************************************
+ * Definitions taken from Realport Dump.
+ ************************************************************************/
+
+#define RPDUMP_MAGIC "Digi-RealPort-1.0"
+
+#define RPDUMP_MESSAGE 0xE2 /* Descriptive message */
+#define RPDUMP_RESET 0xE7 /* Connection reset */
+#define RPDUMP_CLIENT 0xE8 /* Client data */
+#define RPDUMP_SERVER 0xE9 /* Server data */
+
+
+/************************************************************************
+ * Node request/response definitions.
+ ************************************************************************/
+
+#define NR_ECHO 0x0001 /* Server echo packet */
+#define NR_IDENT 0x0002 /* Server Product ID */
+#define NR_CAPABILITY 0x0004 /* Server Capabilties */
+#define NR_VPD 0x0008 /* Server VPD, if any */
+#define NR_PASSWORD 0x0010 /* Server Password */
+
+/************************************************************************
+ * Registration status of the node's Linux struct tty_driver structures.
+ ************************************************************************/
+#define SERIAL_TTDRV_REG 0x0001 /* nd_serial_ttdriver registered */
+#define CALLOUT_TTDRV_REG 0x0002 /* nd_callout_ttdriver registered */
+#define XPRINT_TTDRV_REG 0x0004 /* nd_xprint_ttdriver registered */
+
+
+/************************************************************************
+ * Node structure. There exists one of these for each associated
+ * realport server.
+ ************************************************************************/
+
+struct nd_struct {
+ struct list_head list;
+ long nd_major; /* Node's major number */
+ long nd_ID; /* Node's ID code */
+
+ char nd_serial_name[50]; /* "tty_dgrp_<id>_" + null */
+ char nd_callout_name[50]; /* "cu_dgrp_<id>_" + null */
+ char nd_xprint_name[50]; /* "pr_dgrp_<id>_" + null */
+
+ char password[16]; /* Password for server, if needed */
+ int nd_tty_ref_cnt; /* Linux tty reference count */
+
+ struct proc_dir_entry *nd_net_de; /* Dir entry for /proc/dgrp/net */
+ struct proc_dir_entry *nd_mon_de; /* Dir entry for /proc/dgrp/mon */
+ struct proc_dir_entry *nd_ports_de; /* Dir entry for /proc/dgrp/ports*/
+ struct proc_dir_entry *nd_dpa_de; /* Dir entry for /proc/dgrp/dpa */
+
+ spinlock_t nd_lock; /* General node lock */
+
+ struct semaphore nd_net_semaphore; /* Net read/write lock */
+ struct semaphore nd_mon_semaphore; /* Monitor buffer lock */
+ spinlock_t nd_dpa_lock; /* DPA buffer lock */
+
+ enum dgrp_nd_state_t nd_state; /* NS_* network state */
+ int nd_chan_count; /* # active channels */
+ int nd_flag; /* Node flags */
+ int nd_send; /* Responses to send */
+ int nd_expect; /* Responses we expect */
+
+ u8 *nd_iobuf; /* Network R/W Buffer */
+ wait_queue_head_t nd_tx_waitq; /* Network select wait queue */
+
+ u8 *nd_inputbuf; /* Input Buffer */
+ u8 *nd_inputflagbuf; /* Input Flags Buffer */
+
+ int nd_tx_deposit; /* Accumulated transmit deposits */
+ int nd_tx_charge; /* Accumulated transmit charges */
+ int nd_tx_credit; /* Current TX credit */
+ int nd_tx_ready; /* Ready to transmit */
+ int nd_tx_work; /* TX work waiting */
+ ulong nd_tx_time; /* Last transmit time */
+ ulong nd_poll_time; /* Next scheduled poll time */
+
+ int nd_delay; /* Current TX delay */
+ int nd_rate; /* Current TX rate */
+ struct link_struct nd_link; /* Link speed params. */
+
+ int nd_seq_in; /* TX seq in ptr */
+ int nd_seq_out; /* TX seq out ptr */
+ int nd_unack; /* Unacknowledged byte count */
+ int nd_remain; /* Remaining receive bytes */
+ int nd_tx_module; /* Current TX module # */
+ int nd_rx_module; /* Current RX module # */
+ char *nd_error; /* Protocol error message */
+
+ int nd_write_count; /* drp_write() call count */
+ int nd_read_count; /* drp_read() count */
+ int nd_send_count; /* TCP message sent */
+ int nd_tx_byte; /* Transmit byte count */
+ int nd_rx_byte; /* Receive byte count */
+
+ ulong nd_mon_lbolt; /* Monitor start time */
+ int nd_mon_flag; /* Monitor flags */
+ int nd_mon_in; /* Monitor in pointer */
+ int nd_mon_out; /* Monitor out pointer */
+ wait_queue_head_t nd_mon_wqueue; /* Monitor wait queue (on flags) */
+ u8 *nd_mon_buf; /* Monitor buffer */
+
+ ulong nd_dpa_lbolt; /* DPA start time */
+ int nd_dpa_flag; /* DPA flags */
+ int nd_dpa_in; /* DPA in pointer */
+ int nd_dpa_out; /* DPA out pointer */
+ wait_queue_head_t nd_dpa_wqueue; /* DPA wait queue (on flags) */
+ u8 *nd_dpa_buf; /* DPA buffer */
+
+ uint nd_dpa_debug;
+ uint nd_dpa_port;
+
+ wait_queue_head_t nd_seq_wque[SEQ_MAX]; /* TX thread wait queues */
+ u8 nd_seq_wait[SEQ_MAX]; /* Transmit thread wait count */
+
+ ushort nd_seq_size[SEQ_MAX]; /* Transmit seq packet size */
+ ulong nd_seq_time[SEQ_MAX]; /* Transmit seq packet time */
+
+ ushort nd_hw_ver; /* HW version returned from PS */
+ ushort nd_sw_ver; /* SW version returned from PS */
+ uint nd_hw_id; /* HW ID returned from PS */
+ u8 nd_ps_desc[MAX_DESC_LEN]; /* Description from PS */
+ uint nd_vpd_len; /* VPD len, if any */
+ u8 nd_vpd[VPDSIZE]; /* VPD, if any */
+
+ ulong nd_ttdriver_flags; /* Registration status */
+ struct tty_driver *nd_serial_ttdriver; /* Linux TTYDRIVER structure */
+ struct tty_driver *nd_callout_ttdriver; /* Linux TTYDRIVER structure */
+ struct tty_driver *nd_xprint_ttdriver; /* Linux TTYDRIVER structure */
+
+ u8 *nd_writebuf; /* Used to cache data read
+ * from user
+ */
+ struct ch_struct nd_chan[CHAN_MAX]; /* Channel array */
+ struct device *nd_class_dev; /* Hang our sysfs stuff off of here */
+};
+
+#endif /* __DRP_H */
diff --git a/drivers/staging/et131x/Kconfig b/drivers/staging/et131x/Kconfig
new file mode 100644
index 00000000000..8190f2aaf53
--- /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 00000000000..027ff9453fe
--- /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 00000000000..3befc45fab8
--- /dev/null
+++ b/drivers/staging/et131x/README
@@ -0,0 +1,20 @@
+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 <hch@infradead.org>
+
+Note, the powermanagement options were removed from the vendor provided
+driver as they did not build properly at the time.
+
+TODO:
+ - Look at reducing the number of spinlocks
+ - Simplify code in nic_rx_pkts(), when determining multicast_pkts_rcvd
+ - Implement NAPI support
+ - In et131x_tx(), don't return NETDEV_TX_BUSY, just drop the packet with kfree_skb().
+ - Reduce the number of split lines by careful consideration of variable names etc.
+
+Please send patches to:
+ Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+ Mark Einon <mark.einon@gmail.com>
+
diff --git a/drivers/staging/et131x/et131x.c b/drivers/staging/et131x/et131x.c
new file mode 100644
index 00000000000..08356b6955a
--- /dev/null
+++ b/drivers/staging/et131x/et131x.c
@@ -0,0 +1,4751 @@
+/* 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 <mark.einon@gmail.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 pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/ioport.h>
+#include <linux/crc32.h>
+#include <linux/random.h>
+#include <linux/phy.h>
+
+#include "et131x.h"
+
+MODULE_AUTHOR("Victor Soriano <vjsoriano@agere.com>");
+MODULE_AUTHOR("Mark Einon <mark.einon@gmail.com>");
+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_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 FBR_CHUNKS 32
+#define MAX_DESC_PER_RING_RX 1024
+
+/* number of RFDs - default and min */
+#define RFD_LOW_WATER_MARK 40
+#define NIC_DEFAULT_NUM_RFD 1024
+#define NUM_FBRS 2
+
+#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];
+ 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];
+ u32 local_full;
+ u32 num_entries;
+ dma_addr_t buffsize;
+};
+
+/* struct rx_ring is the sructure representing the adaptor's local
+ * reference(s) to the rings
+ */
+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;
+};
+
+/* 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
+ */
+
+#define TXDESC_FLAG_LASTPKT 0x0001
+#define TXDESC_FLAG_FIRSTPKT 0x0002
+#define TXDESC_FLAG_INTPROC 0x0004
+
+/* 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 tcb_send_qlock;
+ spinlock_t tcb_ready_qlock;
+ spinlock_t send_hw_lock;
+
+ spinlock_t rcv_lock;
+ spinlock_t fbr_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, &reg))
+ 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 < 0)
+ 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 < 0)
+ 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 conditions
+ * 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 = ET_RXDMA_CSR_FBR1_ENABLE;
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+
+ if (rx_ring->fbr[1]->buffsize == 4096)
+ csr |= ET_RXDMA_CSR_FBR1_SIZE_LO;
+ else if (rx_ring->fbr[1]->buffsize == 8192)
+ csr |= ET_RXDMA_CSR_FBR1_SIZE_HI;
+ else if (rx_ring->fbr[1]->buffsize == 16384)
+ csr |= ET_RXDMA_CSR_FBR1_SIZE_LO | ET_RXDMA_CSR_FBR1_SIZE_HI;
+
+ csr |= ET_RXDMA_CSR_FBR0_ENABLE;
+ if (rx_ring->fbr[0]->buffsize == 256)
+ csr |= ET_RXDMA_CSR_FBR0_SIZE_LO;
+ else if (rx_ring->fbr[0]->buffsize == 512)
+ csr |= ET_RXDMA_CSR_FBR0_SIZE_HI;
+ else if (rx_ring->fbr[0]->buffsize == 1024)
+ csr |= ET_RXDMA_CSR_FBR0_SIZE_LO | ET_RXDMA_CSR_FBR0_SIZE_HI;
+ writel(csr, &adapter->regs->rxdma.csr);
+
+ csr = readl(&adapter->regs->rxdma.csr);
+ if (csr & ET_RXDMA_CSR_HALT_STATUS) {
+ udelay(5);
+ csr = readl(&adapter->regs->rxdma.csr);
+ if (csr & ET_RXDMA_CSR_HALT_STATUS) {
+ 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(ET_RXDMA_CSR_HALT | ET_RXDMA_CSR_FBR1_ENABLE,
+ &adapter->regs->rxdma.csr);
+ csr = readl(&adapter->regs->rxdma.csr);
+ if (!(csr & ET_RXDMA_CSR_HALT_STATUS)) {
+ udelay(5);
+ csr = readl(&adapter->regs->rxdma.csr);
+ if (!(csr & ET_RXDMA_CSR_HALT_STATUS))
+ 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(ET_MAC_CFG1_SOFT_RESET | ET_MAC_CFG1_SIM_RESET |
+ ET_MAC_CFG1_RESET_RXMC | ET_MAC_CFG1_RESET_TXMC |
+ ET_MAC_CFG1_RESET_RXFUNC | ET_MAC_CFG1_RESET_TXFUNC,
+ &macregs->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, &macregs->ipg);
+
+ /* Next lets configure the MAC Half Duplex register */
+ /* BEB trunc 0xA, Ex Defer, Rexmit 0xF Coll 0x37 */
+ writel(0x00A1F037, &macregs->hfdp);
+
+ /* Next lets configure the MAC Interface Control register */
+ writel(0, &macregs->if_ctrl);
+
+ /* Let's move on to setting up the mii management configuration */
+ writel(ET_MAC_MIIMGMT_CLK_RST, &macregs->mii_mgmt_cfg);
+
+ /* 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, &macregs->station_addr_1);
+ writel(station2, &macregs->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, &macregs->max_fm_len);
+
+ /* clear out MAC config reset */
+ writel(0, &macregs->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 &= ~ET_MAC_CFG2_IFMODE_MASK;
+ if (phydev->speed == SPEED_1000) {
+ cfg2 |= ET_MAC_CFG2_IFMODE_1000;
+ /* Phy mode bit */
+ ifctrl &= ~ET_MAC_IFCTRL_PHYMODE;
+ } else {
+ cfg2 |= ET_MAC_CFG2_IFMODE_100;
+ ifctrl |= ET_MAC_IFCTRL_PHYMODE;
+ }
+
+ /* We need to enable Rx/Tx */
+ cfg1 |= ET_MAC_CFG1_RX_ENABLE | ET_MAC_CFG1_TX_ENABLE |
+ ET_MAC_CFG1_TX_FLOW;
+ /* Initialize loop back to off */
+ cfg1 &= ~(ET_MAC_CFG1_LOOPBACK | ET_MAC_CFG1_RX_FLOW);
+ if (adapter->flowcontrol == FLOW_RXONLY ||
+ adapter->flowcontrol == FLOW_BOTH)
+ cfg1 |= ET_MAC_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 |= 0x7 << ET_MAC_CFG2_PREAMBLE_SHIFT;
+ cfg2 |= ET_MAC_CFG2_IFMODE_LEN_CHECK;
+ cfg2 |= ET_MAC_CFG2_IFMODE_PAD_CRC;
+ cfg2 |= ET_MAC_CFG2_IFMODE_CRC_ENABLE;
+ cfg2 &= ~ET_MAC_CFG2_IFMODE_HUGE_FRAME;
+ cfg2 &= ~ET_MAC_CFG2_IFMODE_FULL_DPLX;
+
+ /* Turn on duplex if needed */
+ if (phydev->duplex == DUPLEX_FULL)
+ cfg2 |= ET_MAC_CFG2_IFMODE_FULL_DPLX;
+
+ ifctrl &= ~ET_MAC_IFCTRL_GHDMODE;
+ if (phydev->duplex == DUPLEX_HALF)
+ ifctrl |= ET_MAC_IFCTRL_GHDMODE;
+
+ writel(ifctrl, &mac->if_ctrl);
+ writel(cfg2, &mac->cfg2);
+
+ do {
+ udelay(10);
+ delay++;
+ cfg1 = readl(&mac->cfg1);
+ } while ((cfg1 & ET_MAC_CFG1_WAIT) != ET_MAC_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 |= ET_TX_CTRL_TXMAC_ENABLE | ET_TX_CTRL_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 = 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_RX_UNI_PF_ADDR2_1_SHIFT) |
+ (adapter->addr[1] << ET_RX_UNI_PF_ADDR2_2_SHIFT) |
+ (adapter->addr[0] << ET_RX_UNI_PF_ADDR1_1_SHIFT) |
+ adapter->addr[1];
+
+ uni_pf2 = (adapter->addr[2] << ET_RX_UNI_PF_ADDR2_3_SHIFT) |
+ (adapter->addr[3] << ET_RX_UNI_PF_ADDR2_4_SHIFT) |
+ (adapter->addr[4] << ET_RX_UNI_PF_ADDR2_5_SHIFT) |
+ adapter->addr[5];
+
+ uni_pf1 = (adapter->addr[2] << ET_RX_UNI_PF_ADDR1_3_SHIFT) |
+ (adapter->addr[3] << ET_RX_UNI_PF_ADDR1_4_SHIFT) |
+ (adapter->addr[4] << ET_RX_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_RX_WOL_LO_SA3_SHIFT) |
+ (adapter->addr[3] << ET_RX_WOL_LO_SA4_SHIFT) |
+ (adapter->addr[4] << ET_RX_WOL_LO_SA5_SHIFT) |
+ adapter->addr[5];
+ writel(sa_lo, &rxmac->sa_lo);
+
+ sa_hi = (u32) (adapter->addr[0] << ET_RX_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 |= ET_RX_PFCTRL_UNICST_FILTER_ENABLE;
+ } 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 |= ET_RX_PFCTRL_MLTCST_FILTER_ENABLE;
+ et1310_setup_device_for_multicast(adapter);
+ }
+
+ /* Runt packet filtering. Didn't work in version A silicon. */
+ pf_ctrl |= (NIC_MIN_PACKET_SIZE + 4) << ET_RX_PFCTRL_MIN_PKT_SZ_SHIFT;
+ pf_ctrl |= ET_RX_PFCTRL_FRAG_FILTER_ENABLE;
+
+ 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(ET_RX_CTRL_RXMAC_ENABLE | ET_RX_CTRL_WOL_DISABLE, &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
+ */
+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(ET_MAC_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 & ET_MAC_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;
+ goto out;
+ }
+
+ /* 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) & ET_MAC_MIIMGMT_STAT_PHYCRTL_MASK;
+
+out:
+ /* 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 reg 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
+ */
+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(ET_MAC_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 & ET_MAC_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;
+}
+
+static void et1310_phy_read_mii_bit(struct et131x_adapter *adapter,
+ u16 regnum,
+ u16 bitnum,
+ u8 *value)
+{
+ u16 reg;
+ u16 mask = 1 << bitnum;
+
+ /* Read the requested register */
+ et131x_mii_read(adapter, regnum, &reg);
+
+ *value = (reg & mask) >> bitnum;
+}
+
+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_read_mii_bit(adapter, 5, 10, &remote_pause);
+ et1310_phy_read_mii_bit(adapter, 5, 11, &remote_async_pause);
+
+ if (remote_pause && remote_async_pause) {
+ adapter->flowcontrol = adapter->wanted_flow;
+ } else if (remote_pause && !remote_async_pause) {
+ if (adapter->wanted_flow == FLOW_BOTH)
+ adapter->flowcontrol = FLOW_BOTH;
+ else
+ adapter->flowcontrol = FLOW_NONE;
+ } else if (!remote_pause && !remote_async_pause) {
+ adapter->flowcontrol = FLOW_NONE;
+ } else {
+ if (adapter->wanted_flow == FLOW_BOTH)
+ adapter->flowcontrol = FLOW_RXONLY;
+ else
+ adapter->flowcontrol = FLOW_NONE;
+ }
+ }
+}
+
+/* et1310_update_macstat_host_counters - Update local copy of the statistics */
+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
+ *
+ * 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_switch - 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_switch(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 */
+static void et131x_xcvr_init(struct et131x_adapter *adapter)
+{
+ u16 lcr2;
+
+ /* 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
+ *
+ * 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, &regs->rxq_start_addr);
+ writel(INTERNAL_MEM_SIZE - 1, &regs->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, &regs->rxq_end_addr);
+ writel(PARM_RX_MEM_END_DEF + 1, &regs->txq_start_addr);
+ } else if (adapter->registry_jumbo_packet < 8192) {
+ /* For jumbo packets > 2k but < 8k, split 50-50. */
+ writel(INTERNAL_MEM_RX_OFFSET, &regs->rxq_end_addr);
+ writel(INTERNAL_MEM_RX_OFFSET + 1, &regs->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, &regs->rxq_end_addr);
+ writel(0x01b4, &regs->txq_start_addr);
+ }
+
+ /* Initialize the loopback register. Disable all loopbacks. */
+ writel(0, &regs->loopback);
+
+ /* MSI Register */
+ writel(0, &regs->msi_config);
+
+ /* By default, disable the watchdog timer. It will be enabled when
+ * a packet is queued.
+ */
+ writel(0, &regs->watchdog_timer);
+}
+
+/* et131x_config_rx_dma_regs - Start of Rx_DMA init sequence */
+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;
+ u8 id;
+
+ /* Halt RXDMA to perform the reconfigure. */
+ et131x_rx_dma_disable(adapter);
+
+ /* Load the completion writeback physical address */
+ writel(upper_32_bits(rx_local->rx_status_bus), &rx_dma->dma_wb_base_hi);
+ writel(lower_32_bits(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(upper_32_bits(rx_local->ps_ring_physaddr), &rx_dma->psr_base_hi);
+ writel(lower_32_bits(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) & ET_RXDMA_PSR_NUM_DES_MASK;
+ 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;
+
+ for (id = 0; id < NUM_FBRS; id++) {
+ u32 __iomem *num_des;
+ u32 __iomem *full_offset;
+ u32 __iomem *min_des;
+ u32 __iomem *base_hi;
+ u32 __iomem *base_lo;
+ struct fbr_lookup *fbr = rx_local->fbr[id];
+
+ if (id == 0) {
+ num_des = &rx_dma->fbr0_num_des;
+ full_offset = &rx_dma->fbr0_full_offset;
+ min_des = &rx_dma->fbr0_min_des;
+ base_hi = &rx_dma->fbr0_base_hi;
+ base_lo = &rx_dma->fbr0_base_lo;
+ } else {
+ num_des = &rx_dma->fbr1_num_des;
+ full_offset = &rx_dma->fbr1_full_offset;
+ min_des = &rx_dma->fbr1_min_des;
+ base_hi = &rx_dma->fbr1_base_hi;
+ base_lo = &rx_dma->fbr1_base_lo;
+ }
+
+ /* Now's the best time to initialize FBR contents */
+ fbr_entry = fbr->ring_virtaddr;
+ for (entry = 0; entry < fbr->num_entries; entry++) {
+ fbr_entry->addr_hi = fbr->bus_high[entry];
+ fbr_entry->addr_lo = fbr->bus_low[entry];
+ fbr_entry->word2 = entry;
+ fbr_entry++;
+ }
+
+ /* Set the address and parameters of Free buffer ring 1 and 0
+ * into the 1310's registers
+ */
+ writel(upper_32_bits(fbr->ring_physaddr), base_hi);
+ writel(lower_32_bits(fbr->ring_physaddr), base_lo);
+ writel(fbr->num_entries - 1, num_des);
+ writel(ET_DMA10_WRAP, full_offset);
+
+ /* This variable tracks the free buffer ring 1 full position,
+ * so it has to match the above.
+ */
+ fbr->local_full = ET_DMA10_WRAP;
+ writel(((fbr->num_entries * LO_MARK_PERCENT_FOR_RX) / 100) - 1,
+ min_des);
+ }
+
+ /* 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.
+ *
+ * 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;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* Load the hardware with the start of the transmit descriptor ring. */
+ writel(upper_32_bits(tx_ring->tx_desc_ring_pa), &txdma->pr_base_hi);
+ writel(lower_32_bits(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(upper_32_bits(tx_ring->tx_status_pa), &txdma->dma_wb_base_hi);
+ writel(lower_32_bits(tx_ring->tx_status_pa), &txdma->dma_wb_base_lo);
+
+ *tx_ring->tx_status = 0;
+
+ writel(0, &txdma->service_request);
+ tx_ring->send_idx = 0;
+}
+
+/* et131x_adapter_setup - Set the adapter up as per cassini+ documentation */
+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_switch(adapter, 0);
+ et131x_xcvr_init(adapter);
+}
+
+/* et131x_soft_reset - Issue soft reset to the hardware, complete for ET1310 */
+static void et131x_soft_reset(struct et131x_adapter *adapter)
+{
+ u32 reg;
+
+ /* Disable MAC Core */
+ reg = ET_MAC_CFG1_SOFT_RESET | ET_MAC_CFG1_SIM_RESET |
+ ET_MAC_CFG1_RESET_RXMC | ET_MAC_CFG1_RESET_TXMC |
+ ET_MAC_CFG1_RESET_RXFUNC | ET_MAC_CFG1_RESET_TXFUNC;
+ writel(reg, &adapter->regs->mac.cfg1);
+
+ reg = ET_RESET_ALL;
+ writel(reg, &adapter->regs->global.sw_reset);
+
+ reg = ET_MAC_CFG1_RESET_RXMC | ET_MAC_CFG1_RESET_TXMC |
+ ET_MAC_CFG1_RESET_RXFUNC | ET_MAC_CFG1_RESET_TXFUNC;
+ writel(reg, &adapter->regs->mac.cfg1);
+ writel(0, &adapter->regs->mac.cfg1);
+}
+
+/* et131x_enable_interrupts - enable interrupt
+ *
+ * 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
+ *
+ * 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 */
+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 */
+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 */
+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 */
+static void et131x_init_send(struct et131x_adapter *adapter)
+{
+ u32 ct;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+ struct tcb *tcb = 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
+ *
+ * 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
+ */
+
+ /* 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 */
+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
+ */
+
+ /* 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_rx_dma_memory_alloc
+ *
+ * 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)
+{
+ u8 id;
+ u32 i, j;
+ u32 bufsize;
+ u32 pktstat_ringsize;
+ u32 fbr_chunksize;
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+ struct fbr_lookup *fbr;
+
+ /* Alloc memory for the lookup table */
+ rx_ring->fbr[0] = kmalloc(sizeof(struct fbr_lookup), GFP_KERNEL);
+ if (rx_ring->fbr[0] == NULL)
+ return -ENOMEM;
+ rx_ring->fbr[1] = kmalloc(sizeof(struct fbr_lookup), GFP_KERNEL);
+ if (rx_ring->fbr[1] == NULL)
+ return -ENOMEM;
+
+ /* 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) {
+ rx_ring->fbr[0]->buffsize = 256;
+ rx_ring->fbr[0]->num_entries = 512;
+ rx_ring->fbr[1]->buffsize = 2048;
+ rx_ring->fbr[1]->num_entries = 512;
+ } else if (adapter->registry_jumbo_packet < 4096) {
+ rx_ring->fbr[0]->buffsize = 512;
+ rx_ring->fbr[0]->num_entries = 1024;
+ rx_ring->fbr[1]->buffsize = 4096;
+ rx_ring->fbr[1]->num_entries = 512;
+ } else {
+ rx_ring->fbr[0]->buffsize = 1024;
+ rx_ring->fbr[0]->num_entries = 768;
+ rx_ring->fbr[1]->buffsize = 16384;
+ rx_ring->fbr[1]->num_entries = 128;
+ }
+
+ rx_ring->psr_num_entries = rx_ring->fbr[0]->num_entries +
+ rx_ring->fbr[1]->num_entries;
+
+ for (id = 0; id < NUM_FBRS; id++) {
+ fbr = rx_ring->fbr[id];
+ /* Allocate an area of memory for Free Buffer Ring */
+ bufsize = sizeof(struct fbr_desc) * fbr->num_entries;
+ fbr->ring_virtaddr = dma_alloc_coherent(&adapter->pdev->dev,
+ bufsize,
+ &fbr->ring_physaddr,
+ GFP_KERNEL);
+ if (!fbr->ring_virtaddr) {
+ dev_err(&adapter->pdev->dev,
+ "Cannot alloc memory for Free Buffer Ring %d\n", id);
+ return -ENOMEM;
+ }
+ }
+
+ for (id = 0; id < NUM_FBRS; id++) {
+ fbr = rx_ring->fbr[id];
+ fbr_chunksize = (FBR_CHUNKS * fbr->buffsize);
+
+ for (i = 0; i < fbr->num_entries / FBR_CHUNKS; i++) {
+ dma_addr_t fbr_tmp_physaddr;
+
+ fbr->mem_virtaddrs[i] = dma_alloc_coherent(
+ &adapter->pdev->dev, fbr_chunksize,
+ &fbr->mem_physaddrs[i],
+ GFP_KERNEL);
+
+ if (!fbr->mem_virtaddrs[i]) {
+ dev_err(&adapter->pdev->dev,
+ "Could not alloc memory\n");
+ return -ENOMEM;
+ }
+
+ /* See NOTE in "Save Physical Address" comment above */
+ fbr_tmp_physaddr = fbr->mem_physaddrs[i];
+
+ for (j = 0; j < FBR_CHUNKS; j++) {
+ u32 index = (i * FBR_CHUNKS) + j;
+
+ /* Save the Virtual address of this index for
+ * quick access later
+ */
+ fbr->virt[index] = (u8 *)fbr->mem_virtaddrs[i] +
+ (j * fbr->buffsize);
+
+ /* now store the physical address in the
+ * descriptor so the device can access it
+ */
+ fbr->bus_high[index] =
+ upper_32_bits(fbr_tmp_physaddr);
+ fbr->bus_low[index] =
+ lower_32_bits(fbr_tmp_physaddr);
+
+ fbr_tmp_physaddr += fbr->buffsize;
+ }
+ }
+ }
+
+ /* Allocate an area of memory for FIFO of Packet Status ring entries */
+ pktstat_ringsize =
+ sizeof(struct pkt_stat_desc) * 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;
+ }
+
+ /* 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;
+
+ /* 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 */
+static void et131x_rx_dma_memory_free(struct et131x_adapter *adapter)
+{
+ u8 id;
+ u32 index;
+ u32 bufsize;
+ u32 pktstat_ringsize;
+ struct rfd *rfd;
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+ struct fbr_lookup *fbr;
+
+ /* 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 = list_entry(rx_ring->recv_list.next,
+ struct rfd, list_node);
+
+ list_del(&rfd->list_node);
+ rfd->skb = NULL;
+ kfree(rfd);
+ }
+
+ /* Free Free Buffer Rings */
+ for (id = 0; id < NUM_FBRS; id++) {
+ fbr = rx_ring->fbr[id];
+
+ if (!fbr || !fbr->ring_virtaddr)
+ continue;
+
+ /* First the packet memory */
+ for (index = 0;
+ index < fbr->num_entries / FBR_CHUNKS;
+ index++) {
+ if (fbr->mem_virtaddrs[index]) {
+ bufsize = fbr->buffsize * FBR_CHUNKS;
+
+ dma_free_coherent(&adapter->pdev->dev,
+ bufsize,
+ fbr->mem_virtaddrs[index],
+ fbr->mem_physaddrs[index]);
+
+ fbr->mem_virtaddrs[index] = NULL;
+ }
+ }
+
+ bufsize = sizeof(struct fbr_desc) * fbr->num_entries;
+
+ dma_free_coherent(&adapter->pdev->dev,
+ bufsize,
+ fbr->ring_virtaddr,
+ fbr->ring_physaddr);
+
+ fbr->ring_virtaddr = NULL;
+ }
+
+ /* Free Packet Status Ring */
+ if (rx_ring->ps_ring_virtaddr) {
+ pktstat_ringsize = sizeof(struct pkt_stat_desc) *
+ 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;
+ }
+
+ /* Free the FBR Lookup Table */
+ kfree(rx_ring->fbr[0]);
+ kfree(rx_ring->fbr[1]);
+
+ /* Reset Counters */
+ rx_ring->num_ready_recv = 0;
+}
+
+/* et131x_init_recv - Initialize receive data structures */
+static int et131x_init_recv(struct et131x_adapter *adapter)
+{
+ struct rfd *rfd;
+ u32 rfdct;
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+
+ /* Setup each RFD */
+ for (rfdct = 0; rfdct < rx_ring->num_rfd; rfdct++) {
+ rfd = kzalloc(sizeof(struct rfd), GFP_ATOMIC | GFP_DMA);
+ if (!rfd)
+ return -ENOMEM;
+
+ rfd->skb = NULL;
+
+ /* Add this RFD to the recv_list */
+ list_add_tail(&rfd->list_node, &rx_ring->recv_list);
+
+ /* Increment the available RFD's */
+ rx_ring->num_ready_recv++;
+ }
+
+ return 0;
+}
+
+/* et131x_set_rx_dma_timer - Set the heartbeat timer according to line rate */
+static void et131x_set_rx_dma_timer(struct et131x_adapter *adapter)
+{
+ struct phy_device *phydev = adapter->phydev;
+
+ /* 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;
+ struct fbr_lookup *fbr = rx_local->fbr[ring_index];
+
+ /* We don't use any of the OOB data besides status. Otherwise, we
+ * need to clean up OOB data
+ */
+ if (buff_index < fbr->num_entries) {
+ u32 free_buff_ring;
+ u32 __iomem *offset;
+ struct fbr_desc *next;
+
+ spin_lock_irqsave(&adapter->fbr_lock, flags);
+
+ if (ring_index == 0)
+ offset = &rx_dma->fbr0_full_offset;
+ else
+ offset = &rx_dma->fbr1_full_offset;
+
+ next = (struct fbr_desc *)(fbr->ring_virtaddr) +
+ INDEX10(fbr->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 = fbr->bus_high[buff_index];
+ next->addr_lo = fbr->bus_low[buff_index];
+ next->word2 = buff_index;
+
+ free_buff_ring = bump_free_buff_ring(&fbr->local_full,
+ fbr->num_entries - 1);
+ writel(free_buff_ring, offset);
+
+ 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
+ *
+ * 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;
+ struct sk_buff *skb;
+ struct fbr_lookup *fbr;
+
+ /* 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))
+ return NULL; /* Looks like this ring is not updated yet */
+
+ /* 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;
+ fbr = rx_local->fbr[ring_index];
+ 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);
+
+ if (ring_index > 1 || buff_index > fbr->num_entries - 1) {
+ /* 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);
+
+ element = rx_local->recv_list.next;
+ rfd = list_entry(element, struct rfd, list_node);
+
+ if (!rfd) {
+ 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 == 0) {
+ rfd->len = 0;
+ goto out;
+ }
+
+ /* 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)) {
+ buf = fbr->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) {
+ rfd->len = 0;
+ goto out;
+ }
+
+ 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;
+
+ memcpy(skb_put(skb, rfd->len), fbr->virt[buff_index], rfd->len);
+
+ skb->protocol = eth_type_trans(skb, adapter->netdev);
+ skb->ip_summed = CHECKSUM_NONE;
+ netif_rx_ni(skb);
+
+out:
+ nic_return_rfd(adapter, rfd);
+ return rfd;
+}
+
+/* et131x_handle_recv_interrupt - Interrupt handler for receive processing
+ *
+ * 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;
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+
+ /* Process up to available RFD's */
+ while (count < NUM_PACKETS_HANDLED) {
+ if (list_empty(&rx_ring->recv_list)) {
+ WARN_ON(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 (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) {
+ 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. */
+ rx_ring->unfinished_receives = false;
+}
+
+/* et131x_tx_dma_memory_alloc
+ *
+ * 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) */
+ tx_ring->tcb_ring = kcalloc(NUM_TCB, sizeof(struct tcb),
+ GFP_ATOMIC | GFP_DMA);
+ if (!tx_ring->tcb_ring)
+ return -ENOMEM;
+
+ desc_size = (sizeof(struct tx_desc) * NUM_DESC_PER_RING_TX);
+ tx_ring->tx_desc_ring = dma_alloc_coherent(&adapter->pdev->dev,
+ desc_size,
+ &tx_ring->tx_desc_ring_pa,
+ GFP_KERNEL);
+ if (!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 (!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 */
+static void et131x_tx_dma_memory_free(struct et131x_adapter *adapter)
+{
+ int desc_size = 0;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ if (tx_ring->tx_desc_ring) {
+ /* Free memory relating to Tx rings here */
+ desc_size = (sizeof(struct tx_desc) * NUM_DESC_PER_RING_TX);
+ dma_free_coherent(&adapter->pdev->dev,
+ desc_size,
+ tx_ring->tx_desc_ring,
+ tx_ring->tx_desc_ring_pa);
+ tx_ring->tx_desc_ring = NULL;
+ }
+
+ /* Free memory for the Tx status block */
+ if (tx_ring->tx_status) {
+ dma_free_coherent(&adapter->pdev->dev,
+ sizeof(u32),
+ tx_ring->tx_status,
+ tx_ring->tx_status_pa);
+
+ tx_ring->tx_status = NULL;
+ }
+ /* Free the memory for the tcb structures */
+ kfree(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
+ */
+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;
+ dma_addr_t dma_addr;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* 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.
+ */
+
+ /* nr_frags should be no more than 18. */
+ BUILD_BUG_ON(MAX_SKB_FRAGS + 1 > 23);
+
+ 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_headlen(skb) <= 1514) {
+ /* Low 16bits are length, high is vlan and
+ * unused currently so zero
+ */
+ desc[frag].len_vlan = skb_headlen(skb);
+ dma_addr = dma_map_single(&adapter->pdev->dev,
+ skb->data,
+ skb_headlen(skb),
+ DMA_TO_DEVICE);
+ desc[frag].addr_lo = lower_32_bits(dma_addr);
+ desc[frag].addr_hi = upper_32_bits(dma_addr);
+ frag++;
+ } else {
+ desc[frag].len_vlan = skb_headlen(skb) / 2;
+ dma_addr = dma_map_single(&adapter->pdev->dev,
+ skb->data,
+ (skb_headlen(skb) / 2),
+ DMA_TO_DEVICE);
+ desc[frag].addr_lo = lower_32_bits(dma_addr);
+ desc[frag].addr_hi = upper_32_bits(dma_addr);
+ frag++;
+
+ desc[frag].len_vlan = skb_headlen(skb) / 2;
+ dma_addr = dma_map_single(&adapter->pdev->dev,
+ skb->data +
+ (skb_headlen(skb) / 2),
+ (skb_headlen(skb) / 2),
+ DMA_TO_DEVICE);
+ desc[frag].addr_lo = lower_32_bits(dma_addr);
+ desc[frag].addr_hi = upper_32_bits(dma_addr);
+ frag++;
+ }
+ } else {
+ desc[frag].len_vlan = frags[i - 1].size;
+ dma_addr = skb_frag_dma_map(&adapter->pdev->dev,
+ &frags[i - 1],
+ 0,
+ frags[i - 1].size,
+ DMA_TO_DEVICE);
+ desc[frag].addr_lo = lower_32_bits(dma_addr);
+ desc[frag].addr_hi = upper_32_bits(dma_addr);
+ frag++;
+ }
+ }
+
+ if (phydev && phydev->speed == SPEED_1000) {
+ if (++tx_ring->since_irq == PARM_TX_NUM_BUFS_DEF) {
+ /* Last element & Interrupt flag */
+ desc[frag - 1].flags =
+ TXDESC_FLAG_INTPROC | TXDESC_FLAG_LASTPKT;
+ tx_ring->since_irq = 0;
+ } else { /* Last element */
+ desc[frag - 1].flags = TXDESC_FLAG_LASTPKT;
+ }
+ } else
+ desc[frag - 1].flags =
+ TXDESC_FLAG_INTPROC | TXDESC_FLAG_LASTPKT;
+
+ desc[0].flags |= TXDESC_FLAG_FIRSTPKT;
+
+ tcb->index_start = tx_ring->send_idx;
+ tcb->stale = 0;
+
+ spin_lock_irqsave(&adapter->send_hw_lock, flags);
+
+ thiscopy = NUM_DESC_PER_RING_TX - INDEX10(tx_ring->send_idx);
+
+ if (thiscopy >= frag) {
+ remainder = 0;
+ thiscopy = frag;
+ } else {
+ remainder = frag - thiscopy;
+ }
+
+ memcpy(tx_ring->tx_desc_ring + INDEX10(tx_ring->send_idx),
+ desc,
+ sizeof(struct tx_desc) * thiscopy);
+
+ add_10bit(&tx_ring->send_idx, thiscopy);
+
+ if (INDEX10(tx_ring->send_idx) == 0 ||
+ INDEX10(tx_ring->send_idx) == NUM_DESC_PER_RING_TX) {
+ tx_ring->send_idx &= ~ET_DMA10_MASK;
+ tx_ring->send_idx ^= ET_DMA10_WRAP;
+ }
+
+ if (remainder) {
+ memcpy(tx_ring->tx_desc_ring,
+ desc + thiscopy,
+ sizeof(struct tx_desc) * remainder);
+
+ add_10bit(&tx_ring->send_idx, remainder);
+ }
+
+ if (INDEX10(tx_ring->send_idx) == 0) {
+ if (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 = tx_ring->send_idx - 1;
+
+ spin_lock(&adapter->tcb_send_qlock);
+
+ if (tx_ring->send_tail)
+ tx_ring->send_tail->next = tcb;
+ else
+ tx_ring->send_head = tcb;
+
+ tx_ring->send_tail = tcb;
+
+ WARN_ON(tcb->next != NULL);
+
+ tx_ring->used++;
+
+ spin_unlock(&adapter->tcb_send_qlock);
+
+ /* Write the new write pointer back to the device. */
+ writel(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
+ *
+ * Assumption: Send spinlock has been acquired
+ */
+static int send_packet(struct sk_buff *skb, struct et131x_adapter *adapter)
+{
+ int status;
+ struct tcb *tcb;
+ u16 *shbufva;
+ unsigned long flags;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* 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 = tx_ring->tcb_qhead;
+
+ if (tcb == NULL) {
+ spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags);
+ return -ENOMEM;
+ }
+
+ tx_ring->tcb_qhead = tcb->next;
+
+ if (tx_ring->tcb_qhead == NULL)
+ tx_ring->tcb_qtail = NULL;
+
+ spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags);
+
+ tcb->skb = skb;
+
+ if (skb->data != NULL && skb_headlen(skb) >= 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 (tx_ring->tcb_qtail)
+ tx_ring->tcb_qtail->next = tcb;
+ else
+ /* Apparently ready Q is empty. */
+ tx_ring->tcb_qhead = tcb;
+
+ tx_ring->tcb_qtail = tcb;
+ spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags);
+ return status;
+ }
+ WARN_ON(tx_ring->used > NUM_TCB);
+ return 0;
+}
+
+/* et131x_send_packets - This function is called by the OS to send packets */
+static int et131x_send_packets(struct sk_buff *skb, struct net_device *netdev)
+{
+ int status = 0;
+ struct et131x_adapter *adapter = netdev_priv(netdev);
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* 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 (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;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+ u64 dma_addr;
+
+ 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 = tx_ring->tx_desc_ring +
+ INDEX10(tcb->index_start);
+
+ dma_addr = desc->addr_lo;
+ dma_addr |= (u64)desc->addr_hi << 32;
+
+ dma_unmap_single(&adapter->pdev->dev,
+ dma_addr,
+ 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 != 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 (tx_ring->tcb_qtail)
+ tx_ring->tcb_qtail->next = tcb;
+ else
+ /* Apparently ready Q is empty. */
+ tx_ring->tcb_qhead = tcb;
+
+ tx_ring->tcb_qtail = tcb;
+
+ spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags);
+ WARN_ON(tx_ring->used < 0);
+}
+
+/* et131x_free_busy_send_packets - Free and complete the stopped active sends
+ *
+ * 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;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* Any packets being sent? Check the first TCB on the send list */
+ spin_lock_irqsave(&adapter->tcb_send_qlock, flags);
+
+ tcb = tx_ring->send_head;
+
+ while (tcb != NULL && freed < NUM_TCB) {
+ struct tcb *next = tcb->next;
+
+ tx_ring->send_head = next;
+
+ if (next == NULL)
+ tx_ring->send_tail = NULL;
+
+ 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 = tx_ring->send_head;
+ }
+
+ WARN_ON(freed == NUM_TCB);
+
+ spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags);
+
+ tx_ring->used = 0;
+}
+
+/* et131x_handle_send_interrupt - Interrupt handler for sending processing
+ *
+ * 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;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ 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 = tx_ring->send_head;
+
+ while (tcb &&
+ ((serviced ^ tcb->index) & ET_DMA10_WRAP) &&
+ index < INDEX10(tcb->index)) {
+ tx_ring->used--;
+ tx_ring->send_head = tcb->next;
+ if (tcb->next == NULL)
+ 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 = tx_ring->send_head;
+ }
+ while (tcb &&
+ !((serviced ^ tcb->index) & ET_DMA10_WRAP)
+ && index > (tcb->index & ET_DMA10_MASK)) {
+ tx_ring->used--;
+ tx_ring->send_head = tcb->next;
+ if (tcb->next == NULL)
+ 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 = tx_ring->send_head;
+ }
+
+ /* Wake up the queue when we hit a low-water mark */
+ if (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;
+ u16 tmp;
+
+ 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, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_BMSR, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_PHYSID1, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_PHYSID2, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_ADVERTISE, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_LPA, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_EXPANSION, &tmp);
+ regs_buff[num++] = tmp;
+ /* Autoneg next page transmit reg */
+ et131x_mii_read(adapter, 0x07, &tmp);
+ regs_buff[num++] = tmp;
+ /* Link partner next page reg */
+ et131x_mii_read(adapter, 0x08, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_CTRL1000, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_STAT1000, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, 0x0b, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, 0x0c, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_MMD_CTRL, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_MMD_DATA, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, MII_ESTATUS, &tmp);
+ regs_buff[num++] = tmp;
+
+ et131x_mii_read(adapter, PHY_INDEX_REG, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_DATA_REG, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_LOOPBACK_CONTROL, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_LOOPBACK_CONTROL + 1, &tmp);
+ regs_buff[num++] = tmp;
+
+ et131x_mii_read(adapter, PHY_REGISTER_MGMT_CONTROL, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_CONFIG, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_PHY_CONTROL, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_INTERRUPT_MASK, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_INTERRUPT_STATUS, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_PHY_STATUS, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_LED_1, &tmp);
+ regs_buff[num++] = tmp;
+ et131x_mii_read(adapter, PHY_LED_2, &tmp);
+ regs_buff[num++] = tmp;
+
+ /* 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);
+}
+
+static void et131x_get_drvinfo(struct net_device *netdev,
+ struct ethtool_drvinfo *info)
+{
+ struct et131x_adapter *adapter = netdev_priv(netdev);
+
+ strlcpy(info->driver, DRIVER_NAME, sizeof(info->driver));
+ strlcpy(info->version, DRIVER_VERSION, sizeof(info->version));
+ strlcpy(info->bus_info, pci_name(adapter->pdev),
+ sizeof(info->bus_info));
+}
+
+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,
+};
+
+/* et131x_hwaddr_init - set up the MAC Address on the ET1310 */
+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 (is_zero_ether_addr(adapter->rom_addr)) {
+ /* 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
+ *
+ * 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)
+{
+ u16 max_payload;
+ int i, rc;
+
+ rc = et131x_init_eeprom(adapter);
+ if (rc < 0)
+ goto out;
+
+ if (!pci_is_pcie(pdev)) {
+ dev_err(&pdev->dev, "Missing PCIe capabilities\n");
+ goto err_out;
+ }
+
+ /* Let's set up the PORT LOGIC Register. */
+
+ /* Program the Ack/Nak latency and replay timers */
+ max_payload = pdev->pcie_mpss;
+
+ 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 (pcie_set_readrq(pdev, 2048)) {
+ dev_err(&pdev->dev,
+ "Couldn't change 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;
+ }
+ }
+ ether_addr_copy(adapter->addr, adapter->rom_addr);
+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_free - Free all memory allocated for use by Tx & Rx */
+static void et131x_adapter_memory_free(struct et131x_adapter *adapter)
+{
+ et131x_tx_dma_memory_free(adapter);
+ et131x_rx_dma_memory_free(adapter);
+}
+
+/* et131x_adapter_memory_alloc
+ * 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) {
+ dev_err(&adapter->pdev->dev,
+ "et131x_tx_dma_memory_alloc FAILED\n");
+ et131x_tx_dma_memory_free(adapter);
+ return status;
+ }
+ /* Receive buffer memory allocation */
+ status = et131x_rx_dma_memory_alloc(adapter);
+ if (status) {
+ dev_err(&adapter->pdev->dev,
+ "et131x_rx_dma_memory_alloc FAILED\n");
+ et131x_adapter_memory_free(adapter);
+ return status;
+ }
+
+ /* Init receive data structures */
+ status = et131x_init_recv(adapter);
+ if (status) {
+ dev_err(&adapter->pdev->dev, "et131x_init_recv FAILED\n");
+ et131x_adapter_memory_free(adapter);
+ }
+ return status;
+}
+
+static void et131x_adjust_link(struct net_device *netdev)
+{
+ struct et131x_adapter *adapter = netdev_priv(netdev);
+ struct phy_device *phydev = adapter->phydev;
+
+ if (!phydev)
+ return;
+ if (phydev->link == adapter->link)
+ return;
+
+ /* 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);
+
+ adapter->link = phydev->link;
+ phy_print_status(phydev);
+
+ if (phydev->link) {
+ adapter->boot_coma = 20;
+ if (phydev->speed == SPEED_10) {
+ u16 register18;
+
+ et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG,
+ &register18);
+ 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->speed == SPEED_1000 &&
+ adapter->registry_jumbo_packet > 2048) {
+ u16 reg;
+
+ et131x_mii_read(adapter, PHY_CONFIG, &reg);
+ 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);
+ } else {
+ adapter->boot_coma = 0;
+
+ if (phydev->speed == SPEED_10) {
+ u16 register18;
+
+ et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG,
+ &register18);
+ 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);
+ }
+}
+
+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, 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
+ *
+ * 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->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->fbr_lock);
+
+ adapter->registry_jumbo_packet = 1514; /* 1514-9216 */
+
+ /* Set the MAC address to a default */
+ ether_addr_copy(adapter->addr, default_mac);
+
+ return adapter;
+}
+
+/* et131x_pci_remove
+ *
+ * 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 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);
+ cancel_work_sync(&adapter->task);
+ 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. */
+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 */
+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.
+ */
+static 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 = netdev_priv(netdev);
+ struct rx_ring *rx_ring = &adapter->rx_ring;
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+ u32 status;
+
+ if (!netif_device_present(netdev)) {
+ handled = false;
+ goto out;
+ }
+
+ /* 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 = tx_ring->send_head;
+
+ if (tcb)
+ if (++tcb->stale > 1)
+ status |= ET_INTR_TXDMA_ISR;
+
+ if (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) {
+ /* 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
+ *
+ * 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 &= ~(ET_INTR_TXDMA_ERR | ET_INTR_RXDMA_XFR_DONE);
+
+ if (!status)
+ goto out;
+
+ /* Handle the TXDMA Error interrupt */
+ if (status & ET_INTR_TXDMA_ERR) {
+ /* Following read also clears the register (COR) */
+ u32 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.
+ */
+ }
+out:
+ et131x_enable_interrupts(adapter);
+}
+
+/* et131x_stats - Return the current device statistics */
+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. */
+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 */
+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
+ */
+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
+ */
+static int et131x_set_packet_filter(struct et131x_adapter *adapter)
+{
+ int filter = adapter->packet_filter;
+ 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 0;
+}
+
+/* et131x_multicast - The handler to configure multicasting on the interface */
+static void et131x_multicast(struct net_device *netdev)
+{
+ struct et131x_adapter *adapter = netdev_priv(netdev);
+ int packet_filter;
+ struct netdev_hw_addr *ha;
+ int i;
+
+ /* 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)
+ et131x_set_packet_filter(adapter);
+}
+
+/* et131x_tx - The handler to tx a packet on the device */
+static int et131x_tx(struct sk_buff *skb, struct net_device *netdev)
+{
+ int status = 0;
+ struct et131x_adapter *adapter = netdev_priv(netdev);
+ struct tx_ring *tx_ring = &adapter->tx_ring;
+
+ /* stop the queue if it's getting full */
+ if (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
+ *
+ * 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 tx_ring *tx_ring = &adapter->tx_ring;
+ 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 = 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 */
+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 */
+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;
+
+ 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);
+
+ netdev_info(netdev, "Setting MAC address to %pM\n",
+ 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
+ *
+ * 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 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_and_coherent(&pdev->dev, DMA_BIT_MASK(64)) &&
+ dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) {
+ 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);
+ netdev->ethtool_ops = &et131x_ethtool_ops;
+
+ 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_array(PHY_MAX_ADDR, sizeof(int),
+ GFP_KERNEL);
+ if (!adapter->mii_bus->irq)
+ 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 const struct pci_device_id 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 = et131x_pci_remove,
+ .driver.pm = ET131X_PM_OPS,
+};
+
+module_pci_driver(et131x_driver);
diff --git a/drivers/staging/et131x/et131x.h b/drivers/staging/et131x/et131x.h
new file mode 100644
index 00000000000..2ac6e998011
--- /dev/null
+++ b/drivers/staging/et131x/et131x.h
@@ -0,0 +1,1670 @@
+/*
+ * 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
+ */
+
+#define ET_RESET_ALL 0x007F;
+
+/*
+ * 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
+ */
+
+#define ET_RXDMA_CSR_HALT 0x0001
+#define ET_RXDMA_CSR_FBR0_SIZE_LO 0x0100
+#define ET_RXDMA_CSR_FBR0_SIZE_HI 0x0200
+#define ET_RXDMA_CSR_FBR0_ENABLE 0x0400
+#define ET_RXDMA_CSR_FBR1_SIZE_LO 0x0800
+#define ET_RXDMA_CSR_FBR1_SIZE_HI 0x1000
+#define ET_RXDMA_CSR_FBR1_ENABLE 0x2000
+#define ET_RXDMA_CSR_HALT_STATUS 0x00020000
+
+/*
+ * 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
+ */
+
+#define ET_RXDMA_PSR_NUM_DES_MASK 0xFFF;
+
+/*
+ * 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
+ */
+
+#define ET_TX_CTRL_FC_DISABLE 0x0008
+#define ET_TX_CTRL_TXMAC_ENABLE 0x0001
+
+/*
+ * 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
+ */
+
+#define ET_RX_CTRL_WOL_DISABLE 0x0008
+#define ET_RX_CTRL_RXMAC_ENABLE 0x0001
+
+/*
+ * 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_RX_WOL_LO_SA3_SHIFT 24
+#define ET_RX_WOL_LO_SA4_SHIFT 16
+#define ET_RX_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_RX_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_RX_UNI_PF_ADDR1_3_SHIFT 24
+#define ET_RX_UNI_PF_ADDR1_4_SHIFT 16
+#define ET_RX_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_RX_UNI_PF_ADDR2_3_SHIFT 24
+#define ET_RX_UNI_PF_ADDR2_4_SHIFT 16
+#define ET_RX_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_RX_UNI_PF_ADDR2_1_SHIFT 24
+#define ET_RX_UNI_PF_ADDR2_2_SHIFT 16
+#define ET_RX_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
+ */
+
+#define ET_RX_PFCTRL_MIN_PKT_SZ_SHIFT 16;
+#define ET_RX_PFCTRL_FRAG_FILTER_ENABLE 0x0008;
+#define ET_RX_PFCTRL_UNICST_FILTER_ENABLE 0x0004;
+#define ET_RX_PFCTRL_MLTCST_FILTER_ENABLE 0x0002;
+#define ET_RX_PFCTRL_BRDCST_FILTER_ENABLE 0x0001;
+
+/*
+ * 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
+ */
+
+#define ET_RX_MCIF_CTRL_MAX_SEG_SIZE_SHIFT 2;
+#define ET_RX_MCIF_CTRL_MAX_SEG_FC_ENABLE 0x0002;
+#define ET_RX_MCIF_CTRL_MAX_SEG_ENABLE 0x0001;
+
+/*
+ * 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 ET_MAC_CFG1_SOFT_RESET 0x80000000
+#define ET_MAC_CFG1_SIM_RESET 0x40000000
+#define ET_MAC_CFG1_RESET_RXMC 0x00080000
+#define ET_MAC_CFG1_RESET_TXMC 0x00040000
+#define ET_MAC_CFG1_RESET_RXFUNC 0x00020000
+#define ET_MAC_CFG1_RESET_TXFUNC 0x00010000
+#define ET_MAC_CFG1_LOOPBACK 0x00000100
+#define ET_MAC_CFG1_RX_FLOW 0x00000020
+#define ET_MAC_CFG1_TX_FLOW 0x00000010
+#define ET_MAC_CFG1_RX_ENABLE 0x00000004
+#define ET_MAC_CFG1_TX_ENABLE 0x00000001
+#define ET_MAC_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
+ */
+
+#define ET_MAC_CFG2_PREAMBLE_SHIFT 12;
+#define ET_MAC_CFG2_IFMODE_MASK 0x0300;
+#define ET_MAC_CFG2_IFMODE_1000 0x0200;
+#define ET_MAC_CFG2_IFMODE_100 0x0100;
+#define ET_MAC_CFG2_IFMODE_HUGE_FRAME 0x0020;
+#define ET_MAC_CFG2_IFMODE_LEN_CHECK 0x0010;
+#define ET_MAC_CFG2_IFMODE_PAD_CRC 0x0004;
+#define ET_MAC_CFG2_IFMODE_CRC_ENABLE 0x0002;
+#define ET_MAC_CFG2_IFMODE_FULL_DPLX 0x0001;
+
+/*
+ * 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
+ */
+
+#define ET_MAC_MIIMGMT_CLK_RST 0x0007
+
+/*
+ * 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 ET_MAC_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
+ */
+
+#define ET_MAC_MIIMGMT_STAT_PHYCRTL_MASK 0xFFFF;
+
+/*
+ * 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 ET_MAC_MGMT_BUSY 0x00000001 /* busy */
+#define ET_MAC_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
+ */
+
+#define ET_MAC_IFCTRL_GHDMODE (1 << 26)
+#define ET_MAC_IFCTRL_PHYMODE (1 << 24)
+
+/*
+ * 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
+ */
+
+/* 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) */
diff --git a/drivers/staging/frontier/Kconfig b/drivers/staging/frontier/Kconfig
new file mode 100644
index 00000000000..4da290b2f5b
--- /dev/null
+++ b/drivers/staging/frontier/Kconfig
@@ -0,0 +1,5 @@
+config TRANZPORT
+ tristate "Frontier Tranzport and Alphatrack support"
+ depends on USB
+ ---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 00000000000..2d2ac97492d
--- /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 00000000000..cd07af22406
--- /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 00000000000..3620ad2df3e
--- /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 <greg@kroah.com>
+and David Taht <d@teklibre.com>.
diff --git a/drivers/staging/frontier/alphatrack.c b/drivers/staging/frontier/alphatrack.c
new file mode 100644
index 00000000000..226b2316310
--- /dev/null
+++ b/drivers/staging/frontier/alphatrack.c
@@ -0,0 +1,849 @@
+/*
+ * 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 <mhund@ld-didactic.de>
+ *
+ * The ldusb driver was, in turn, derived from Lego USB Tower driver
+ * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net>
+ * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net>
+ *
+ * This program is free software; 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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kobject.h>
+#include <linux/mutex.h>
+
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+
+#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 <m@taht.net>");
+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->oldi_buffer);
+ kfree(dev->write_buffer);
+ kfree(dev);
+}
+
+/** 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) {
+ pr_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;
+ pr_err("%s: No device or device unplugged %d\n",
+ __func__, 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;
+ pr_err("%s: No device or device unplugged %d\n",
+ __func__, 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) {
+ dev_err(&dev->intf->dev, "Endpoint should not 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;
+ dev_err(&dev->intf->dev,
+ "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)
+ 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_array(true_size,
+ sizeof(struct alphatrack_icmd),
+ GFP_KERNEL);
+ if (!dev->ring_buffer)
+ goto error;
+
+ dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size,
+ GFP_KERNEL);
+ if (!dev->interrupt_in_buffer)
+ goto error;
+
+ dev->oldi_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL);
+ if (!dev->oldi_buffer)
+ 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_array(true_size,
+ dev->interrupt_out_endpoint_size,
+ GFP_KERNEL);
+ if (!dev->interrupt_out_buffer)
+ goto error;
+
+ dev->write_buffer = kmalloc_array(true_size,
+ sizeof(struct alphatrack_ocmd),
+ GFP_KERNEL);
+ if (!dev->write_buffer)
+ 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 {
+ atomic_set(&dev->writes_pending, 0);
+ dev->intf = NULL;
+ mutex_unlock(&dev->mtx);
+ }
+
+ 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 00000000000..418c6053c02
--- /dev/null
+++ b/drivers/staging/frontier/alphatrack.h
@@ -0,0 +1,78 @@
+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 00000000000..2f86163d701
--- /dev/null
+++ b/drivers/staging/frontier/tranzport.c
@@ -0,0 +1,973 @@
+/*
+ * 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 <mhund@ld-didactic.de>
+ *
+ * The ldusb driver was, in turn, derived from Lego USB Tower driver
+ * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net>
+ * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net>
+ *
+ * This program is free software; 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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+
+/* 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 <m@taht.net>");
+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 value##_show(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_RO(value)
+
+#define show_set_int(value) \
+ static ssize_t value##_show(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 value##_store(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_RW(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) {
+ pr_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;
+ pr_err("%s: No device or device unplugged %d\n",
+ __func__, 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;
+ pr_err("%s: No device or device unplugged %d\n",
+ __func__, 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) {
+ dev_err(&dev->intf->dev, "Endpoint should not 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;
+ dev_err(&dev->intf->dev,
+ "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)
+ 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)
+ goto error;
+
+ dev->interrupt_in_buffer =
+ kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL);
+ if (!dev->interrupt_in_buffer)
+ 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_array(write_buffer_size,
+ dev->interrupt_out_endpoint_size, GFP_KERNEL);
+ if (!dev->interrupt_out_buffer)
+ 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 00000000000..c54b4e83d6e
--- /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 00000000000..3e987770a14
--- /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 00000000000..1d346bc4f44
--- /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 <greg@kroah.com> and
+Cc: Marek Belisko <marek.belisko@gmail.com>
diff --git a/drivers/staging/ft1000/ft1000-pcmcia/Makefile b/drivers/staging/ft1000/ft1000-pcmcia/Makefile
new file mode 100644
index 00000000000..660b7a50e89
--- /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 00000000000..60c015c1c28
--- /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 */
+static 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 00000000000..0c21ac68003
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.h
@@ -0,0 +1,73 @@
+/*---------------------------------------------------------------------------
+ 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_pcmcia {
+ int PktIntfErr;
+ u16 packetseqnum;
+ void *link;
+};
+
+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 00000000000..aad3c80d07c
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.img
Binary files 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 00000000000..1f8b3ca35c6
--- /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
+ <dahinds@users.sourceforge.net>. 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+/*====================================================================*/
+
+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) {
+ dev_err(&link->dev, "Could not configure pcmcia\n");
+ return -ENODEV;
+ }
+
+ /* configure device */
+ ret = pcmcia_enable_device(link);
+ if (ret) {
+ dev_err(&link->dev, "Could not enable pcmcia\n");
+ goto failed;
+ }
+
+ link->priv = init_ft1000_card(link, &ft1000_reset);
+ if (!link->priv) {
+ dev_err(&link->dev, "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 00000000000..d44e8583ad1
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c
@@ -0,0 +1,774 @@
+/*---------------------------------------------------------------------------
+ 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 <linux/module.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/unistd.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/vmalloc.h>
+
+#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 = false;
+ 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 00000000000..a6158bef58e
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c
@@ -0,0 +1,2266 @@
+/*---------------------------------------------------------------------------
+ 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/ioport.h>
+#include <linux/wait.h>
+#include <linux/vmalloc.h>
+
+#include <linux/firmware.h>
+#include <linux/ethtool.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#ifdef FT_DEBUG
+#define DEBUG(n, args...) printk(KERN_DEBUG args);
+#else
+#define DEBUG(n, args...)
+#endif
+
+#include <linux/delay.h>
+#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);
+ struct ft1000_pcmcia *pcmcia = info->priv;
+ u16 tempword;
+
+ DEBUG(1, "ft1000_hw:ft1000_reset_asic called\n");
+
+ (*info->ft1000_reset) (pcmcia->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);
+ struct ft1000_pcmcia *pcmcia = info->priv;
+ u16 i;
+ u32 templong;
+ u16 tempword;
+
+ DEBUG(1, "ft1000:ft1000_hw:ft1000_flush_fifo called\n");
+ if (pcmcia->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 */
+ pcmcia->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) {
+ pcmcia->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);
+ struct ft1000_pcmcia *pcmcia = info->priv;
+ 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 = pcmcia->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);
+ }
+
+ kfree(info->priv);
+
+ 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);
+
+ strlcpy(info->driver, "ft1000", sizeof(info->driver));
+ snprintf(info->bus_info, sizeof(info->bus_info), "PCMCIA 0x%lx",
+ dev->base_addr);
+ snprintf(info->fw_version, sizeof(info->fw_version), "%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 ft1000_pcmcia *pcmcia;
+ 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));
+
+ info->priv = kzalloc(sizeof(struct ft1000_pcmcia), GFP_KERNEL);
+ pcmcia = info->priv;
+ pcmcia->link = link;
+
+ spin_lock_init(&info->dpram_lock);
+ info->DrvErrNum = 0;
+ info->registered = 1;
+ 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;
+ dev->ethtool_ops = &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 00000000000..88f6f9ce304
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c
@@ -0,0 +1,211 @@
+/*---------------------------------------------------------------------------
+ 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include <linux/netdevice.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include "ft1000.h"
+
+#define FT1000_PROC "ft1000"
+#define MAX_FILE_LEN 255
+
+#define seq_putx(m, message, size, var) \
+ seq_printf(m, message); \
+ for (i = 0; i < (size - 1); i++) { \
+ seq_printf(m, "%02x:", var[i]); \
+ } \
+ seq_printf(m, "%02x\n", var[i])
+
+#define seq_putd(m, message, size, var) \
+ seq_printf(m, message); \
+ for (i = 0; i < (size - 1); i++) { \
+ seq_printf(m, "%d.", var[i]); \
+ } \
+ seq_printf(m, "%d\n", var[i])
+
+static int ft1000ReadProc(struct seq_file *m, void *v)
+{
+ static const char *status[] = {
+ "Idle (Disconnect)", "Searching", "Active (Connected)",
+ "Waiting for L2", "Sleep", "No Coverage", "", ""
+ };
+ static const char *signal[] = { "", "*", "**", "***", "****" };
+
+ struct net_device *dev = m->private;
+ struct ft1000_info *info = netdev_priv(dev);
+ int i;
+ int strength;
+ int quality;
+ struct timeval tv;
+ time_t delta;
+
+ 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;
+ seq_printf(m, "Connection Time: %02ld:%02ld:%02ld\n",
+ ((delta / 3600) % 24), ((delta / 60) % 60), (delta % 60));
+ seq_printf(m, "Connection Time[s]: %ld\n", delta);
+ seq_printf(m, "Asic ID: %s\n",
+ info->AsicID ==
+ ELECTRABUZZ_ID ? "ELECTRABUZZ ASIC" : "MAGNEMITE ASIC");
+ seq_putx(m, "SKU: ", SKUSZ, info->Sku);
+ seq_putx(m, "EUI64: ", EUISZ, info->eui64);
+ seq_putd(m, "DSP version number: ", DSPVERSZ, info->DspVer);
+ seq_putx(m, "Hardware Serial Number: ", HWSERNUMSZ, info->HwSerNum);
+ seq_putx(m, "Caliberation Version: ", CALVERSZ, info->RfCalVer);
+ seq_putd(m, "Caliberation Date: ", CALDATESZ, info->RfCalDate);
+ seq_printf(m, "Media State: %s\n",
+ (info->mediastate) ? "link" : "no link");
+ seq_printf(m, "Connection Status: %s\n", status[info->ConStat & 0x7]);
+ seq_printf(m, "RX packets: %ld\n", info->stats.rx_packets);
+ seq_printf(m, "TX packets: %ld\n", info->stats.tx_packets);
+ seq_printf(m, "RX bytes: %ld\n", info->stats.rx_bytes);
+ seq_printf(m, "TX bytes: %ld\n", info->stats.tx_bytes);
+ seq_printf(m, "Signal Strength: %s\n", signal[strength]);
+ seq_printf(m, "Signal Quality: %s\n", signal[quality]);
+ return 0;
+}
+
+/*
+ * seq_file wrappers for procfile show routines.
+ */
+static int ft1000_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ft1000ReadProc, PDE_DATA(inode));
+}
+
+static const struct file_operations ft1000_proc_fops = {
+ .open = ft1000_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int ft1000NotifyProc(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct ft1000_info *info;
+
+ info = netdev_priv(dev);
+
+ switch (event) {
+ case NETDEV_CHANGENAME:
+ remove_proc_entry(info->netdevname, info->ft1000_proc_dir);
+ proc_create_data(dev->name, 0644, info->ft1000_proc_dir,
+ &ft1000_proc_fops, 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->ft1000_proc_dir = proc_mkdir(FT1000_PROC, init_net.proc_net);
+
+ proc_create_data(dev->name, 0644, info->ft1000_proc_dir,
+ &ft1000_proc_fops, 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->ft1000_proc_dir);
+ 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 00000000000..f0f52401588
--- /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 00000000000..a8945b78596
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_debug.c
@@ -0,0 +1,792 @@
+/*
+*---------------------------------------------------------------------------
+* 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/netdevice.h>
+#include <linux/delay.h>
+
+#include <linux/ioctl.h>
+#include <linux/debugfs.h>
+#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 const 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_usb *dev)
+{
+ int result;
+ int i;
+ struct dentry *dir, *file;
+ struct ft1000_debug_dirs *tmp;
+
+ /* make a new device name */
+ sprintf(dev->DeviceName, "%s%d", "FT1000_", dev->CardNumber);
+
+ DEBUG("%s: number of instance = %d\n", __func__, ft1000_flarion_cnt);
+ DEBUG("DeviceCreated = %x\n", dev->DeviceCreated);
+
+ if (dev->DeviceCreated) {
+ DEBUG("%s: \"%s\" already registered\n", __func__, dev->DeviceName);
+ return -EIO;
+ }
+
+
+ /* register the device */
+ DEBUG("%s: \"%s\" debugfs device registration\n", __func__, dev->DeviceName);
+
+ tmp = kmalloc(sizeof(struct ft1000_debug_dirs), GFP_KERNEL);
+ if (tmp == NULL) {
+ result = -1;
+ goto fail;
+ }
+
+ dir = debugfs_create_dir(dev->DeviceName, NULL);
+ 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 = dev->CardNumber;
+ list_add(&(tmp->list), &(dev->nodes.list));
+
+ DEBUG("%s: registered debugfs directory \"%s\"\n", __func__, dev->DeviceName);
+
+ /* initialize application information */
+ dev->appcnt = 0;
+ for (i=0; i<MAX_NUM_APP; i++) {
+ dev->app_info[i].nTxMsg = 0;
+ dev->app_info[i].nRxMsg = 0;
+ dev->app_info[i].nTxMsgReject = 0;
+ dev->app_info[i].nRxMsgMiss = 0;
+ dev->app_info[i].fileobject = NULL;
+ dev->app_info[i].app_id = i+1;
+ dev->app_info[i].DspBCMsgFlag = 0;
+ dev->app_info[i].NumOfMsg = 0;
+ init_waitqueue_head(&dev->app_info[i].wait_dpram_msg);
+ INIT_LIST_HEAD(&dev->app_info[i].app_sqlist);
+ }
+
+ dev->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 *netdev)
+{
+ struct ft1000_info *info = netdev_priv(netdev);
+ struct ft1000_usb *dev = info->priv;
+ 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 (dev->DeviceCreated) {
+ ft1000_flarion_cnt--;
+ list_for_each_safe(pos, q, &dev->nodes.list) {
+ dir = list_entry(pos, struct ft1000_debug_dirs, list);
+ if (dir->int_number == dev->CardNumber) {
+ debugfs_remove(dir->file);
+ debugfs_remove(dir->dent);
+ list_del(pos);
+ kfree(dir);
+ }
+ }
+ DEBUG("%s: unregistered device \"%s\"\n", __func__,
+ dev->DeviceName);
+
+ /* Make sure we free any memory reserve for slow Queue */
+ for (i=0; i<MAX_NUM_APP; i++) {
+ while (list_empty(&dev->app_info[i].app_sqlist) == 0) {
+ pdpram_blk = list_entry(dev->app_info[i].app_sqlist.next, struct dpram_blk, list);
+ list_del(&pdpram_blk->list);
+ ft1000_free_buffer(pdpram_blk, &freercvpool);
+
+ }
+ wake_up_interruptible(&dev->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);
+ }
+ }
+ dev->DeviceCreated = FALSE;
+ }
+
+
+}
+
+/*
+*---------------------------------------------------------------------------
+* Function: ft1000_open
+*
+* Parameters:
+*
+* Description:
+*
+* Notes:
+*
+*---------------------------------------------------------------------------
+*/
+static int ft1000_open(struct inode *inode, struct file *file)
+{
+ struct ft1000_info *info;
+ struct ft1000_usb *dev = (struct ft1000_usb *)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), dev->appcnt);
+
+ /* Check if maximum number of application exceeded */
+ if (dev->appcnt > MAX_NUM_APP) {
+ DEBUG("Maximum number of application exceeded\n");
+ return -EACCES;
+ }
+
+ /* Search for available application info block */
+ for (i=0; i<MAX_NUM_APP; i++) {
+ if ((dev->app_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;
+ }
+
+ dev->appcnt++;
+ dev->app_info[i].fileobject = &file->f_owner;
+ dev->app_info[i].nTxMsg = 0;
+ dev->app_info[i].nRxMsg = 0;
+ dev->app_info[i].nTxMsgReject = 0;
+ dev->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 *netdev = file->private_data;
+ struct ft1000_info *info = netdev_priv(netdev);
+ struct ft1000_usb *dev = info->priv;
+ 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);
+ }
+
+ /* Search for matching file object */
+ for (i=0; i<MAX_NUM_APP; i++) {
+ if (dev->app_info[i].fileobject == &file->f_owner) {
+ /* DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", dev->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(&dev->app_info[i].app_sqlist) == 0) {
+ DEBUG("FT1000:ft1000_poll_dev:Message detected in slow queue\n");
+ return(POLLIN | POLLRDNORM | POLLPRI);
+ }
+
+ poll_wait(file, &dev->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_usb *ft1000dev;
+ int result=0;
+ int cmd;
+ int i;
+ u16 tempword;
+ unsigned long flags;
+ struct timeval tv;
+ struct IOCTL_GET_VER get_ver_data;
+ struct 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->priv;
+ 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; i<MAX_NUM_APP; i++) {
+ if (ft1000dev->app_info[i].fileobject == &file->f_owner) {
+ ft1000dev->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:
+ {
+ struct IOCTL_DPRAM_BLK *dpram_data = NULL;
+ /* struct 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 (ft1000dev->DrvMsgPend) {
+ return (-ENOTTY);
+ }
+
+ if (ft1000dev->fProvComplete == 0) {
+ return (-EACCES);
+ }
+
+ ft1000dev->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);
+ if (result)
+ break;
+ 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; i<MAX_NUM_APP; i++) {
+ if (ft1000dev->app_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 = ft1000dev->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);
+
+
+ ft1000dev->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;
+ struct 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; i<MAX_NUM_APP; i++) {
+ if (ft1000dev->app_info[i].fileobject == &file->f_owner) {
+ /*DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", ft1000dev->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(&ft1000dev->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(ft1000dev->app_info[i].app_sqlist.next, struct dpram_blk, list);
+ list_del(&pdpram_blk->list);
+ ft1000dev->app_info[i].NumOfMsg--;
+ /* DEBUG("FT1000:ft1000_ioctl:NumOfMsg for app %d = %d\n", i, ft1000dev->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;
+ }
+ ft1000dev->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;
+ struct ft1000_usb *ft1000dev;
+ int i;
+ struct dpram_blk *pdpram_blk;
+
+ DEBUG("ft1000_release called\n");
+
+ dev = file->private_data;
+ info = netdev_priv(dev);
+ ft1000dev = info->priv;
+
+ if (ft1000_flarion_cnt == 0) {
+ ft1000dev->appcnt--;
+ return (-EBADF);
+ }
+
+ /* Search for matching file object */
+ for (i=0; i<MAX_NUM_APP; i++) {
+ if (ft1000dev->app_info[i].fileobject == &file->f_owner) {
+ /* DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", ft1000dev->app_info[i].app_id); */
+ break;
+ }
+ }
+
+ if (i==MAX_NUM_APP)
+ return 0;
+
+ while (list_empty(&ft1000dev->app_info[i].app_sqlist) == 0) {
+ DEBUG("Remove and free memory queue up on slow queue\n");
+ pdpram_blk = list_entry(ft1000dev->app_info[i].app_sqlist.next, struct dpram_blk, list);
+ list_del(&pdpram_blk->list);
+ ft1000_free_buffer(pdpram_blk, &freercvpool);
+ }
+
+ /* initialize application information */
+ ft1000dev->appcnt--;
+ DEBUG("ft1000_chdev:%s:appcnt = %d\n", __func__, ft1000dev->appcnt);
+ ft1000dev->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 00000000000..65f98016713
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_download.c
@@ -0,0 +1,1074 @@
+/*
+* CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved.
+*
+* This file is part of Express Card USB Driver
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#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;
+};
+
+
+/* checks if the doorbell register is cleared */
+static int check_usb_db(struct ft1000_usb *ft1000dev)
+{
+ int loopcnt;
+ u16 temp;
+ int 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 -1;
+}
+
+/* gets the handshake and compares it with the expected value */
+static u16 get_handshake(struct ft1000_usb *ft1000dev, u16 expected_value)
+{
+ u16 handshake;
+ int loopcnt;
+ int status = 0;
+
+ 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 (ft1000dev->fcodeldr) {
+ DEBUG(" get_handshake: fcodeldr is %d\n",
+ ft1000dev->fcodeldr);
+ ft1000dev->fcodeldr = 0;
+ status = check_usb_db(ft1000dev);
+ if (status != 0) {
+ DEBUG("get_handshake: check_usb_db failed\n");
+ 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;
+}
+
+/* write the handshake value to the handshake location */
+static void put_handshake(struct ft1000_usb *ft1000dev, u16 handshake_value)
+{
+ u32 tempx;
+ u16 tempword;
+ int 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_usb *ft1000dev, u16 expected_value)
+{
+ u16 handshake;
+ int loopcnt;
+ u16 temp;
+ int status = 0;
+
+ loopcnt = 0;
+ handshake = 0;
+
+ while (loopcnt < 100) {
+ if (ft1000dev->usbboot == 2) {
+ status = ft1000_read_dpram32(ft1000dev, 0,
+ (u8 *)&(ft1000dev->tempbuf[0]), 64);
+ for (temp = 0; temp < 16; temp++) {
+ DEBUG("tempbuf %d = 0x%x\n", temp,
+ ft1000dev->tempbuf[temp]);
+ }
+ status = ft1000_read_dpram16(ft1000dev,
+ DWNLD_MAG1_HANDSHAKE_LOC,
+ (u8 *)&handshake, 1);
+ DEBUG("handshake from read_dpram16 = 0x%x\n",
+ handshake);
+ if (ft1000dev->dspalive == ft1000dev->tempbuf[6]) {
+ handshake = 0;
+ } else {
+ handshake = ft1000dev->tempbuf[1];
+ ft1000dev->dspalive =
+ ft1000dev->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_usb *ft1000dev, u16 handshake_value)
+{
+ int i;
+
+ for (i = 0; i < 1000; i++)
+ ;
+}
+
+static u16 get_request_type(struct ft1000_usb *ft1000dev)
+{
+ u16 request_type;
+ int status;
+ u16 tempword;
+ u32 tempx;
+
+ if (ft1000dev->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_usb *ft1000dev)
+{
+ u16 request_type;
+ int status;
+ u16 tempword;
+ u32 tempx;
+
+ if (ft1000dev->bootmode == 1) {
+ status = fix_ft1000_read_dpram32(ft1000dev,
+ DWNLD_MAG1_TYPE_LOC, (u8 *)&tempx);
+ tempx = ntohl(tempx);
+ } else {
+ if (ft1000dev->usbboot == 2) {
+ tempx = ft1000dev->tempbuf[2];
+ tempword = ft1000dev->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;
+}
+
+static long get_request_value(struct ft1000_usb *ft1000dev)
+{
+ u32 value;
+ u16 tempword;
+ int status;
+
+ if (ft1000dev->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;
+}
+
+
+/* writes a value to DWNLD_MAG1_SIZE_LOC */
+static void put_request_value(struct ft1000_usb *ft1000dev, long lvalue)
+{
+ u32 tempx;
+ int status;
+
+ tempx = ntohl(lvalue);
+ status = fix_ft1000_write_dpram32(ft1000dev, DWNLD_MAG1_SIZE_LOC,
+ (u8 *)&tempx);
+}
+
+
+
+/* returns the checksum of the pseudo header */
+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 -EREMOTEIO;
+ }
+
+ return 0;
+}
+
+static int write_dpram32_and_check(struct ft1000_usb *ft1000dev,
+ u16 tempbuffer[], u16 dpram)
+{
+ int status;
+ u16 resultbuffer[64];
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ status = ft1000_write_dpram32(ft1000dev, dpram,
+ (u8 *)&tempbuffer[0], 64);
+ if (status == 0) {
+ /* 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");
+ usleep_range(9000, 11000);
+ 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");
+ usleep_range(9000, 11000);
+ break;
+ }
+ } else {
+ if (check_buffers(tempbuffer, resultbuffer, 32,
+ 0)) {
+ DEBUG("FT1000:download:DPRAM write failed 3 during bootloading\n");
+ usleep_range(9000, 11000);
+ break;
+ }
+ }
+ if (status == 0)
+ break;
+ }
+ }
+ return status;
+}
+
+/* writes a block of DSP image to DPRAM
+ * Parameters: struct ft1000_usb - 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
+ */
+static int write_blk(struct ft1000_usb *ft1000dev, u16 **pUsFile, u8 **pUcFile,
+ long word_length)
+{
+ int status = 0;
+ u16 dpram;
+ int loopcnt, i;
+ u16 tempword;
+ u16 tempbuffer[64];
+
+ /*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 (ft1000dev->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 {
+ status = write_dpram32_and_check(ft1000dev, tempbuffer,
+ dpram);
+ if (status != 0) {
+ 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"); */
+}
+
+/* writes a block of DSP image to DPRAM
+ * Parameters: struct ft1000_usb - 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
+ */
+static int write_blk_fifo(struct ft1000_usb *ft1000dev, u16 **pUsFile,
+ u8 **pUcFile, long word_length)
+{
+ 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 0;
+}
+
+static int scram_start_dwnld(struct ft1000_usb *ft1000dev, u16 *hshake,
+ u32 *state)
+{
+ int status = 0;
+
+ DEBUG("FT1000:STATE_START_DWNLD\n");
+ if (ft1000dev->usbboot)
+ *hshake = get_handshake_usb(ft1000dev, HANDSHAKE_DSP_BL_READY);
+ else
+ *hshake = get_handshake(ft1000dev, HANDSHAKE_DSP_BL_READY);
+ if (*hshake == 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 if (*hshake == HANDSHAKE_TIMEOUT_VALUE) {
+ status = -ETIMEDOUT;
+ } else {
+ DEBUG("FT1000:download:Download error: Handshake failed\n");
+ status = -ENETRESET;
+ }
+ *state = STATE_BOOT_DWNLD;
+ return status;
+}
+
+static int request_code_segment(struct ft1000_usb *ft1000dev, u16 **s_file,
+ u8 **c_file, const u8 *endpoint, bool boot_case)
+{
+ long word_length;
+ int status = 0;
+
+ /*DEBUG("FT1000:REQUEST_CODE_SEGMENT\n");i*/
+ 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");
+ return -1;
+ }
+ if ((word_length * 2 + (long)c_file) > (long)endpoint) {
+ /* Error, beyond boot code range.*/
+ DEBUG("FT1000:download:Download error: Requested len=%d exceeds BOOT code boundary.\n", (int)word_length);
+ return -1;
+ }
+ if (word_length & 0x1)
+ word_length++;
+ word_length = word_length / 2;
+
+ if (boot_case) {
+ status = write_blk(ft1000dev, s_file, c_file, word_length);
+ /*DEBUG("write_blk returned %d\n", status); */
+ } else {
+ status = write_blk_fifo(ft1000dev, s_file, c_file, word_length);
+ if (ft1000dev->usbboot == 0)
+ ft1000dev->usbboot++;
+ if (ft1000dev->usbboot == 1)
+ status |= ft1000_write_dpram16(ft1000dev,
+ DWNLD_MAG1_PS_HDR_LOC, 0, 0);
+ }
+ return status;
+}
+
+/* Scramble downloader for Harley based ASIC via USB interface */
+int scram_dnldr(struct ft1000_usb *ft1000dev, void *pFileStart,
+ u32 FileLength)
+{
+ int status = 0;
+ u32 state;
+ u16 handshake;
+ struct pseudo_hdr *pseudo_header;
+ u16 pseudo_header_len;
+ long word_length;
+ u16 request;
+ u16 temp;
+
+ 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");
+
+ ft1000dev->fcodeldr = 0;
+ ft1000dev->usbboot = 0;
+ ft1000dev->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 == 0) && (state != STATE_DONE_FILE)) {
+ switch (state) {
+ case STATE_START_DWNLD:
+ status = scram_start_dwnld(ft1000dev, &handshake,
+ &state);
+ break;
+
+ case STATE_BOOT_DWNLD:
+ DEBUG("FT1000:STATE_BOOT_DWNLD\n");
+ ft1000dev->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;
+ ft1000dev->fcodeldr = 1;
+ break;
+ case REQUEST_CODE_SEGMENT:
+ status = request_code_segment(ft1000dev,
+ &s_file, &c_file,
+ (const u8 *)boot_end,
+ true);
+ break;
+ default:
+ DEBUG
+ ("FT1000:download:Download error: Bad request type=%d in BOOT download state.\n",
+ request);
+ status = -1;
+ break;
+ }
+ if (ft1000dev->usbboot)
+ put_handshake_usb(ft1000dev,
+ HANDSHAKE_RESPONSE);
+ else
+ put_handshake(ft1000dev,
+ HANDSHAKE_RESPONSE);
+ } else {
+ DEBUG
+ ("FT1000:download:Download error: Handshake failed\n");
+ status = -1;
+ }
+
+ break;
+
+ case STATE_CODE_DWNLD:
+ /* DEBUG("FT1000:STATE_CODE_DWNLD\n"); */
+ ft1000dev->bootmode = 0;
+ if (ft1000dev->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 (ft1000dev->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 = -1;
+ 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 = -1;
+ break;
+ }
+ break;
+ case REQUEST_DONE_CL:
+ ft1000dev->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 = -1;
+ break;
+ }
+
+ status = request_code_segment(ft1000dev,
+ &s_file, &c_file,
+ (const u8 *)code_end,
+ false);
+
+ 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 = -1;
+ break;
+ }
+ break;
+
+ default:
+ DEBUG
+ ("FT1000:download:Download error: Bad request type=%d in CODE download state.\n",
+ request);
+ status = -1;
+ break;
+ }
+ if (ft1000dev->usbboot)
+ put_handshake_usb(ft1000dev,
+ HANDSHAKE_RESPONSE);
+ else
+ put_handshake(ft1000dev,
+ HANDSHAKE_RESPONSE);
+ } else {
+ DEBUG
+ ("FT1000:download:Download error: Handshake failed\n");
+ status = -1;
+ }
+
+ 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 = -1;
+ }
+ } else {
+ status = -1;
+ }
+ } else {
+ /* Checksum did not compute */
+ status = -1;
+ }
+ 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 = -1;
+ break;
+ } /* End Switch */
+
+ if (status != 0)
+ 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 00000000000..b6a77088cfe
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_hw.c
@@ -0,0 +1,1604 @@
+/* CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved.
+*
+*
+* This file is part of Express Card USB Driver
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/usb.h>
+#include "ft1000_usb.h"
+#include <linux/types.h>
+
+#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
+
+#if 0
+#define JDEBUG
+#endif
+
+static int ft1000_submit_rx_urb(struct ft1000_info *info);
+
+static u8 tempbuffer[1600];
+
+#define MAX_RCV_LOOP 100
+
+/* send a control message via USB interface synchronously
+* Parameters: ft1000_usb - 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
+*/
+static int ft1000_control(struct ft1000_usb *ft1000dev, unsigned int pipe,
+ u8 request, u8 requesttype, u16 value, u16 index,
+ void *data, u16 size, int timeout)
+{
+ int 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, timeout);
+
+ if (ret > 0)
+ ret = 0;
+
+ return ret;
+}
+
+/* returns the value in a register */
+int ft1000_read_register(struct ft1000_usb *ft1000dev, u16 *Data,
+ u16 nRegIndx)
+{
+ int ret = 0;
+
+ ret = ft1000_control(ft1000dev,
+ usb_rcvctrlpipe(ft1000dev->dev, 0),
+ HARLEY_READ_REGISTER,
+ HARLEY_READ_OPERATION,
+ 0,
+ nRegIndx,
+ Data,
+ 2,
+ USB_CTRL_GET_TIMEOUT);
+
+ return ret;
+}
+
+/* writes the value in a register */
+int ft1000_write_register(struct ft1000_usb *ft1000dev, u16 value,
+ u16 nRegIndx)
+{
+ int ret = 0;
+
+ ret = ft1000_control(ft1000dev,
+ usb_sndctrlpipe(ft1000dev->dev, 0),
+ HARLEY_WRITE_REGISTER,
+ HARLEY_WRITE_OPERATION,
+ value,
+ nRegIndx,
+ NULL,
+ 0,
+ USB_CTRL_SET_TIMEOUT);
+
+ return ret;
+}
+
+/* read a number of bytes from DPRAM */
+int ft1000_read_dpram32(struct ft1000_usb *ft1000dev, u16 indx, u8 *buffer,
+ u16 cnt)
+{
+ int ret = 0;
+
+ ret = ft1000_control(ft1000dev,
+ usb_rcvctrlpipe(ft1000dev->dev, 0),
+ HARLEY_READ_DPRAM_32,
+ HARLEY_READ_OPERATION,
+ 0,
+ indx,
+ buffer,
+ cnt,
+ USB_CTRL_GET_TIMEOUT);
+
+ return ret;
+}
+
+/* writes into DPRAM a number of bytes */
+int ft1000_write_dpram32(struct ft1000_usb *ft1000dev, u16 indx, u8 *buffer,
+ u16 cnt)
+{
+ int ret = 0;
+
+ 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,
+ USB_CTRL_SET_TIMEOUT);
+
+ return ret;
+}
+
+/* read 16 bits from DPRAM */
+int ft1000_read_dpram16(struct ft1000_usb *ft1000dev, u16 indx, u8 *buffer,
+ u8 highlow)
+{
+ int ret = 0;
+ 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,
+ USB_CTRL_GET_TIMEOUT);
+
+ return ret;
+}
+
+/* write into DPRAM a number of bytes */
+int ft1000_write_dpram16(struct ft1000_usb *ft1000dev, u16 indx, u16 value,
+ u8 highlow)
+{
+ int ret = 0;
+ 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,
+ USB_CTRL_SET_TIMEOUT);
+
+ return ret;
+}
+
+/* read DPRAM 4 words at a time */
+int fix_ft1000_read_dpram32(struct ft1000_usb *ft1000dev, u16 indx,
+ u8 *buffer)
+{
+ u8 buf[16];
+ u16 pos;
+ int ret = 0;
+
+ pos = (indx / 4) * 4;
+ ret = ft1000_read_dpram32(ft1000dev, pos, buf, 16);
+
+ if (ret == 0) {
+ 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;
+}
+
+
+/* Description: This function write to DPRAM 4 words at a time */
+int fix_ft1000_write_dpram32(struct ft1000_usb *ft1000dev, u16 indx, u8 *buffer)
+{
+ u16 pos1;
+ u16 pos2;
+ u16 i;
+ u8 buf[32];
+ u8 resultbuffer[32];
+ u8 *pdata;
+ int ret = 0;
+
+ pos1 = (indx / 4) * 4;
+ pdata = buffer;
+ ret = ft1000_read_dpram32(ft1000dev, pos1, buf, 16);
+
+ if (ret == 0) {
+ 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 == 0) {
+ buffer = pdata;
+ for (i = 0; i < 16; i++) {
+ if (buf[i] != resultbuffer[i])
+ ret = -1;
+ }
+ }
+
+ if (ret == -1) {
+ ret = ft1000_write_dpram32(ft1000dev, pos1,
+ (u8 *)&tempbuffer[0], 16);
+ ret = ft1000_read_dpram32(ft1000dev, pos1,
+ (u8 *)&resultbuffer[0], 16);
+ if (ret == 0) {
+ buffer = pdata;
+ for (i = 0; i < 16; i++) {
+ if (tempbuffer[i] != resultbuffer[i]) {
+ ret = -1;
+ DEBUG("%s Failed to write\n",
+ __func__);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* reset or activate the DSP */
+static void card_reset_dsp(struct ft1000_usb *ft1000dev, bool value)
+{
+ int status = 0;
+ 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);
+ }
+}
+
+/* send a command to ASIC
+* Parameters: ft1000_usb - device structure
+* ptempbuffer - command buffer
+* size - command buffer size
+*/
+void card_send_command(struct ft1000_usb *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)
+ usleep_range(900, 1100);
+
+ /* 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);
+ usleep_range(900, 1100);
+ ft1000_write_register(ft1000dev, FT1000_DB_DPRAM_TX,
+ FT1000_REG_DOORBELL);
+ usleep_range(900, 1100);
+
+ ft1000_read_register(ft1000dev, &temp, FT1000_REG_DOORBELL);
+
+#if 0
+ if ((temp & 0x0100) == 0)
+ DEBUG("card_send_command: Message sent\n");
+#endif
+
+}
+
+/* load or reload the DSP */
+int dsp_reload(struct ft1000_usb *ft1000dev)
+{
+ int 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 != 0)
+ return -EIO;
+
+ msleep(1000);
+
+ DEBUG("dsp_reload returned\n");
+
+ return 0;
+}
+
+/* call the Card Service function to reset the ASIC. */
+static void ft1000_reset_asic(struct net_device *dev)
+{
+ struct ft1000_info *info = netdev_priv(dev);
+ struct ft1000_usb *ft1000dev = info->priv;
+ 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);
+}
+
+static int ft1000_reset_card(struct net_device *dev)
+{
+ struct ft1000_info *info = netdev_priv(dev);
+ struct ft1000_usb *ft1000dev = info->priv;
+ u16 tempword;
+ struct prov_record *ptr;
+
+ DEBUG("ft1000_hw:ft1000_reset_card called.....\n");
+
+ ft1000dev->fCondResetPend = true;
+ info->CardReady = 0;
+ ft1000dev->fProvComplete = false;
+
+ /* 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;
+
+ ft1000dev->fCondResetPend = false;
+
+ return TRUE;
+}
+
+/* callback function when a urb is transmitted */
+static void ft1000_usb_transmit_complete(struct urb *urb)
+{
+
+ struct ft1000_usb *ft1000dev = urb->context;
+
+ if (urb->status)
+ pr_err("%s: TX status %d\n", ft1000dev->net->name, urb->status);
+
+ netif_wake_queue(ft1000dev->net);
+}
+
+/* take an ethernet packet and convert it to a Flarion
+* packet prior to sending it to the ASIC Downlink FIFO.
+*/
+static int ft1000_copy_down_pkt(struct net_device *netdev, u8 *packet, u16 len)
+{
+ struct ft1000_info *pInfo = netdev_priv(netdev);
+ struct ft1000_usb *pFt1000Dev = pInfo->priv;
+
+ 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;
+}
+
+/* transmit an ethernet packet
+* Parameters: skb - socket buffer to be sent
+* dev - network device
+*/
+static int ft1000_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ft1000_info *pInfo = netdev_priv(dev);
+ struct ft1000_usb *pFt1000Dev = pInfo->priv;
+ 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;
+}
+
+/* open the network driver */
+static int ft1000_open(struct net_device *dev)
+{
+ struct ft1000_info *pInfo = netdev_priv(dev);
+ struct ft1000_usb *pFt1000Dev = pInfo->priv;
+ struct timeval tv;
+
+ DEBUG("ft1000_open is called for card %d\n", pFt1000Dev->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);
+
+ return ft1000_submit_rx_urb(pInfo);
+}
+
+static struct net_device_stats *ft1000_netdev_stats(struct net_device *dev)
+{
+ struct ft1000_info *info = netdev_priv(dev);
+
+ return &(info->stats);
+}
+
+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,
+};
+
+/* initialize the network device */
+static int ft1000_reset(void *dev)
+{
+ ft1000_reset_card(dev);
+ return 0;
+}
+
+int init_ft1000_netdev(struct ft1000_usb *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];
+ u8 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 = kstrtou8(card_nr, 10, &gCardIndex);
+ if (ret_val) {
+ netdev_err(ft1000dev->net, "Can't parse netdev\n");
+ goto err_net;
+ }
+
+ ft1000dev->CardNumber = gCardIndex;
+ DEBUG("card number = %d\n", ft1000dev->CardNumber);
+ } else {
+ netdev_err(ft1000dev->net, "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->priv = ft1000dev;
+ pInfo->DrvErrNum = 0;
+ pInfo->registered = 1;
+ pInfo->ft1000_reset = ft1000_reset;
+ pInfo->mediastate = 0;
+ pInfo->fifo_cnt = 0;
+ ft1000dev->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;
+ ft1000dev->fAppMsgPend = false;
+ ft1000dev->fCondResetPend = false;
+ ft1000dev->usbboot = 0;
+ ft1000dev->dspalive = 0;
+ memset(&ft1000dev->tempbuf[0], 0, sizeof(ft1000dev->tempbuf));
+
+ INIT_LIST_HEAD(&pInfo->prov_list);
+
+ INIT_LIST_HEAD(&ft1000dev->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;
+}
+
+/* register the network driver */
+int reg_ft1000_netdev(struct ft1000_usb *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;
+}
+
+/* take a packet from the FIFO up link and
+* convert it into an ethernet packet and deliver it to the IP stack
+*/
+static int ft1000_copy_up_pkt(struct urb *urb)
+{
+ struct ft1000_info *info = urb->context;
+ struct ft1000_usb *ft1000dev = info->priv;
+ 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 0;
+ }
+ /* 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 -1;
+ }
+
+ 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 -1;
+ }
+
+ 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 0;
+}
+
+
+/* the receiving function of the network driver */
+static int ft1000_submit_rx_urb(struct ft1000_info *info)
+{
+ int result;
+ struct ft1000_usb *pFt1000Dev = info->priv;
+
+ 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;
+}
+
+/* close the network driver */
+int ft1000_close(struct net_device *net)
+{
+ struct ft1000_info *pInfo = netdev_priv(net);
+ struct ft1000_usb *ft1000dev = pInfo->priv;
+
+ 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;
+}
+
+/* check if the device is presently available on the system. */
+static int ft1000_chkcard(struct ft1000_usb *dev)
+{
+ u16 tempword;
+ int status;
+
+ if (dev->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;
+}
+
+/* read a message from the dpram area.
+* Input:
+* dev - network device structure
+* pbuffer - caller supply address to buffer
+*/
+static bool ft1000_receive_cmd(struct ft1000_usb *dev, u16 *pbuffer,
+ int maxsz)
+{
+ u16 size;
+ int 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_usb *dev = (struct ft1000_usb *)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;
+ int 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 -1;
+ }
+ 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);
+ }
+ usleep_range(9000, 11000);
+ }
+
+ DEBUG("DSP Provisioning List Entry finished\n");
+
+ msleep(100);
+
+ dev->fProvComplete = true;
+ info->CardReady = 1;
+
+ return 0;
+}
+
+static int ft1000_proc_drvmsg(struct ft1000_usb *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;
+ int status;
+ union {
+ u8 byte[2];
+ u16 wrd;
+ } convert;
+
+ char *cmdbuffer = kmalloc(1600, GFP_KERNEL);
+ if (!cmdbuffer)
+ return -ENOMEM;
+
+ 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 (dev->NetDevRegDone)
+ netif_wake_queue(dev->net);
+ info->mediastate = 1;
+ }
+ } else {
+ DEBUG("Media is down\n");
+ if (info->mediastate == 1) {
+ info->mediastate = 0;
+ if (dev->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) {
+ dev->fProvComplete = false;
+ status = ft1000_dsp_prov(dev);
+ if (status != 0)
+ goto out;
+ } else {
+ dev->fProvComplete = true;
+ 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 */
+ dev->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);
+ dev->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 */
+ dev->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;
+ }
+ dev->DrvMsgPend = 0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ status = 0;
+out:
+ kfree(cmdbuffer);
+ DEBUG("return from ft1000_proc_drvmsg\n");
+ return status;
+}
+
+/* Check which application has registered for dsp broadcast messages */
+static int dsp_broadcast_msg_id(struct ft1000_usb *dev)
+{
+ struct dpram_blk *pdpram_blk;
+ unsigned long flags;
+ int i;
+
+ for (i = 0; i < MAX_NUM_APP; i++) {
+ if ((dev->app_info[i].DspBCMsgFlag)
+ && (dev->app_info[i].fileobject)
+ && (dev->app_info[i].NumOfMsg
+ < MAX_MSG_LIMIT)) {
+ pdpram_blk = ft1000_get_buffer(&freercvpool);
+ if (pdpram_blk == NULL) {
+ DEBUG("Out of memory in free receive command pool\n");
+ dev->app_info[i].nRxMsgMiss++;
+ return -1;
+ }
+ if (ft1000_receive_cmd(dev, pdpram_blk->pbuffer,
+ MAX_CMD_SQSIZE)) {
+ /* Put message into the
+ * appropriate application block
+ */
+ dev->app_info[i].nRxMsg++;
+ spin_lock_irqsave(&free_buff_lock, flags);
+ list_add_tail(&pdpram_blk->list,
+ &dev->app_info[i] .app_sqlist);
+ dev->app_info[i].NumOfMsg++;
+ spin_unlock_irqrestore(&free_buff_lock, flags);
+ wake_up_interruptible(&dev->app_info[i]
+ .wait_dpram_msg);
+ } else {
+ dev->app_info[i].nRxMsgMiss++;
+ ft1000_free_buffer(pdpram_blk, &freercvpool);
+ DEBUG("pdpram_blk::ft1000_get_buffer NULL\n");
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int handle_misc_portid(struct ft1000_usb *dev)
+{
+ struct dpram_blk *pdpram_blk;
+ int i;
+
+ pdpram_blk = ft1000_get_buffer(&freercvpool);
+ if (pdpram_blk == NULL) {
+ DEBUG("Out of memory in free receive command pool\n");
+ return -1;
+ }
+ if (!ft1000_receive_cmd(dev, pdpram_blk->pbuffer, MAX_CMD_SQSIZE))
+ goto exit_failure;
+
+ /* Search for correct application block */
+ for (i = 0; i < MAX_NUM_APP; i++) {
+ if (dev->app_info[i].app_id == ((struct pseudo_hdr *)
+ pdpram_blk->pbuffer)->portdest)
+ break;
+ }
+ if (i == MAX_NUM_APP) {
+ DEBUG("FT1000:ft1000_parse_dpram_msg: No application matching id = %d\n", ((struct pseudo_hdr *)pdpram_blk->pbuffer)->portdest);
+ goto exit_failure;
+ } else if (dev->app_info[i].NumOfMsg > MAX_MSG_LIMIT) {
+ goto exit_failure;
+ } else {
+ dev->app_info[i].nRxMsg++;
+ /* Put message into the appropriate application block */
+ list_add_tail(&pdpram_blk->list, &dev->app_info[i].app_sqlist);
+ dev->app_info[i].NumOfMsg++;
+ }
+ return 0;
+
+exit_failure:
+ ft1000_free_buffer(pdpram_blk, &freercvpool);
+ return -1;
+}
+
+int ft1000_poll(void *dev_id)
+{
+ struct ft1000_usb *dev = (struct ft1000_usb *)dev_id;
+ struct ft1000_info *info = netdev_priv(dev->net);
+ u16 tempword;
+ int status;
+ u16 size;
+ int i;
+ u16 data;
+ u16 modulo;
+ u16 portid;
+
+ if (ft1000_chkcard(dev) == FALSE) {
+ DEBUG("ft1000_poll::ft1000_chkcard: failed\n");
+ return -1;
+ }
+ 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 != 0)
+ return status;
+ break;
+ case DSPBCMSGID:
+ status = dsp_broadcast_msg_id(dev);
+ break;
+ default:
+ status = handle_misc_portid(dev);
+ 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);
+ usleep_range(9000, 11000);
+ i++;
+ if (i == 100)
+ break;
+ }
+ if (i == 100) {
+ DEBUG("Unable to reset ASIC\n");
+ return 0;
+ }
+ usleep_range(9000, 11000);
+ /* 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);
+ usleep_range(9000, 11000);
+ } 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);
+ 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 (!dev->fAppMsgPend) {
+ /* 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 {
+ dev->fProvComplete = false;
+ dev->fCondResetPend = true;
+ }
+ ft1000_write_register(dev, FT1000_DB_COND_RESET,
+ FT1000_REG_DOORBELL);
+ }
+ }
+ return 0;
+}
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 00000000000..cb644a58d9f
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h
@@ -0,0 +1,123 @@
+/*
+*---------------------------------------------------------------------------
+* 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_
+
+struct IOCTL_GET_VER {
+ unsigned long drv_ver;
+} __packed;
+
+/* Data structure for Dsp statistics */
+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 */
+} __packed;
+
+/* Data structure for Dual Ported RAM messaging between Host and Dsp */
+struct IOCTL_DPRAM_BLK {
+ unsigned short total_len;
+ struct pseudo_hdr pseudohdr;
+ unsigned char buffer[1780];
+} __packed;
+
+struct IOCTL_DPRAM_COMMAND {
+ unsigned short extra;
+ struct IOCTL_DPRAM_BLK dpram_blk;
+} __packed;
+
+/*
+* 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, \
+ struct IOCTL_GET_DSP_STAT)
+#define IOCTL_FT1000_GET_VER _IOR(FT1000_MAGIC_CODE, IOCTL_GET_VER_CMD, \
+ struct IOCTL_GET_VER)
+#define IOCTL_FT1000_CONNECT _IO(FT1000_MAGIC_CODE, IOCTL_CONNECT)
+#define IOCTL_FT1000_DISCONNECT _IO(FT1000_MAGIC_CODE, IOCTL_DISCONNECT)
+#define IOCTL_FT1000_SET_DPRAM _IOW(FT1000_MAGIC_CODE, IOCTL_SET_DPRAM_CMD, \
+ struct IOCTL_DPRAM_BLK)
+#define IOCTL_FT1000_GET_DPRAM _IOR(FT1000_MAGIC_CODE, IOCTL_GET_DPRAM_CMD, \
+ struct IOCTL_DPRAM_BLK)
+#define IOCTL_FT1000_REGISTER _IOW(FT1000_MAGIC_CODE, IOCTL_REGISTER_CMD, \
+ 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 00000000000..e89b5d2c8e6
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_proc.c
@@ -0,0 +1,241 @@
+/*
+ * ft1000_proc.c - ft1000 proc interface
+ *
+ * Copyright (C) 2009-2010 Quintec
+ * (C) 2010 Open-nandra
+ * <marek.belisko@open-nandra.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/netdevice.h>
+
+
+#include "ft1000_usb.h"
+
+#define FT1000_PROC_DIR "ft1000"
+
+
+#define seq_putx(m, message, size, var) \
+ do { \
+ seq_printf(m, message); \
+ for (i = 0; i < (size - 1); i++) \
+ seq_printf(m, "%02x:", var[i]); \
+ seq_printf(m, "%02x\n", var[i]); \
+ } while (0)
+
+#define seq_putd(m, message, size, var) \
+ do { \
+ seq_printf(m, message); \
+ for (i = 0; i < (size - 1); i++) \
+ seq_printf(m, "%d.", var[i]); \
+ seq_printf(m, "%d\n", var[i]); \
+ } while (0)
+
+#define FTNET_PROC init_net.proc_net
+
+
+int ft1000_read_dpram16(struct ft1000_usb *ft1000dev, u16 indx,
+ u8 *buffer, u8 highlow);
+
+
+static int ft1000ReadProc(struct seq_file *m, void *v)
+{
+ static const char *status[] = {
+ "Idle (Disconnect)",
+ "Searching",
+ "Active (Connected)",
+ "Waiting for L2",
+ "Sleep",
+ "No Coverage",
+ "",
+ "",
+ };
+ static const char *signal[] = { "", "*", "**", "***", "****" };
+
+ struct net_device *dev = m->private;
+ struct ft1000_info *info = netdev_priv(dev);
+ int i;
+ unsigned short ledStat;
+ unsigned short conStat;
+ int strength;
+ int quality;
+ struct timeval tv;
+ time_t delta;
+
+ if (info->ProgConStat != 0xFF) {
+ ft1000_read_dpram16(info->priv, FT1000_MAG_DSP_LED,
+ (u8 *)&ledStat, FT1000_MAG_DSP_LED_INDX);
+ info->LedStat = ntohs(ledStat);
+
+ ft1000_read_dpram16(info->priv, 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;
+ }
+
+ seq_printf(m, "Connection Time: %02ld:%02ld:%02ld\n",
+ ((delta / 3600) % 24), ((delta / 60) % 60), (delta % 60));
+ seq_printf(m, "Connection Time[s]: %ld\n", delta);
+ seq_printf(m, "Asic ID: %s\n",
+ (info->AsicID) == ELECTRABUZZ_ID ? "ELECTRABUZZ ASIC" : "MAGNEMITE ASIC");
+ seq_putx(m, "SKU: ", SKUSZ, info->Sku);
+ seq_putx(m, "EUI64: ", EUISZ, info->eui64);
+ seq_putd(m, "DSP version number: ", DSPVERSZ, info->DspVer);
+ seq_putx(m, "Hardware Serial Number: ", HWSERNUMSZ, info->HwSerNum);
+ seq_putx(m, "Caliberation Version: ", CALVERSZ, info->RfCalVer);
+ seq_putd(m, "Caliberation Date: ", CALDATESZ, info->RfCalDate);
+ seq_printf(m, "Media State: %s\n", (info->mediastate) ? "link" : "no link");
+ seq_printf(m, "Connection Status: %s\n", status[info->ConStat & 0x7]);
+ seq_printf(m, "RX packets: %ld\n", info->stats.rx_packets);
+ seq_printf(m, "TX packets: %ld\n", info->stats.tx_packets);
+ seq_printf(m, "RX bytes: %ld\n", info->stats.rx_bytes);
+ seq_printf(m, "TX bytes: %ld\n", info->stats.tx_bytes);
+ seq_printf(m, "Signal Strength: %s\n", signal[strength]);
+ seq_printf(m, "Signal Quality: %s\n", signal[quality]);
+ return 0;
+}
+
+/*
+ * seq_file wrappers for procfile show routines.
+ */
+static int ft1000_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ft1000ReadProc, PDE_DATA(inode));
+}
+
+static const struct file_operations ft1000_proc_fops = {
+ .open = ft1000_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int
+ft1000NotifyProc(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_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 =
+ proc_create_data(dev->name, 0644, info->ft1000_proc_dir,
+ &ft1000_proc_fops, 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 = -EINVAL;
+
+ info = netdev_priv(dev);
+
+ info->ft1000_proc_dir = proc_mkdir(FT1000_PROC_DIR, FTNET_PROC);
+ if (info->ft1000_proc_dir == NULL) {
+ netdev_warn(dev, "Unable to create %s dir.\n",
+ FT1000_PROC_DIR);
+ goto fail;
+ }
+
+ ft1000_proc_file =
+ proc_create_data(dev->name, 0644, info->ft1000_proc_dir,
+ &ft1000_proc_fops, dev);
+
+ if (!ft1000_proc_file) {
+ netdev_warn(dev, "Unable to create /proc entry.\n");
+ 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 00000000000..0a2544c7829
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.c
@@ -0,0 +1,268 @@
+/*=====================================================
+ * CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved.
+ *
+ *
+ * This file is part of Express Card USB Driver
+ *
+ * $Id:
+ *====================================================
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/firmware.h>
+#include "ft1000_usb.h"
+
+#include <linux/kthread.h>
+
+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;
+static int ft1000_poll_thread(void *arg)
+{
+ int ret;
+
+ while (!kthread_should_stop()) {
+ msleep(10);
+ if (!gPollingfailed) {
+ ret = ft1000_poll(arg);
+ if (ret != 0) {
+ DEBUG("ft1000_poll_thread: polling failed\n");
+ gPollingfailed = true;
+ }
+ }
+ }
+ return 0;
+}
+
+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_usb *ft1000dev;
+ struct ft1000_info *pft1000info = NULL;
+ const struct firmware *dsp_fw;
+
+ ft1000dev = kzalloc(sizeof(struct ft1000_usb), GFP_KERNEL);
+ if (!ft1000dev)
+ return -ENOMEM;
+
+ 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_KERNEL);
+ ft1000dev->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ft1000dev->tx_urb || !ft1000dev->rx_urb) {
+ ret = -ENOMEM;
+ goto err_fw;
+ }
+
+ 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) {
+ pr_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) {
+ pr_err("Problem with DSP image loading\n");
+ goto err_load;
+ }
+
+ gPollingfailed = false;
+ ft1000dev->pPollThread =
+ kthread_run(ft1000_poll_thread, ft1000dev, "ft1000_poll");
+
+ if (IS_ERR(ft1000dev->pPollThread)) {
+ ret = PTR_ERR(ft1000dev->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;
+
+ ft1000dev->NetDevRegDone = 1;
+
+ return 0;
+
+err_proc:
+ unregister_netdev(ft1000dev->net);
+ free_netdev(ft1000dev->net);
+err_thread:
+ kthread_stop(ft1000dev->pPollThread);
+err_load:
+ kfree(pFileStart);
+err_fw:
+ usb_free_urb(ft1000dev->rx_urb);
+ usb_free_urb(ft1000dev->tx_urb);
+ kfree(ft1000dev);
+ return ret;
+}
+
+static void ft1000_disconnect(struct usb_interface *interface)
+{
+ struct ft1000_info *pft1000info;
+ struct ft1000_usb *ft1000dev;
+
+ DEBUG("ft1000_disconnect is called\n");
+
+ pft1000info = (struct ft1000_info *) usb_get_intfdata(interface);
+ DEBUG("In disconnect pft1000info=%p\n", pft1000info);
+
+ if (pft1000info) {
+ ft1000dev = pft1000info->priv;
+ ft1000_cleanup_proc(pft1000info);
+ if (ft1000dev->pPollThread)
+ kthread_stop(ft1000dev->pPollThread);
+
+ DEBUG("ft1000_disconnect: threads are terminated\n");
+
+ if (ft1000dev->net) {
+ DEBUG("ft1000_disconnect: destroy char driver\n");
+ ft1000_destroy_dev(ft1000dev->net);
+ unregister_netdev(ft1000dev->net);
+ DEBUG
+ ("ft1000_disconnect: network device unregistered\n");
+ free_netdev(ft1000dev->net);
+
+ }
+
+ usb_free_urb(ft1000dev->rx_urb);
+ usb_free_urb(ft1000dev->tx_urb);
+
+ DEBUG("ft1000_disconnect: urb freed\n");
+
+ kfree(ft1000dev);
+ }
+ 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 00000000000..2d4b02e2382
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.h
@@ -0,0 +1,155 @@
+#ifndef _FT1000_USB_H_
+#define _FT1000_USB_H_
+
+#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
+
+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
+ */
+} __packed;
+
+#define DEBUG(args...) pr_info(args)
+
+#define FALSE 0
+#define TRUE 1
+
+#define FT1000_STATUS_CLOSING 0x01
+
+#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_debug_dirs {
+ struct list_head list;
+ struct dentry *dent;
+ struct dentry *file;
+ int int_number;
+};
+
+struct ft1000_usb {
+ 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 task_struct *pPollThread;
+ unsigned char fcodeldr;
+ unsigned char bootmode;
+ unsigned char usbboot;
+ unsigned short dspalive;
+ bool fProvComplete;
+ bool fCondResetPend;
+ bool fAppMsgPend;
+ int DeviceCreated;
+ int NetDevRegDone;
+ u8 CardNumber;
+ u8 DeviceName[15];
+ struct ft1000_debug_dirs nodes;
+ spinlock_t fifo_lock;
+ int appcnt;
+ struct app_info_block app_info[MAX_NUM_APP];
+ u16 DrvMsgPend;
+ unsigned short tempbuf[32];
+} __packed;
+
+
+struct dpram_blk {
+ struct list_head list;
+ u16 *pbuffer;
+} __packed;
+
+int ft1000_read_register(struct ft1000_usb *ft1000dev,
+ u16 *Data, u16 nRegIndx);
+int ft1000_write_register(struct ft1000_usb *ft1000dev,
+ u16 value, u16 nRegIndx);
+int ft1000_read_dpram32(struct ft1000_usb *ft1000dev,
+ u16 indx, u8 *buffer, u16 cnt);
+int ft1000_write_dpram32(struct ft1000_usb *ft1000dev,
+ u16 indx, u8 *buffer, u16 cnt);
+int ft1000_read_dpram16(struct ft1000_usb *ft1000dev,
+ u16 indx, u8 *buffer, u8 highlow);
+int ft1000_write_dpram16(struct ft1000_usb *ft1000dev,
+ u16 indx, u16 value, u8 highlow);
+int fix_ft1000_read_dpram32(struct ft1000_usb *ft1000dev,
+ u16 indx, u8 *buffer);
+int fix_ft1000_write_dpram32(struct ft1000_usb *ft1000dev,
+ u16 indx, u8 *buffer);
+
+extern void *pFileStart;
+extern size_t FileLength;
+extern int numofmsgbuf;
+
+int ft1000_close(struct net_device *dev);
+int scram_dnldr(struct ft1000_usb *ft1000dev, void *pFileStart,
+ u32 FileLength);
+
+extern struct list_head freercvpool;
+
+/* lock to arbitrate free buffer list for receive command data */
+extern spinlock_t free_buff_lock;
+
+int ft1000_create_dev(struct ft1000_usb *dev);
+void ft1000_destroy_dev(struct net_device *dev);
+extern void card_send_command(struct ft1000_usb *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_usb *ft1000dev);
+int init_ft1000_netdev(struct ft1000_usb *ft1000dev);
+struct usb_interface;
+int reg_ft1000_netdev(struct ft1000_usb *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 /* _FT1000_USB_H_ */
diff --git a/drivers/staging/ft1000/ft1000-usb/ft3000.img b/drivers/staging/ft1000/ft1000-usb/ft3000.img
new file mode 100644
index 00000000000..7bef6bd3680
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000-usb/ft3000.img
Binary files differ
diff --git a/drivers/staging/ft1000/ft1000.h b/drivers/staging/ft1000/ft1000.h
new file mode 100644
index 00000000000..db57430f3b9
--- /dev/null
+++ b/drivers/staging/ft1000/ft1000.h
@@ -0,0 +1,368 @@
+/*
+ * 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;
+};
+
+struct ft1000_info {
+ void *priv;
+ struct net_device_stats stats;
+ u16 DrvErrNum;
+ u16 AsicID;
+ int CardReady;
+ int registered;
+ int mediastate;
+ 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 */
+ u8 ProductMode[MODESZ];
+ u8 RfCalVer[CALVERSZ];
+ u8 RfCalDate[CALDATESZ];
+ u16 DSP_TIME[4];
+ u16 LedStat;
+ u16 ConStat;
+ u16 ProgConStat;
+ struct list_head prov_list;
+ u16 DSPInfoBlklen;
+ int (*ft1000_reset)(void *);
+ 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 *ft1000_proc_dir;
+ char netdevname[IFNAMSIZ];
+};
diff --git a/drivers/staging/fwserial/Kconfig b/drivers/staging/fwserial/Kconfig
new file mode 100644
index 00000000000..9c7c9267d52
--- /dev/null
+++ b/drivers/staging/fwserial/Kconfig
@@ -0,0 +1,31 @@
+config FIREWIRE_SERIAL
+ tristate "TTY over Firewire"
+ depends on FIREWIRE && TTY
+ help
+ This enables TTY over IEEE 1394, providing high-speed serial
+ connectivity to cabled peers. This driver implements a
+ ad-hoc transport protocol and is currently limited to
+ Linux-to-Linux communication.
+
+ To compile this driver as a module, say M here: the module will
+ be called firewire-serial.
+
+if FIREWIRE_SERIAL
+
+config FWTTY_MAX_TOTAL_PORTS
+ int "Maximum number of serial ports supported"
+ default "64"
+ help
+ Set this to the maximum number of serial ports you want the
+ firewire-serial driver to support.
+
+config FWTTY_MAX_CARD_PORTS
+ int "Maximum number of serial ports supported per adapter"
+ range 0 FWTTY_MAX_TOTAL_PORTS
+ default "32"
+ help
+ Set this to the maximum number of serial ports each firewire
+ adapter supports. The actual number of serial ports registered
+ is set with the module parameter "ttys".
+
+endif
diff --git a/drivers/staging/fwserial/Makefile b/drivers/staging/fwserial/Makefile
new file mode 100644
index 00000000000..2170869a19b
--- /dev/null
+++ b/drivers/staging/fwserial/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_FIREWIRE_SERIAL) += firewire-serial.o
+firewire-serial-objs := fwserial.o dma_fifo.o
diff --git a/drivers/staging/fwserial/TODO b/drivers/staging/fwserial/TODO
new file mode 100644
index 00000000000..382a7959407
--- /dev/null
+++ b/drivers/staging/fwserial/TODO
@@ -0,0 +1,14 @@
+TODOs prior to this driver moving out of staging
+------------------------------------------------
+1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR
+ - I/O is handled asynchronously which presents some issues when error
+ conditions occur.
+2. Implement _robust_ console on top of this. The existing prototype console
+ driver is not ready for the big leagues yet.
+3. Expose means of controlling attach/detach of peers via sysfs. Include
+ GUID-to-port matching/whitelist/blacklist.
+
+-- Issues with firewire stack --
+1. This driver uses the same unregistered vendor id that the firewire core does
+ (0xd00d1e). Perhaps this could be exposed as a define in
+ firewire.h?
diff --git a/drivers/staging/fwserial/dma_fifo.c b/drivers/staging/fwserial/dma_fifo.c
new file mode 100644
index 00000000000..02790624959
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.c
@@ -0,0 +1,303 @@
+/*
+ * DMA-able FIFO implementation
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/bug.h>
+
+#include "dma_fifo.h"
+
+#ifdef DEBUG_TRACING
+#define df_trace(s, args...) pr_debug(s, ##args)
+#else
+#define df_trace(s, args...)
+#endif
+
+#define FAIL(fifo, condition, format...) ({ \
+ fifo->corrupt = !!(condition); \
+ WARN(fifo->corrupt, format); \
+})
+
+/*
+ * private helper fn to determine if check is in open interval (lo,hi)
+ */
+static bool addr_check(unsigned check, unsigned lo, unsigned hi)
+{
+ return check - (lo + 1) < (hi - 1) - lo;
+}
+
+/**
+ * dma_fifo_init: initialize the fifo to a valid but inoperative state
+ * @fifo: address of in-place "struct dma_fifo" object
+ */
+void dma_fifo_init(struct dma_fifo *fifo)
+{
+ memset(fifo, 0, sizeof(*fifo));
+ INIT_LIST_HEAD(&fifo->pending);
+}
+
+/**
+ * dma_fifo_alloc - initialize and allocate dma_fifo
+ * @fifo: address of in-place "struct dma_fifo" object
+ * @size: 'apparent' size, in bytes, of fifo
+ * @align: dma alignment to maintain (should be at least cpu cache alignment),
+ * must be power of 2
+ * @tx_limit: maximum # of bytes transmissable per dma (rounded down to
+ * multiple of alignment, but at least align size)
+ * @open_limit: maximum # of outstanding dma transactions allowed
+ * @gfp_mask: get_free_pages mask, passed to kmalloc()
+ *
+ * The 'apparent' size will be rounded up to next greater aligned size.
+ * Returns 0 if no error, otherwise an error code
+ */
+int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+ int tx_limit, int open_limit, gfp_t gfp_mask)
+{
+ int capacity;
+
+ if (!is_power_of_2(align) || size < 0)
+ return -EINVAL;
+
+ size = round_up(size, align);
+ capacity = size + align * open_limit + align * DMA_FIFO_GUARD;
+ fifo->data = kmalloc(capacity, gfp_mask);
+ if (!fifo->data)
+ return -ENOMEM;
+
+ fifo->in = 0;
+ fifo->out = 0;
+ fifo->done = 0;
+ fifo->size = size;
+ fifo->avail = size;
+ fifo->align = align;
+ fifo->tx_limit = max_t(int, round_down(tx_limit, align), align);
+ fifo->open = 0;
+ fifo->open_limit = open_limit;
+ fifo->guard = size + align * open_limit;
+ fifo->capacity = capacity;
+ fifo->corrupt = 0;
+
+ return 0;
+}
+
+/**
+ * dma_fifo_free - frees the fifo
+ * @fifo: address of in-place "struct dma_fifo" to free
+ *
+ * Also reinits the fifo to a valid but inoperative state. This
+ * allows the fifo to be reused with a different target requiring
+ * different fifo parameters.
+ */
+void dma_fifo_free(struct dma_fifo *fifo)
+{
+ struct dma_pending *pending, *next;
+
+ if (fifo->data == NULL)
+ return;
+
+ list_for_each_entry_safe(pending, next, &fifo->pending, link)
+ list_del_init(&pending->link);
+ kfree(fifo->data);
+ fifo->data = NULL;
+}
+
+/**
+ * dma_fifo_reset - dumps the fifo contents and reinits for reuse
+ * @fifo: address of in-place "struct dma_fifo" to reset
+ */
+void dma_fifo_reset(struct dma_fifo *fifo)
+{
+ struct dma_pending *pending, *next;
+
+ if (fifo->data == NULL)
+ return;
+
+ list_for_each_entry_safe(pending, next, &fifo->pending, link)
+ list_del_init(&pending->link);
+ fifo->in = 0;
+ fifo->out = 0;
+ fifo->done = 0;
+ fifo->avail = fifo->size;
+ fifo->open = 0;
+ fifo->corrupt = 0;
+}
+
+/**
+ * dma_fifo_in - copies data into the fifo
+ * @fifo: address of in-place "struct dma_fifo" to write to
+ * @src: buffer to copy from
+ * @n: # of bytes to copy
+ *
+ * Returns the # of bytes actually copied, which can be less than requested if
+ * the fifo becomes full. If < 0, return is error code.
+ */
+int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n)
+{
+ int ofs, l;
+
+ if (fifo->data == NULL)
+ return -ENOENT;
+ if (fifo->corrupt)
+ return -ENXIO;
+
+ if (n > fifo->avail)
+ n = fifo->avail;
+ if (n <= 0)
+ return 0;
+
+ ofs = fifo->in % fifo->capacity;
+ l = min(n, fifo->capacity - ofs);
+ memcpy(fifo->data + ofs, src, l);
+ memcpy(fifo->data, src + l, n - l);
+
+ if (FAIL(fifo, addr_check(fifo->done, fifo->in, fifo->in + n) ||
+ fifo->avail < n,
+ "fifo corrupt: in:%u out:%u done:%u n:%d avail:%d",
+ fifo->in, fifo->out, fifo->done, n, fifo->avail))
+ return -ENXIO;
+
+ fifo->in += n;
+ fifo->avail -= n;
+
+ df_trace("in:%u out:%u done:%u n:%d avail:%d", fifo->in, fifo->out,
+ fifo->done, n, fifo->avail);
+
+ return n;
+}
+
+/**
+ * dma_fifo_out_pend - gets address/len of next avail read and marks as pended
+ * @fifo: address of in-place "struct dma_fifo" to read from
+ * @pended: address of structure to fill with read address/len
+ * The data/len fields will be NULL/0 if no dma is pended.
+ *
+ * Returns the # of used bytes remaining in fifo (ie, if > 0, more data
+ * remains in the fifo that was not pended). If < 0, return is error code.
+ */
+int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended)
+{
+ unsigned len, n, ofs, l, limit;
+
+ if (fifo->data == NULL)
+ return -ENOENT;
+ if (fifo->corrupt)
+ return -ENXIO;
+
+ pended->len = 0;
+ pended->data = NULL;
+ pended->out = fifo->out;
+
+ len = fifo->in - fifo->out;
+ if (!len)
+ return -ENODATA;
+ if (fifo->open == fifo->open_limit)
+ return -EAGAIN;
+
+ n = len;
+ ofs = fifo->out % fifo->capacity;
+ l = fifo->capacity - ofs;
+ limit = min_t(unsigned, l, fifo->tx_limit);
+ if (n > limit) {
+ n = limit;
+ fifo->out += limit;
+ } else if (ofs + n > fifo->guard) {
+ fifo->out += l;
+ fifo->in = fifo->out;
+ } else {
+ fifo->out += round_up(n, fifo->align);
+ fifo->in = fifo->out;
+ }
+
+ df_trace("in: %u out: %u done: %u n: %d len: %u avail: %d", fifo->in,
+ fifo->out, fifo->done, n, len, fifo->avail);
+
+ pended->len = n;
+ pended->data = fifo->data + ofs;
+ pended->next = fifo->out;
+ list_add_tail(&pended->link, &fifo->pending);
+ ++fifo->open;
+
+ if (FAIL(fifo, fifo->open > fifo->open_limit,
+ "past open limit:%d (limit:%d)",
+ fifo->open, fifo->open_limit))
+ return -ENXIO;
+ if (FAIL(fifo, fifo->out & (fifo->align - 1),
+ "fifo out unaligned:%u (align:%u)",
+ fifo->out, fifo->align))
+ return -ENXIO;
+
+ return len - n;
+}
+
+/**
+ * dma_fifo_out_complete - marks pended dma as completed
+ * @fifo: address of in-place "struct dma_fifo" which was read from
+ * @complete: address of structure for previously pended dma to mark completed
+ */
+int dma_fifo_out_complete(struct dma_fifo *fifo, struct dma_pending *complete)
+{
+ struct dma_pending *pending, *next, *tmp;
+
+ if (fifo->data == NULL)
+ return -ENOENT;
+ if (fifo->corrupt)
+ return -ENXIO;
+ if (list_empty(&fifo->pending) && fifo->open == 0)
+ return -EINVAL;
+
+ if (FAIL(fifo, list_empty(&fifo->pending) != (fifo->open == 0),
+ "pending list disagrees with open count:%d",
+ fifo->open))
+ return -ENXIO;
+
+ tmp = complete->data;
+ *tmp = *complete;
+ list_replace(&complete->link, &tmp->link);
+ dp_mark_completed(tmp);
+
+ /* Only update the fifo in the original pended order */
+ list_for_each_entry_safe(pending, next, &fifo->pending, link) {
+ if (!dp_is_completed(pending)) {
+ df_trace("still pending: saved out: %u len: %d",
+ pending->out, pending->len);
+ break;
+ }
+
+ if (FAIL(fifo, pending->out != fifo->done ||
+ addr_check(fifo->in, fifo->done, pending->next),
+ "in:%u out:%u done:%u saved:%u next:%u",
+ fifo->in, fifo->out, fifo->done, pending->out,
+ pending->next))
+ return -ENXIO;
+
+ list_del_init(&pending->link);
+ fifo->done = pending->next;
+ fifo->avail += pending->len;
+ --fifo->open;
+
+ df_trace("in: %u out: %u done: %u len: %u avail: %d", fifo->in,
+ fifo->out, fifo->done, pending->len, fifo->avail);
+ }
+
+ if (FAIL(fifo, fifo->open < 0, "open dma:%d < 0", fifo->open))
+ return -ENXIO;
+ if (FAIL(fifo, fifo->avail > fifo->size, "fifo avail:%d > size:%d",
+ fifo->avail, fifo->size))
+ return -ENXIO;
+
+ return 0;
+}
diff --git a/drivers/staging/fwserial/dma_fifo.h b/drivers/staging/fwserial/dma_fifo.h
new file mode 100644
index 00000000000..410988224f8
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.h
@@ -0,0 +1,126 @@
+/*
+ * DMA-able FIFO interface
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.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.
+ */
+
+#ifndef _DMA_FIFO_H_
+#define _DMA_FIFO_H_
+
+/**
+ * The design basis for the DMA FIFO is to provide an output side that
+ * complies with the streaming DMA API design that can be DMA'd from directly
+ * (without additional copying), coupled with an input side that maintains a
+ * logically consistent 'apparent' size (ie, bytes in + bytes avail is static
+ * for the lifetime of the FIFO).
+ *
+ * DMA output transactions originate on a cache line boundary and can be
+ * variably-sized. DMA output transactions can be retired out-of-order but
+ * the FIFO will only advance the output in the original input sequence.
+ * This means the FIFO will eventually stall if a transaction is never retired.
+ *
+ * Chunking the output side into cache line multiples means that some FIFO
+ * memory is unused. For example, if all the avail input has been pended out,
+ * then the in and out markers are re-aligned to the next cache line.
+ * The maximum possible waste is
+ * (cache line alignment - 1) * (max outstanding dma transactions)
+ * This potential waste requires additional hidden capacity within the FIFO
+ * to be able to accept input while the 'apparent' size has not been reached.
+ *
+ * Additional cache lines (ie, guard area) are used to minimize DMA
+ * fragmentation when wrapping at the end of the FIFO. Input is allowed into the
+ * guard area, but the in and out FIFO markers are wrapped when DMA is pended.
+ */
+
+#define DMA_FIFO_GUARD 3 /* # of cache lines to reserve for the guard area */
+
+struct dma_fifo {
+ unsigned in;
+ unsigned out; /* updated when dma is pended */
+ unsigned done; /* updated upon dma completion */
+ struct {
+ unsigned corrupt:1;
+ };
+ int size; /* 'apparent' size of fifo */
+ int guard; /* ofs of guard area */
+ int capacity; /* size + reserved */
+ int avail; /* # of unused bytes in fifo */
+ unsigned align; /* must be power of 2 */
+ int tx_limit; /* max # of bytes per dma transaction */
+ int open_limit; /* max # of outstanding allowed */
+ int open; /* # of outstanding dma transactions */
+ struct list_head pending; /* fifo markers for outstanding dma */
+ void *data;
+};
+
+struct dma_pending {
+ struct list_head link;
+ void *data;
+ unsigned len;
+ unsigned next;
+ unsigned out;
+};
+
+static inline void dp_mark_completed(struct dma_pending *dp)
+{
+ dp->data += 1;
+}
+
+static inline bool dp_is_completed(struct dma_pending *dp)
+{
+ return (unsigned long)dp->data & 1UL;
+}
+
+void dma_fifo_init(struct dma_fifo *fifo);
+int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+ int tx_limit, int open_limit, gfp_t gfp_mask);
+void dma_fifo_free(struct dma_fifo *fifo);
+void dma_fifo_reset(struct dma_fifo *fifo);
+int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n);
+int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended);
+int dma_fifo_out_complete(struct dma_fifo *fifo,
+ struct dma_pending *complete);
+
+/* returns the # of used bytes in the fifo */
+static inline int dma_fifo_level(struct dma_fifo *fifo)
+{
+ return fifo->size - fifo->avail;
+}
+
+/* returns the # of bytes ready for output in the fifo */
+static inline int dma_fifo_out_level(struct dma_fifo *fifo)
+{
+ return fifo->in - fifo->out;
+}
+
+/* returns the # of unused bytes in the fifo */
+static inline int dma_fifo_avail(struct dma_fifo *fifo)
+{
+ return fifo->avail;
+}
+
+/* returns true if fifo has max # of outstanding dmas */
+static inline bool dma_fifo_busy(struct dma_fifo *fifo)
+{
+ return fifo->open == fifo->open_limit;
+}
+
+/* changes the max size of dma returned from dma_fifo_out_pend() */
+static inline int dma_fifo_change_tx_limit(struct dma_fifo *fifo, int tx_limit)
+{
+ tx_limit = round_down(tx_limit, fifo->align);
+ fifo->tx_limit = max_t(int, tx_limit, fifo->align);
+ return 0;
+}
+
+#endif /* _DMA_FIFO_H_ */
diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c
new file mode 100644
index 00000000000..384758b11e3
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.c
@@ -0,0 +1,2955 @@
+/*
+ * FireWire Serial driver
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/rculist.h>
+#include <linux/workqueue.h>
+#include <linux/ratelimit.h>
+#include <linux/bug.h>
+#include <linux/uaccess.h>
+
+#include "fwserial.h"
+
+#define be32_to_u64(hi, lo) ((u64)be32_to_cpu(hi) << 32 | be32_to_cpu(lo))
+
+#define LINUX_VENDOR_ID 0xd00d1eU /* same id used in card root directory */
+#define FWSERIAL_VERSION 0x00e81cU /* must be unique within LINUX_VENDOR_ID */
+
+/* configurable options */
+static int num_ttys = 4; /* # of std ttys to create per fw_card */
+ /* - doubles as loopback port index */
+static bool auto_connect = true; /* try to VIRT_CABLE to every peer */
+static bool create_loop_dev = true; /* create a loopback device for each card */
+
+module_param_named(ttys, num_ttys, int, S_IRUGO | S_IWUSR);
+module_param_named(auto, auto_connect, bool, S_IRUGO | S_IWUSR);
+module_param_named(loop, create_loop_dev, bool, S_IRUGO | S_IWUSR);
+
+/*
+ * Threshold below which the tty is woken for writing
+ * - should be equal to WAKEUP_CHARS in drivers/tty/n_tty.c because
+ * even if the writer is woken, n_tty_poll() won't set POLLOUT until
+ * our fifo is below this level
+ */
+#define WAKEUP_CHARS 256
+
+/**
+ * fwserial_list: list of every fw_serial created for each fw_card
+ * See discussion in fwserial_probe.
+ */
+static LIST_HEAD(fwserial_list);
+static DEFINE_MUTEX(fwserial_list_mutex);
+
+/**
+ * port_table: array of tty ports allocated to each fw_card
+ *
+ * tty ports are allocated during probe when an fw_serial is first
+ * created for a given fw_card. Ports are allocated in a contiguous block,
+ * each block consisting of 'num_ports' ports.
+ */
+static struct fwtty_port *port_table[MAX_TOTAL_PORTS];
+static DEFINE_MUTEX(port_table_lock);
+static bool port_table_corrupt;
+#define FWTTY_INVALID_INDEX MAX_TOTAL_PORTS
+
+#define loop_idx(port) (((port)->index) / num_ports)
+#define table_idx(loop) ((loop) * num_ports + num_ttys)
+
+/* total # of tty ports created per fw_card */
+static int num_ports;
+
+/* slab used as pool for struct fwtty_transactions */
+static struct kmem_cache *fwtty_txn_cache;
+
+struct tty_driver *fwtty_driver;
+static struct tty_driver *fwloop_driver;
+
+static struct dentry *fwserial_debugfs;
+
+struct fwtty_transaction;
+typedef void (*fwtty_transaction_cb)(struct fw_card *card, int rcode,
+ void *data, size_t length,
+ struct fwtty_transaction *txn);
+
+struct fwtty_transaction {
+ struct fw_transaction fw_txn;
+ fwtty_transaction_cb callback;
+ struct fwtty_port *port;
+ union {
+ struct dma_pending dma_pended;
+ };
+};
+
+#define to_device(a, b) (a->b)
+#define fwtty_err(p, fmt, ...) \
+ dev_err(to_device(p, device), fmt, ##__VA_ARGS__)
+#define fwtty_info(p, fmt, ...) \
+ dev_info(to_device(p, device), fmt, ##__VA_ARGS__)
+#define fwtty_notice(p, fmt, ...) \
+ dev_notice(to_device(p, device), fmt, ##__VA_ARGS__)
+#define fwtty_dbg(p, fmt, ...) \
+ dev_dbg(to_device(p, device), "%s: " fmt, __func__, ##__VA_ARGS__)
+#define fwtty_err_ratelimited(p, fmt, ...) \
+ dev_err_ratelimited(to_device(p, device), fmt, ##__VA_ARGS__)
+
+#ifdef DEBUG
+static inline void debug_short_write(struct fwtty_port *port, int c, int n)
+{
+ int avail;
+
+ if (n < c) {
+ spin_lock_bh(&port->lock);
+ avail = dma_fifo_avail(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+ fwtty_dbg(port, "short write: avail:%d req:%d wrote:%d\n",
+ avail, c, n);
+ }
+}
+#else
+#define debug_short_write(port, c, n)
+#endif
+
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+ int generation, int id);
+
+#ifdef FWTTY_PROFILING
+
+static void fwtty_profile_fifo(struct fwtty_port *port, unsigned *stat)
+{
+ spin_lock_bh(&port->lock);
+ fwtty_profile_data(stat, dma_fifo_avail(&port->tx_fifo));
+ spin_unlock_bh(&port->lock);
+}
+
+static void fwtty_dump_profile(struct seq_file *m, struct stats *stats)
+{
+ /* for each stat, print sum of 0 to 2^k, then individually */
+ int k = 4;
+ unsigned sum;
+ int j;
+ char t[10];
+
+ snprintf(t, 10, "< %d", 1 << k);
+ seq_printf(m, "\n%14s %6s", " ", t);
+ for (j = k + 1; j < DISTRIBUTION_MAX_INDEX; ++j)
+ seq_printf(m, "%6d", 1 << j);
+
+ ++k;
+ for (j = 0, sum = 0; j <= k; ++j)
+ sum += stats->reads[j];
+ seq_printf(m, "\n%14s: %6d", "reads", sum);
+ for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+ seq_printf(m, "%6d", stats->reads[j]);
+
+ for (j = 0, sum = 0; j <= k; ++j)
+ sum += stats->writes[j];
+ seq_printf(m, "\n%14s: %6d", "writes", sum);
+ for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+ seq_printf(m, "%6d", stats->writes[j]);
+
+ for (j = 0, sum = 0; j <= k; ++j)
+ sum += stats->txns[j];
+ seq_printf(m, "\n%14s: %6d", "txns", sum);
+ for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+ seq_printf(m, "%6d", stats->txns[j]);
+
+ for (j = 0, sum = 0; j <= k; ++j)
+ sum += stats->unthrottle[j];
+ seq_printf(m, "\n%14s: %6d", "avail @ unthr", sum);
+ for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+ seq_printf(m, "%6d", stats->unthrottle[j]);
+}
+
+#else
+#define fwtty_profile_fifo(port, stat)
+#define fwtty_dump_profile(m, stats)
+#endif
+
+/*
+ * Returns the max receive packet size for the given node
+ * Devices which are OHCI v1.0/ v1.1/ v1.2-draft or RFC 2734 compliant
+ * are required by specification to support max_rec of 8 (512 bytes) or more.
+ */
+static inline int device_max_receive(struct fw_device *fw_device)
+{
+ /* see IEEE 1394-2008 table 8-8 */
+ return min(2 << fw_device->max_rec, 4096);
+}
+
+static void fwtty_log_tx_error(struct fwtty_port *port, int rcode)
+{
+ switch (rcode) {
+ case RCODE_SEND_ERROR:
+ fwtty_err_ratelimited(port, "card busy\n");
+ break;
+ case RCODE_ADDRESS_ERROR:
+ fwtty_err_ratelimited(port, "bad unit addr or write length\n");
+ break;
+ case RCODE_DATA_ERROR:
+ fwtty_err_ratelimited(port, "failed rx\n");
+ break;
+ case RCODE_NO_ACK:
+ fwtty_err_ratelimited(port, "missing ack\n");
+ break;
+ case RCODE_BUSY:
+ fwtty_err_ratelimited(port, "remote busy\n");
+ break;
+ default:
+ fwtty_err_ratelimited(port, "failed tx: %d\n", rcode);
+ }
+}
+
+static void fwtty_txn_constructor(void *this)
+{
+ struct fwtty_transaction *txn = this;
+
+ init_timer(&txn->fw_txn.split_timeout_timer);
+}
+
+static void fwtty_common_callback(struct fw_card *card, int rcode,
+ void *payload, size_t len, void *cb_data)
+{
+ struct fwtty_transaction *txn = cb_data;
+ struct fwtty_port *port = txn->port;
+
+ if (port && rcode != RCODE_COMPLETE)
+ fwtty_log_tx_error(port, rcode);
+ if (txn->callback)
+ txn->callback(card, rcode, payload, len, txn);
+ kmem_cache_free(fwtty_txn_cache, txn);
+}
+
+static int fwtty_send_data_async(struct fwtty_peer *peer, int tcode,
+ unsigned long long addr, void *payload,
+ size_t len, fwtty_transaction_cb callback,
+ struct fwtty_port *port)
+{
+ struct fwtty_transaction *txn;
+ int generation;
+
+ txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+ if (!txn)
+ return -ENOMEM;
+
+ txn->callback = callback;
+ txn->port = port;
+
+ generation = peer->generation;
+ smp_rmb();
+ fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+ peer->node_id, generation, peer->speed, addr, payload,
+ len, fwtty_common_callback, txn);
+ return 0;
+}
+
+static void fwtty_send_txn_async(struct fwtty_peer *peer,
+ struct fwtty_transaction *txn, int tcode,
+ unsigned long long addr, void *payload,
+ size_t len, fwtty_transaction_cb callback,
+ struct fwtty_port *port)
+{
+ int generation;
+
+ txn->callback = callback;
+ txn->port = port;
+
+ generation = peer->generation;
+ smp_rmb();
+ fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+ peer->node_id, generation, peer->speed, addr, payload,
+ len, fwtty_common_callback, txn);
+}
+
+
+static void __fwtty_restart_tx(struct fwtty_port *port)
+{
+ int len, avail;
+
+ len = dma_fifo_out_level(&port->tx_fifo);
+ if (len)
+ schedule_delayed_work(&port->drain, 0);
+ avail = dma_fifo_avail(&port->tx_fifo);
+
+ fwtty_dbg(port, "fifo len: %d avail: %d\n", len, avail);
+}
+
+static void fwtty_restart_tx(struct fwtty_port *port)
+{
+ spin_lock_bh(&port->lock);
+ __fwtty_restart_tx(port);
+ spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_update_port_status - decodes & dispatches line status changes
+ *
+ * Note: in loopback, the port->lock is being held. Only use functions that
+ * don't attempt to reclaim the port->lock.
+ */
+static void fwtty_update_port_status(struct fwtty_port *port, unsigned status)
+{
+ unsigned delta;
+ struct tty_struct *tty;
+
+ /* simulated LSR/MSR status from remote */
+ status &= ~MCTRL_MASK;
+ delta = (port->mstatus ^ status) & ~MCTRL_MASK;
+ delta &= ~(status & TIOCM_RNG);
+ port->mstatus = status;
+
+ if (delta & TIOCM_RNG)
+ ++port->icount.rng;
+ if (delta & TIOCM_DSR)
+ ++port->icount.dsr;
+ if (delta & TIOCM_CAR)
+ ++port->icount.dcd;
+ if (delta & TIOCM_CTS)
+ ++port->icount.cts;
+
+ fwtty_dbg(port, "status: %x delta: %x\n", status, delta);
+
+ if (delta & TIOCM_CAR) {
+ tty = tty_port_tty_get(&port->port);
+ if (tty && !C_CLOCAL(tty)) {
+ if (status & TIOCM_CAR)
+ wake_up_interruptible(&port->port.open_wait);
+ else
+ schedule_work(&port->hangup);
+ }
+ tty_kref_put(tty);
+ }
+
+ if (delta & TIOCM_CTS) {
+ tty = tty_port_tty_get(&port->port);
+ if (tty && C_CRTSCTS(tty)) {
+ if (tty->hw_stopped) {
+ if (status & TIOCM_CTS) {
+ tty->hw_stopped = 0;
+ if (port->loopback)
+ __fwtty_restart_tx(port);
+ else
+ fwtty_restart_tx(port);
+ }
+ } else {
+ if (~status & TIOCM_CTS)
+ tty->hw_stopped = 1;
+ }
+ }
+ tty_kref_put(tty);
+
+ } else if (delta & OOB_TX_THROTTLE) {
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ if (tty->hw_stopped) {
+ if (~status & OOB_TX_THROTTLE) {
+ tty->hw_stopped = 0;
+ if (port->loopback)
+ __fwtty_restart_tx(port);
+ else
+ fwtty_restart_tx(port);
+ }
+ } else {
+ if (status & OOB_TX_THROTTLE)
+ tty->hw_stopped = 1;
+ }
+ }
+ tty_kref_put(tty);
+ }
+
+ if (delta & (UART_LSR_BI << 24)) {
+ if (status & (UART_LSR_BI << 24)) {
+ port->break_last = jiffies;
+ schedule_delayed_work(&port->emit_breaks, 0);
+ } else {
+ /* run emit_breaks one last time (if pending) */
+ mod_delayed_work(system_wq, &port->emit_breaks, 0);
+ }
+ }
+
+ if (delta & (TIOCM_DSR | TIOCM_CAR | TIOCM_CTS | TIOCM_RNG))
+ wake_up_interruptible(&port->port.delta_msr_wait);
+}
+
+/**
+ * __fwtty_port_line_status - generate 'line status' for indicated port
+ *
+ * This function returns a remote 'MSR' state based on the local 'MCR' state,
+ * as if a null modem cable was attached. The actual status is a mangling
+ * of TIOCM_* bits suitable for sending to a peer's status_addr.
+ *
+ * Note: caller must be holding port lock
+ */
+static unsigned __fwtty_port_line_status(struct fwtty_port *port)
+{
+ unsigned status = 0;
+
+ /* TODO: add module param to tie RNG to DTR as well */
+
+ if (port->mctrl & TIOCM_DTR)
+ status |= TIOCM_DSR | TIOCM_CAR;
+ if (port->mctrl & TIOCM_RTS)
+ status |= TIOCM_CTS;
+ if (port->mctrl & OOB_RX_THROTTLE)
+ status |= OOB_TX_THROTTLE;
+ /* emulate BRK as add'l line status */
+ if (port->break_ctl)
+ status |= UART_LSR_BI << 24;
+
+ return status;
+}
+
+/**
+ * __fwtty_write_port_status - send the port line status to peer
+ *
+ * Note: caller must be holding the port lock.
+ */
+static int __fwtty_write_port_status(struct fwtty_port *port)
+{
+ struct fwtty_peer *peer;
+ int err = -ENOENT;
+ unsigned status = __fwtty_port_line_status(port);
+
+ rcu_read_lock();
+ peer = rcu_dereference(port->peer);
+ if (peer) {
+ err = fwtty_send_data_async(peer, TCODE_WRITE_QUADLET_REQUEST,
+ peer->status_addr, &status,
+ sizeof(status), NULL, port);
+ }
+ rcu_read_unlock();
+
+ return err;
+}
+
+/**
+ * fwtty_write_port_status - same as above but locked by port lock
+ */
+static int fwtty_write_port_status(struct fwtty_port *port)
+{
+ int err;
+
+ spin_lock_bh(&port->lock);
+ err = __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+ return err;
+}
+
+static void fwtty_throttle_port(struct fwtty_port *port)
+{
+ struct tty_struct *tty;
+ unsigned old;
+
+ tty = tty_port_tty_get(&port->port);
+ if (!tty)
+ return;
+
+ spin_lock_bh(&port->lock);
+
+ old = port->mctrl;
+ port->mctrl |= OOB_RX_THROTTLE;
+ if (C_CRTSCTS(tty))
+ port->mctrl &= ~TIOCM_RTS;
+ if (~old & OOB_RX_THROTTLE)
+ __fwtty_write_port_status(port);
+
+ spin_unlock_bh(&port->lock);
+
+ tty_kref_put(tty);
+}
+
+/**
+ * fwtty_do_hangup - wait for ldisc to deliver all pending rx; only then hangup
+ *
+ * When the remote has finished tx, and all in-flight rx has been received and
+ * and pushed to the flip buffer, the remote may close its device. This will
+ * drop DTR on the remote which will drop carrier here. Typically, the tty is
+ * hung up when carrier is dropped or lost.
+ *
+ * However, there is a race between the hang up and the line discipline
+ * delivering its data to the reader. A hangup will cause the ldisc to flush
+ * (ie., clear) the read buffer and flip buffer. Because of firewire's
+ * relatively high throughput, the ldisc frequently lags well behind the driver,
+ * resulting in lost data (which has already been received and written to
+ * the flip buffer) when the remote closes its end.
+ *
+ * Unfortunately, since the flip buffer offers no direct method for determining
+ * if it holds data, ensuring the ldisc has delivered all data is problematic.
+ */
+
+/* FIXME: drop this workaround when __tty_hangup waits for ldisc completion */
+static void fwtty_do_hangup(struct work_struct *work)
+{
+ struct fwtty_port *port = to_port(work, hangup);
+ struct tty_struct *tty;
+
+ schedule_timeout_uninterruptible(msecs_to_jiffies(50));
+
+ tty = tty_port_tty_get(&port->port);
+ if (tty)
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+}
+
+
+static void fwtty_emit_breaks(struct work_struct *work)
+{
+ struct fwtty_port *port = to_port(to_delayed_work(work), emit_breaks);
+ static const char buf[16];
+ unsigned long now = jiffies;
+ unsigned long elapsed = now - port->break_last;
+ int n, t, c, brk = 0;
+
+ /* generate breaks at the line rate (but at least 1) */
+ n = (elapsed * port->cps) / HZ + 1;
+ port->break_last = now;
+
+ fwtty_dbg(port, "sending %d brks\n", n);
+
+ while (n) {
+ t = min(n, 16);
+ c = tty_insert_flip_string_fixed_flag(&port->port, buf,
+ TTY_BREAK, t);
+ n -= c;
+ brk += c;
+ if (c < t)
+ break;
+ }
+ tty_flip_buffer_push(&port->port);
+
+ if (port->mstatus & (UART_LSR_BI << 24))
+ schedule_delayed_work(&port->emit_breaks, FREQ_BREAKS);
+ port->icount.brk += brk;
+}
+
+static int fwtty_rx(struct fwtty_port *port, unsigned char *data, size_t len)
+{
+ int c, n = len;
+ unsigned lsr;
+ int err = 0;
+
+ fwtty_dbg(port, "%d\n", n);
+ fwtty_profile_data(port->stats.reads, n);
+
+ if (port->write_only) {
+ n = 0;
+ goto out;
+ }
+
+ /* disregard break status; breaks are generated by emit_breaks work */
+ lsr = (port->mstatus >> 24) & ~UART_LSR_BI;
+
+ if (port->overrun)
+ lsr |= UART_LSR_OE;
+
+ if (lsr & UART_LSR_OE)
+ ++port->icount.overrun;
+
+ lsr &= port->status_mask;
+ if (lsr & ~port->ignore_mask & UART_LSR_OE) {
+ if (!tty_insert_flip_char(&port->port, 0, TTY_OVERRUN)) {
+ err = -EIO;
+ goto out;
+ }
+ }
+ port->overrun = false;
+
+ if (lsr & port->ignore_mask & ~UART_LSR_OE) {
+ /* TODO: don't drop SAK and Magic SysRq here */
+ n = 0;
+ goto out;
+ }
+
+ c = tty_insert_flip_string_fixed_flag(&port->port, data, TTY_NORMAL, n);
+ if (c > 0)
+ tty_flip_buffer_push(&port->port);
+ n -= c;
+
+ if (n) {
+ port->overrun = true;
+ err = -EIO;
+ fwtty_err_ratelimited(port, "flip buffer overrun\n");
+
+ } else {
+ /* throttle the sender if remaining flip buffer space has
+ * reached high watermark to avoid losing data which may be
+ * in-flight. Since the AR request context is 32k, that much
+ * data may have _already_ been acked.
+ */
+ if (tty_buffer_space_avail(&port->port) < HIGH_WATERMARK)
+ fwtty_throttle_port(port);
+ }
+
+out:
+ port->icount.rx += len;
+ port->stats.lost += n;
+ return err;
+}
+
+/**
+ * fwtty_port_handler - bus address handler for port reads/writes
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling inbound read/write dma from remotes.
+ */
+static void fwtty_port_handler(struct fw_card *card,
+ struct fw_request *request,
+ int tcode, int destination, int source,
+ int generation,
+ unsigned long long addr,
+ void *data, size_t len,
+ void *callback_data)
+{
+ struct fwtty_port *port = callback_data;
+ struct fwtty_peer *peer;
+ int err;
+ int rcode;
+
+ /* Only accept rx from the peer virtual-cabled to this port */
+ rcu_read_lock();
+ peer = __fwserial_peer_by_node_id(card, generation, source);
+ rcu_read_unlock();
+ if (!peer || peer != rcu_access_pointer(port->peer)) {
+ rcode = RCODE_ADDRESS_ERROR;
+ fwtty_err_ratelimited(port, "ignoring unauthenticated data\n");
+ goto respond;
+ }
+
+ switch (tcode) {
+ case TCODE_WRITE_QUADLET_REQUEST:
+ if (addr != port->rx_handler.offset || len != 4) {
+ rcode = RCODE_ADDRESS_ERROR;
+ } else {
+ fwtty_update_port_status(port, *(unsigned *)data);
+ rcode = RCODE_COMPLETE;
+ }
+ break;
+
+ case TCODE_WRITE_BLOCK_REQUEST:
+ if (addr != port->rx_handler.offset + 4 ||
+ len > port->rx_handler.length - 4) {
+ rcode = RCODE_ADDRESS_ERROR;
+ } else {
+ err = fwtty_rx(port, data, len);
+ switch (err) {
+ case 0:
+ rcode = RCODE_COMPLETE;
+ break;
+ case -EIO:
+ rcode = RCODE_DATA_ERROR;
+ break;
+ default:
+ rcode = RCODE_CONFLICT_ERROR;
+ break;
+ }
+ }
+ break;
+
+ default:
+ rcode = RCODE_TYPE_ERROR;
+ }
+
+respond:
+ fw_send_response(card, request, rcode);
+}
+
+/**
+ * fwtty_tx_complete - callback for tx dma
+ * @data: ignored, has no meaning for write txns
+ * @length: ignored, has no meaning for write txns
+ *
+ * The writer must be woken here if the fifo has been emptied because it
+ * may have slept if chars_in_buffer was != 0
+ */
+static void fwtty_tx_complete(struct fw_card *card, int rcode,
+ void *data, size_t length,
+ struct fwtty_transaction *txn)
+{
+ struct fwtty_port *port = txn->port;
+ int len;
+
+ fwtty_dbg(port, "rcode: %d\n", rcode);
+
+ switch (rcode) {
+ case RCODE_COMPLETE:
+ spin_lock_bh(&port->lock);
+ dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+ len = dma_fifo_level(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+
+ port->icount.tx += txn->dma_pended.len;
+ break;
+
+ default:
+ /* TODO: implement retries */
+ spin_lock_bh(&port->lock);
+ dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+ len = dma_fifo_level(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+
+ port->stats.dropped += txn->dma_pended.len;
+ }
+
+ if (len < WAKEUP_CHARS)
+ tty_port_tty_wakeup(&port->port);
+}
+
+static int fwtty_tx(struct fwtty_port *port, bool drain)
+{
+ struct fwtty_peer *peer;
+ struct fwtty_transaction *txn;
+ struct tty_struct *tty;
+ int n, len;
+
+ tty = tty_port_tty_get(&port->port);
+ if (!tty)
+ return -ENOENT;
+
+ rcu_read_lock();
+ peer = rcu_dereference(port->peer);
+ if (!peer) {
+ n = -EIO;
+ goto out;
+ }
+
+ if (test_and_set_bit(IN_TX, &port->flags)) {
+ n = -EALREADY;
+ goto out;
+ }
+
+ /* try to write as many dma transactions out as possible */
+ n = -EAGAIN;
+ while (!tty->stopped && !tty->hw_stopped &&
+ !test_bit(STOP_TX, &port->flags)) {
+ txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+ if (!txn) {
+ n = -ENOMEM;
+ break;
+ }
+
+ spin_lock_bh(&port->lock);
+ n = dma_fifo_out_pend(&port->tx_fifo, &txn->dma_pended);
+ spin_unlock_bh(&port->lock);
+
+ fwtty_dbg(port, "out: %u rem: %d\n", txn->dma_pended.len, n);
+
+ if (n < 0) {
+ kmem_cache_free(fwtty_txn_cache, txn);
+ if (n == -EAGAIN) {
+ ++port->stats.tx_stall;
+ } else if (n == -ENODATA) {
+ fwtty_profile_data(port->stats.txns, 0);
+ } else {
+ ++port->stats.fifo_errs;
+ fwtty_err_ratelimited(port, "fifo err: %d\n",
+ n);
+ }
+ break;
+ }
+
+ fwtty_profile_data(port->stats.txns, txn->dma_pended.len);
+
+ fwtty_send_txn_async(peer, txn, TCODE_WRITE_BLOCK_REQUEST,
+ peer->fifo_addr, txn->dma_pended.data,
+ txn->dma_pended.len, fwtty_tx_complete,
+ port);
+ ++port->stats.sent;
+
+ /*
+ * Stop tx if the 'last view' of the fifo is empty or if
+ * this is the writer and there's not enough data to bother
+ */
+ if (n == 0 || (!drain && n < WRITER_MINIMUM))
+ break;
+ }
+
+ if (n >= 0 || n == -EAGAIN || n == -ENOMEM || n == -ENODATA) {
+ spin_lock_bh(&port->lock);
+ len = dma_fifo_out_level(&port->tx_fifo);
+ if (len) {
+ unsigned long delay = (n == -ENOMEM) ? HZ : 1;
+ schedule_delayed_work(&port->drain, delay);
+ }
+ len = dma_fifo_level(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+
+ /* wakeup the writer */
+ if (drain && len < WAKEUP_CHARS)
+ tty_wakeup(tty);
+ }
+
+ clear_bit(IN_TX, &port->flags);
+ wake_up_interruptible(&port->wait_tx);
+
+out:
+ rcu_read_unlock();
+ tty_kref_put(tty);
+ return n;
+}
+
+static void fwtty_drain_tx(struct work_struct *work)
+{
+ struct fwtty_port *port = to_port(to_delayed_work(work), drain);
+
+ fwtty_tx(port, true);
+}
+
+static void fwtty_write_xchar(struct fwtty_port *port, char ch)
+{
+ struct fwtty_peer *peer;
+
+ ++port->stats.xchars;
+
+ fwtty_dbg(port, "%02x\n", ch);
+
+ rcu_read_lock();
+ peer = rcu_dereference(port->peer);
+ if (peer) {
+ fwtty_send_data_async(peer, TCODE_WRITE_BLOCK_REQUEST,
+ peer->fifo_addr, &ch, sizeof(ch),
+ NULL, port);
+ }
+ rcu_read_unlock();
+}
+
+struct fwtty_port *fwtty_port_get(unsigned index)
+{
+ struct fwtty_port *port;
+
+ if (index >= MAX_TOTAL_PORTS)
+ return NULL;
+
+ mutex_lock(&port_table_lock);
+ port = port_table[index];
+ if (port)
+ kref_get(&port->serial->kref);
+ mutex_unlock(&port_table_lock);
+ return port;
+}
+EXPORT_SYMBOL(fwtty_port_get);
+
+static int fwtty_ports_add(struct fw_serial *serial)
+{
+ int err = -EBUSY;
+ int i, j;
+
+ if (port_table_corrupt)
+ return err;
+
+ mutex_lock(&port_table_lock);
+ for (i = 0; i + num_ports <= MAX_TOTAL_PORTS; i += num_ports) {
+ if (!port_table[i]) {
+ for (j = 0; j < num_ports; ++i, ++j) {
+ serial->ports[j]->index = i;
+ port_table[i] = serial->ports[j];
+ }
+ err = 0;
+ break;
+ }
+ }
+ mutex_unlock(&port_table_lock);
+ return err;
+}
+
+static void fwserial_destroy(struct kref *kref)
+{
+ struct fw_serial *serial = to_serial(kref, kref);
+ struct fwtty_port **ports = serial->ports;
+ int j, i = ports[0]->index;
+
+ synchronize_rcu();
+
+ mutex_lock(&port_table_lock);
+ for (j = 0; j < num_ports; ++i, ++j) {
+ port_table_corrupt |= port_table[i] != ports[j];
+ WARN_ONCE(port_table_corrupt, "port_table[%d]: %p != ports[%d]: %p",
+ i, port_table[i], j, ports[j]);
+
+ port_table[i] = NULL;
+ }
+ mutex_unlock(&port_table_lock);
+
+ for (j = 0; j < num_ports; ++j) {
+ fw_core_remove_address_handler(&ports[j]->rx_handler);
+ tty_port_destroy(&ports[j]->port);
+ kfree(ports[j]);
+ }
+ kfree(serial);
+}
+
+void fwtty_port_put(struct fwtty_port *port)
+{
+ kref_put(&port->serial->kref, fwserial_destroy);
+}
+EXPORT_SYMBOL(fwtty_port_put);
+
+static void fwtty_port_dtr_rts(struct tty_port *tty_port, int on)
+{
+ struct fwtty_port *port = to_port(tty_port, port);
+
+ fwtty_dbg(port, "on/off: %d\n", on);
+
+ spin_lock_bh(&port->lock);
+ /* Don't change carrier state if this is a console */
+ if (!port->port.console) {
+ if (on)
+ port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+ else
+ port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+ }
+
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_port_carrier_raised: required tty_port operation
+ *
+ * This port operation is polled after a tty has been opened and is waiting for
+ * carrier detect -- see drivers/tty/tty_port:tty_port_block_til_ready().
+ */
+static int fwtty_port_carrier_raised(struct tty_port *tty_port)
+{
+ struct fwtty_port *port = to_port(tty_port, port);
+ int rc;
+
+ rc = (port->mstatus & TIOCM_CAR);
+
+ fwtty_dbg(port, "%d\n", rc);
+
+ return rc;
+}
+
+static unsigned set_termios(struct fwtty_port *port, struct tty_struct *tty)
+{
+ unsigned baud, frame;
+
+ baud = tty_termios_baud_rate(&tty->termios);
+ tty_termios_encode_baud_rate(&tty->termios, baud, baud);
+
+ /* compute bit count of 2 frames */
+ frame = 12 + ((C_CSTOPB(tty)) ? 4 : 2) + ((C_PARENB(tty)) ? 2 : 0);
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ frame -= (C_CSTOPB(tty)) ? 1 : 0;
+ break;
+ case CS6:
+ frame += 2;
+ break;
+ case CS7:
+ frame += 4;
+ break;
+ case CS8:
+ frame += 6;
+ break;
+ }
+
+ port->cps = (baud << 1) / frame;
+
+ port->status_mask = UART_LSR_OE;
+ if (_I_FLAG(tty, BRKINT | PARMRK))
+ port->status_mask |= UART_LSR_BI;
+
+ port->ignore_mask = 0;
+ if (I_IGNBRK(tty)) {
+ port->ignore_mask |= UART_LSR_BI;
+ if (I_IGNPAR(tty))
+ port->ignore_mask |= UART_LSR_OE;
+ }
+
+ port->write_only = !C_CREAD(tty);
+
+ /* turn off echo and newline xlat if loopback */
+ if (port->loopback) {
+ tty->termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE |
+ ECHONL | ECHOPRT | ECHOCTL);
+ tty->termios.c_oflag &= ~ONLCR;
+ }
+
+ return baud;
+}
+
+static int fwtty_port_activate(struct tty_port *tty_port,
+ struct tty_struct *tty)
+{
+ struct fwtty_port *port = to_port(tty_port, port);
+ unsigned baud;
+ int err;
+
+ set_bit(TTY_IO_ERROR, &tty->flags);
+
+ err = dma_fifo_alloc(&port->tx_fifo, FWTTY_PORT_TXFIFO_LEN,
+ cache_line_size(),
+ port->max_payload,
+ FWTTY_PORT_MAX_PEND_DMA,
+ GFP_KERNEL);
+ if (err)
+ return err;
+
+ spin_lock_bh(&port->lock);
+
+ baud = set_termios(port, tty);
+
+ /* if console, don't change carrier state */
+ if (!port->port.console) {
+ port->mctrl = 0;
+ if (baud != 0)
+ port->mctrl = TIOCM_DTR | TIOCM_RTS;
+ }
+
+ if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS)
+ tty->hw_stopped = 1;
+
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+
+ return 0;
+}
+
+/**
+ * fwtty_port_shutdown
+ *
+ * Note: the tty port core ensures this is not the console and
+ * manages TTY_IO_ERROR properly
+ */
+static void fwtty_port_shutdown(struct tty_port *tty_port)
+{
+ struct fwtty_port *port = to_port(tty_port, port);
+
+ /* TODO: cancel outstanding transactions */
+
+ cancel_delayed_work_sync(&port->emit_breaks);
+ cancel_delayed_work_sync(&port->drain);
+
+ spin_lock_bh(&port->lock);
+ port->flags = 0;
+ port->break_ctl = 0;
+ port->overrun = 0;
+ __fwtty_write_port_status(port);
+ dma_fifo_free(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+}
+
+static int fwtty_open(struct tty_struct *tty, struct file *fp)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ return tty_port_open(&port->port, tty, fp);
+}
+
+static void fwtty_close(struct tty_struct *tty, struct file *fp)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ tty_port_close(&port->port, tty, fp);
+}
+
+static void fwtty_hangup(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ tty_port_hangup(&port->port);
+}
+
+static void fwtty_cleanup(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ tty->driver_data = NULL;
+ fwtty_port_put(port);
+}
+
+static int fwtty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct fwtty_port *port = fwtty_port_get(tty->index);
+ int err;
+
+ err = tty_standard_install(driver, tty);
+ if (!err)
+ tty->driver_data = port;
+ else
+ fwtty_port_put(port);
+ return err;
+}
+
+static int fwloop_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct fwtty_port *port = fwtty_port_get(table_idx(tty->index));
+ int err;
+
+ err = tty_standard_install(driver, tty);
+ if (!err)
+ tty->driver_data = port;
+ else
+ fwtty_port_put(port);
+ return err;
+}
+
+static int fwtty_write(struct tty_struct *tty, const unsigned char *buf, int c)
+{
+ struct fwtty_port *port = tty->driver_data;
+ int n, len;
+
+ fwtty_dbg(port, "%d\n", c);
+ fwtty_profile_data(port->stats.writes, c);
+
+ spin_lock_bh(&port->lock);
+ n = dma_fifo_in(&port->tx_fifo, buf, c);
+ len = dma_fifo_out_level(&port->tx_fifo);
+ if (len < DRAIN_THRESHOLD)
+ schedule_delayed_work(&port->drain, 1);
+ spin_unlock_bh(&port->lock);
+
+ if (len >= DRAIN_THRESHOLD)
+ fwtty_tx(port, false);
+
+ debug_short_write(port, c, n);
+
+ return (n < 0) ? 0 : n;
+}
+
+static int fwtty_write_room(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+ int n;
+
+ spin_lock_bh(&port->lock);
+ n = dma_fifo_avail(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+
+ fwtty_dbg(port, "%d\n", n);
+
+ return n;
+}
+
+static int fwtty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+ int n;
+
+ spin_lock_bh(&port->lock);
+ n = dma_fifo_level(&port->tx_fifo);
+ spin_unlock_bh(&port->lock);
+
+ fwtty_dbg(port, "%d\n", n);
+
+ return n;
+}
+
+static void fwtty_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ fwtty_dbg(port, "%02x\n", ch);
+
+ fwtty_write_xchar(port, ch);
+}
+
+static void fwtty_throttle(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ /*
+ * Ignore throttling (but not unthrottling).
+ * It only makes sense to throttle when data will no longer be
+ * accepted by the tty flip buffer. For example, it is
+ * possible for received data to overflow the tty buffer long
+ * before the line discipline ever has a chance to throttle the driver.
+ * Additionally, the driver may have already completed the I/O
+ * but the tty buffer is still emptying, so the line discipline is
+ * throttling and unthrottling nothing.
+ */
+
+ ++port->stats.throttled;
+}
+
+static void fwtty_unthrottle(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ fwtty_dbg(port, "CRTSCTS: %d\n", (C_CRTSCTS(tty) != 0));
+
+ fwtty_profile_fifo(port, port->stats.unthrottle);
+
+ spin_lock_bh(&port->lock);
+ port->mctrl &= ~OOB_RX_THROTTLE;
+ if (C_CRTSCTS(tty))
+ port->mctrl |= TIOCM_RTS;
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+}
+
+static int check_msr_delta(struct fwtty_port *port, unsigned long mask,
+ struct async_icount *prev)
+{
+ struct async_icount now;
+ int delta;
+
+ now = port->icount;
+
+ delta = ((mask & TIOCM_RNG && prev->rng != now.rng) ||
+ (mask & TIOCM_DSR && prev->dsr != now.dsr) ||
+ (mask & TIOCM_CAR && prev->dcd != now.dcd) ||
+ (mask & TIOCM_CTS && prev->cts != now.cts));
+
+ *prev = now;
+
+ return delta;
+}
+
+static int wait_msr_change(struct fwtty_port *port, unsigned long mask)
+{
+ struct async_icount prev;
+
+ prev = port->icount;
+
+ return wait_event_interruptible(port->port.delta_msr_wait,
+ check_msr_delta(port, mask, &prev));
+}
+
+static int get_serial_info(struct fwtty_port *port,
+ struct serial_struct __user *info)
+{
+ struct serial_struct tmp;
+
+ memset(&tmp, 0, sizeof(tmp));
+
+ tmp.type = PORT_UNKNOWN;
+ tmp.line = port->port.tty->index;
+ tmp.flags = port->port.flags;
+ tmp.xmit_fifo_size = FWTTY_PORT_TXFIFO_LEN;
+ tmp.baud_base = 400000000;
+ tmp.close_delay = port->port.close_delay;
+
+ return (copy_to_user(info, &tmp, sizeof(*info))) ? -EFAULT : 0;
+}
+
+static int set_serial_info(struct fwtty_port *port,
+ struct serial_struct __user *info)
+{
+ struct serial_struct tmp;
+
+ if (copy_from_user(&tmp, info, sizeof(tmp)))
+ return -EFAULT;
+
+ if (tmp.irq != 0 || tmp.port != 0 || tmp.custom_divisor != 0 ||
+ tmp.baud_base != 400000000)
+ return -EPERM;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (((tmp.flags & ~ASYNC_USR_MASK) !=
+ (port->port.flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ } else {
+ port->port.close_delay = tmp.close_delay * HZ / 100;
+ }
+
+ return 0;
+}
+
+static int fwtty_ioctl(struct tty_struct *tty, unsigned cmd,
+ unsigned long arg)
+{
+ struct fwtty_port *port = tty->driver_data;
+ int err;
+
+ switch (cmd) {
+ case TIOCGSERIAL:
+ mutex_lock(&port->port.mutex);
+ err = get_serial_info(port, (void __user *)arg);
+ mutex_unlock(&port->port.mutex);
+ break;
+
+ case TIOCSSERIAL:
+ mutex_lock(&port->port.mutex);
+ err = set_serial_info(port, (void __user *)arg);
+ mutex_unlock(&port->port.mutex);
+ break;
+
+ case TIOCMIWAIT:
+ err = wait_msr_change(port, arg);
+ break;
+
+ default:
+ err = -ENOIOCTLCMD;
+ }
+
+ return err;
+}
+
+static void fwtty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+ struct fwtty_port *port = tty->driver_data;
+ unsigned baud;
+
+ spin_lock_bh(&port->lock);
+ baud = set_termios(port, tty);
+
+ if ((baud == 0) && (old->c_cflag & CBAUD)) {
+ port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+ } else if ((baud != 0) && !(old->c_cflag & CBAUD)) {
+ if (C_CRTSCTS(tty) || !test_bit(TTY_THROTTLED, &tty->flags))
+ port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+ else
+ port->mctrl |= TIOCM_DTR;
+ }
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+
+ if (old->c_cflag & CRTSCTS) {
+ if (!C_CRTSCTS(tty)) {
+ tty->hw_stopped = 0;
+ fwtty_restart_tx(port);
+ }
+ } else if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS) {
+ tty->hw_stopped = 1;
+ }
+}
+
+/**
+ * fwtty_break_ctl - start/stop sending breaks
+ *
+ * Signals the remote to start or stop generating simulated breaks.
+ * First, stop dequeueing from the fifo and wait for writer/drain to leave tx
+ * before signalling the break line status. This guarantees any pending rx will
+ * be queued to the line discipline before break is simulated on the remote.
+ * Conversely, turning off break_ctl requires signalling the line status change,
+ * then enabling tx.
+ */
+static int fwtty_break_ctl(struct tty_struct *tty, int state)
+{
+ struct fwtty_port *port = tty->driver_data;
+ long ret;
+
+ fwtty_dbg(port, "%d\n", state);
+
+ if (state == -1) {
+ set_bit(STOP_TX, &port->flags);
+ ret = wait_event_interruptible_timeout(port->wait_tx,
+ !test_bit(IN_TX, &port->flags),
+ 10);
+ if (ret == 0 || ret == -ERESTARTSYS) {
+ clear_bit(STOP_TX, &port->flags);
+ fwtty_restart_tx(port);
+ return -EINTR;
+ }
+ }
+
+ spin_lock_bh(&port->lock);
+ port->break_ctl = (state == -1);
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+
+ if (state == 0) {
+ spin_lock_bh(&port->lock);
+ dma_fifo_reset(&port->tx_fifo);
+ clear_bit(STOP_TX, &port->flags);
+ spin_unlock_bh(&port->lock);
+ }
+ return 0;
+}
+
+static int fwtty_tiocmget(struct tty_struct *tty)
+{
+ struct fwtty_port *port = tty->driver_data;
+ unsigned tiocm;
+
+ spin_lock_bh(&port->lock);
+ tiocm = (port->mctrl & MCTRL_MASK) | (port->mstatus & ~MCTRL_MASK);
+ spin_unlock_bh(&port->lock);
+
+ fwtty_dbg(port, "%x\n", tiocm);
+
+ return tiocm;
+}
+
+static int fwtty_tiocmset(struct tty_struct *tty, unsigned set, unsigned clear)
+{
+ struct fwtty_port *port = tty->driver_data;
+
+ fwtty_dbg(port, "set: %x clear: %x\n", set, clear);
+
+ /* TODO: simulate loopback if TIOCM_LOOP set */
+
+ spin_lock_bh(&port->lock);
+ port->mctrl &= ~(clear & MCTRL_MASK & 0xffff);
+ port->mctrl |= set & MCTRL_MASK & 0xffff;
+ __fwtty_write_port_status(port);
+ spin_unlock_bh(&port->lock);
+ return 0;
+}
+
+static int fwtty_get_icount(struct tty_struct *tty,
+ struct serial_icounter_struct *icount)
+{
+ struct fwtty_port *port = tty->driver_data;
+ struct stats stats;
+
+ memcpy(&stats, &port->stats, sizeof(stats));
+ if (port->port.console)
+ (*port->fwcon_ops->stats)(&stats, port->con_data);
+
+ icount->cts = port->icount.cts;
+ icount->dsr = port->icount.dsr;
+ icount->rng = port->icount.rng;
+ icount->dcd = port->icount.dcd;
+ icount->rx = port->icount.rx;
+ icount->tx = port->icount.tx + stats.xchars;
+ icount->frame = port->icount.frame;
+ icount->overrun = port->icount.overrun;
+ icount->parity = port->icount.parity;
+ icount->brk = port->icount.brk;
+ icount->buf_overrun = port->icount.overrun;
+ return 0;
+}
+
+static void fwtty_proc_show_port(struct seq_file *m, struct fwtty_port *port)
+{
+ struct stats stats;
+
+ memcpy(&stats, &port->stats, sizeof(stats));
+ if (port->port.console)
+ (*port->fwcon_ops->stats)(&stats, port->con_data);
+
+ seq_printf(m, " addr:%012llx tx:%d rx:%d", port->rx_handler.offset,
+ port->icount.tx + stats.xchars, port->icount.rx);
+ seq_printf(m, " cts:%d dsr:%d rng:%d dcd:%d", port->icount.cts,
+ port->icount.dsr, port->icount.rng, port->icount.dcd);
+ seq_printf(m, " fe:%d oe:%d pe:%d brk:%d", port->icount.frame,
+ port->icount.overrun, port->icount.parity, port->icount.brk);
+}
+
+static void fwtty_debugfs_show_port(struct seq_file *m, struct fwtty_port *port)
+{
+ struct stats stats;
+
+ memcpy(&stats, &port->stats, sizeof(stats));
+ if (port->port.console)
+ (*port->fwcon_ops->stats)(&stats, port->con_data);
+
+ seq_printf(m, " dr:%d st:%d err:%d lost:%d", stats.dropped,
+ stats.tx_stall, stats.fifo_errs, stats.lost);
+ seq_printf(m, " pkts:%d thr:%d", stats.sent, stats.throttled);
+
+ if (port->port.console) {
+ seq_puts(m, "\n ");
+ (*port->fwcon_ops->proc_show)(m, port->con_data);
+ }
+
+ fwtty_dump_profile(m, &port->stats);
+}
+
+static void fwtty_debugfs_show_peer(struct seq_file *m, struct fwtty_peer *peer)
+{
+ int generation = peer->generation;
+
+ smp_rmb();
+ seq_printf(m, " %s:", dev_name(&peer->unit->device));
+ seq_printf(m, " node:%04x gen:%d", peer->node_id, generation);
+ seq_printf(m, " sp:%d max:%d guid:%016llx", peer->speed,
+ peer->max_payload, (unsigned long long) peer->guid);
+ seq_printf(m, " mgmt:%012llx", (unsigned long long) peer->mgmt_addr);
+ seq_printf(m, " addr:%012llx", (unsigned long long) peer->status_addr);
+ seq_putc(m, '\n');
+}
+
+static int fwtty_proc_show(struct seq_file *m, void *v)
+{
+ struct fwtty_port *port;
+ int i;
+
+ seq_puts(m, "fwserinfo: 1.0 driver: 1.0\n");
+ for (i = 0; i < MAX_TOTAL_PORTS && (port = fwtty_port_get(i)); ++i) {
+ seq_printf(m, "%2d:", i);
+ if (capable(CAP_SYS_ADMIN))
+ fwtty_proc_show_port(m, port);
+ fwtty_port_put(port);
+ seq_puts(m, "\n");
+ }
+ return 0;
+}
+
+static int fwtty_debugfs_stats_show(struct seq_file *m, void *v)
+{
+ struct fw_serial *serial = m->private;
+ struct fwtty_port *port;
+ int i;
+
+ for (i = 0; i < num_ports; ++i) {
+ port = fwtty_port_get(serial->ports[i]->index);
+ if (port) {
+ seq_printf(m, "%2d:", port->index);
+ fwtty_proc_show_port(m, port);
+ fwtty_debugfs_show_port(m, port);
+ fwtty_port_put(port);
+ seq_puts(m, "\n");
+ }
+ }
+ return 0;
+}
+
+static int fwtty_debugfs_peers_show(struct seq_file *m, void *v)
+{
+ struct fw_serial *serial = m->private;
+ struct fwtty_peer *peer;
+
+ rcu_read_lock();
+ seq_printf(m, "card: %s guid: %016llx\n",
+ dev_name(serial->card->device),
+ (unsigned long long) serial->card->guid);
+ list_for_each_entry_rcu(peer, &serial->peer_list, list)
+ fwtty_debugfs_show_peer(m, peer);
+ rcu_read_unlock();
+ return 0;
+}
+
+static int fwtty_proc_open(struct inode *inode, struct file *fp)
+{
+ return single_open(fp, fwtty_proc_show, NULL);
+}
+
+static int fwtty_stats_open(struct inode *inode, struct file *fp)
+{
+ return single_open(fp, fwtty_debugfs_stats_show, inode->i_private);
+}
+
+static int fwtty_peers_open(struct inode *inode, struct file *fp)
+{
+ return single_open(fp, fwtty_debugfs_peers_show, inode->i_private);
+}
+
+static const struct file_operations fwtty_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = fwtty_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations fwtty_peers_fops = {
+ .owner = THIS_MODULE,
+ .open = fwtty_peers_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations fwtty_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = fwtty_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct tty_port_operations fwtty_port_ops = {
+ .dtr_rts = fwtty_port_dtr_rts,
+ .carrier_raised = fwtty_port_carrier_raised,
+ .shutdown = fwtty_port_shutdown,
+ .activate = fwtty_port_activate,
+};
+
+static const struct tty_operations fwtty_ops = {
+ .open = fwtty_open,
+ .close = fwtty_close,
+ .hangup = fwtty_hangup,
+ .cleanup = fwtty_cleanup,
+ .install = fwtty_install,
+ .write = fwtty_write,
+ .write_room = fwtty_write_room,
+ .chars_in_buffer = fwtty_chars_in_buffer,
+ .send_xchar = fwtty_send_xchar,
+ .throttle = fwtty_throttle,
+ .unthrottle = fwtty_unthrottle,
+ .ioctl = fwtty_ioctl,
+ .set_termios = fwtty_set_termios,
+ .break_ctl = fwtty_break_ctl,
+ .tiocmget = fwtty_tiocmget,
+ .tiocmset = fwtty_tiocmset,
+ .get_icount = fwtty_get_icount,
+ .proc_fops = &fwtty_proc_fops,
+};
+
+static const struct tty_operations fwloop_ops = {
+ .open = fwtty_open,
+ .close = fwtty_close,
+ .hangup = fwtty_hangup,
+ .cleanup = fwtty_cleanup,
+ .install = fwloop_install,
+ .write = fwtty_write,
+ .write_room = fwtty_write_room,
+ .chars_in_buffer = fwtty_chars_in_buffer,
+ .send_xchar = fwtty_send_xchar,
+ .throttle = fwtty_throttle,
+ .unthrottle = fwtty_unthrottle,
+ .ioctl = fwtty_ioctl,
+ .set_termios = fwtty_set_termios,
+ .break_ctl = fwtty_break_ctl,
+ .tiocmget = fwtty_tiocmget,
+ .tiocmset = fwtty_tiocmset,
+ .get_icount = fwtty_get_icount,
+};
+
+static inline int mgmt_pkt_expected_len(__be16 code)
+{
+ static const struct fwserial_mgmt_pkt pkt;
+
+ switch (be16_to_cpu(code)) {
+ case FWSC_VIRT_CABLE_PLUG:
+ return sizeof(pkt.hdr) + sizeof(pkt.plug_req);
+
+ case FWSC_VIRT_CABLE_PLUG_RSP: /* | FWSC_RSP_OK */
+ return sizeof(pkt.hdr) + sizeof(pkt.plug_rsp);
+
+
+ case FWSC_VIRT_CABLE_UNPLUG:
+ case FWSC_VIRT_CABLE_UNPLUG_RSP:
+ case FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK:
+ case FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK:
+ return sizeof(pkt.hdr);
+
+ default:
+ return -1;
+ }
+}
+
+static inline void fill_plug_params(struct virt_plug_params *params,
+ struct fwtty_port *port)
+{
+ u64 status_addr = port->rx_handler.offset;
+ u64 fifo_addr = port->rx_handler.offset + 4;
+ size_t fifo_len = port->rx_handler.length - 4;
+
+ params->status_hi = cpu_to_be32(status_addr >> 32);
+ params->status_lo = cpu_to_be32(status_addr);
+ params->fifo_hi = cpu_to_be32(fifo_addr >> 32);
+ params->fifo_lo = cpu_to_be32(fifo_addr);
+ params->fifo_len = cpu_to_be32(fifo_len);
+}
+
+static inline void fill_plug_req(struct fwserial_mgmt_pkt *pkt,
+ struct fwtty_port *port)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+ fill_plug_params(&pkt->plug_req, port);
+}
+
+static inline void fill_plug_rsp_ok(struct fwserial_mgmt_pkt *pkt,
+ struct fwtty_port *port)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+ fill_plug_params(&pkt->plug_rsp, port);
+}
+
+static inline void fill_plug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_req(struct fwserial_mgmt_pkt *pkt)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_ok(struct fwserial_mgmt_pkt *pkt)
+{
+ pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP);
+ pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static void fwserial_virt_plug_complete(struct fwtty_peer *peer,
+ struct virt_plug_params *params)
+{
+ struct fwtty_port *port = peer->port;
+
+ peer->status_addr = be32_to_u64(params->status_hi, params->status_lo);
+ peer->fifo_addr = be32_to_u64(params->fifo_hi, params->fifo_lo);
+ peer->fifo_len = be32_to_cpu(params->fifo_len);
+ peer_set_state(peer, FWPS_ATTACHED);
+
+ /* reconfigure tx_fifo optimally for this peer */
+ spin_lock_bh(&port->lock);
+ port->max_payload = min(peer->max_payload, peer->fifo_len);
+ dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+ spin_unlock_bh(&peer->port->lock);
+
+ if (port->port.console && port->fwcon_ops->notify != NULL)
+ (*port->fwcon_ops->notify)(FWCON_NOTIFY_ATTACH, port->con_data);
+
+ fwtty_info(&peer->unit, "peer (guid:%016llx) connected on %s\n",
+ (unsigned long long)peer->guid, dev_name(port->device));
+}
+
+static inline int fwserial_send_mgmt_sync(struct fwtty_peer *peer,
+ struct fwserial_mgmt_pkt *pkt)
+{
+ int generation;
+ int rcode, tries = 5;
+
+ do {
+ generation = peer->generation;
+ smp_rmb();
+
+ rcode = fw_run_transaction(peer->serial->card,
+ TCODE_WRITE_BLOCK_REQUEST,
+ peer->node_id,
+ generation, peer->speed,
+ peer->mgmt_addr,
+ pkt, be16_to_cpu(pkt->hdr.len));
+ if (rcode == RCODE_BUSY || rcode == RCODE_SEND_ERROR ||
+ rcode == RCODE_GENERATION) {
+ fwtty_dbg(&peer->unit, "mgmt write error: %d\n", rcode);
+ continue;
+ } else {
+ break;
+ }
+ } while (--tries > 0);
+ return rcode;
+}
+
+/**
+ * fwserial_claim_port - attempt to claim port @ index for peer
+ *
+ * Returns ptr to claimed port or error code (as ERR_PTR())
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_claim_port(struct fwtty_peer *peer,
+ int index)
+{
+ struct fwtty_port *port;
+
+ if (index < 0 || index >= num_ports)
+ return ERR_PTR(-EINVAL);
+
+ /* must guarantee that previous port releases have completed */
+ synchronize_rcu();
+
+ port = peer->serial->ports[index];
+ spin_lock_bh(&port->lock);
+ if (!rcu_access_pointer(port->peer))
+ rcu_assign_pointer(port->peer, peer);
+ else
+ port = ERR_PTR(-EBUSY);
+ spin_unlock_bh(&port->lock);
+
+ return port;
+}
+
+/**
+ * fwserial_find_port - find avail port and claim for peer
+ *
+ * Returns ptr to claimed port or NULL if none avail
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_find_port(struct fwtty_peer *peer)
+{
+ struct fwtty_port **ports = peer->serial->ports;
+ int i;
+
+ /* must guarantee that previous port releases have completed */
+ synchronize_rcu();
+
+ /* TODO: implement optional GUID-to-specific port # matching */
+
+ /* find an unattached port (but not the loopback port, if present) */
+ for (i = 0; i < num_ttys; ++i) {
+ spin_lock_bh(&ports[i]->lock);
+ if (!ports[i]->peer) {
+ /* claim port */
+ rcu_assign_pointer(ports[i]->peer, peer);
+ spin_unlock_bh(&ports[i]->lock);
+ return ports[i];
+ }
+ spin_unlock_bh(&ports[i]->lock);
+ }
+ return NULL;
+}
+
+static void fwserial_release_port(struct fwtty_port *port, bool reset)
+{
+ /* drop carrier (and all other line status) */
+ if (reset)
+ fwtty_update_port_status(port, 0);
+
+ spin_lock_bh(&port->lock);
+
+ /* reset dma fifo max transmission size back to S100 */
+ port->max_payload = link_speed_to_max_payload(SCODE_100);
+ dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+
+ RCU_INIT_POINTER(port->peer, NULL);
+ spin_unlock_bh(&port->lock);
+
+ if (port->port.console && port->fwcon_ops->notify != NULL)
+ (*port->fwcon_ops->notify)(FWCON_NOTIFY_DETACH, port->con_data);
+}
+
+static void fwserial_plug_timeout(unsigned long data)
+{
+ struct fwtty_peer *peer = (struct fwtty_peer *)data;
+ struct fwtty_port *port;
+
+ spin_lock_bh(&peer->lock);
+ if (peer->state != FWPS_PLUG_PENDING) {
+ spin_unlock_bh(&peer->lock);
+ return;
+ }
+
+ port = peer_revert_state(peer);
+ spin_unlock_bh(&peer->lock);
+
+ if (port)
+ fwserial_release_port(port, false);
+}
+
+/**
+ * fwserial_connect_peer - initiate virtual cable with peer
+ *
+ * Returns 0 if VIRT_CABLE_PLUG request was successfully sent,
+ * otherwise error code. Must be called from process context.
+ */
+static int fwserial_connect_peer(struct fwtty_peer *peer)
+{
+ struct fwtty_port *port;
+ struct fwserial_mgmt_pkt *pkt;
+ int err, rcode;
+
+ pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+ if (!pkt)
+ return -ENOMEM;
+
+ port = fwserial_find_port(peer);
+ if (!port) {
+ fwtty_err(&peer->unit, "avail ports in use\n");
+ err = -EBUSY;
+ goto free_pkt;
+ }
+
+ spin_lock_bh(&peer->lock);
+
+ /* only initiate VIRT_CABLE_PLUG if peer is currently not attached */
+ if (peer->state != FWPS_NOT_ATTACHED) {
+ err = -EBUSY;
+ goto release_port;
+ }
+
+ peer->port = port;
+ peer_set_state(peer, FWPS_PLUG_PENDING);
+
+ fill_plug_req(pkt, peer->port);
+
+ setup_timer(&peer->timer, fwserial_plug_timeout, (unsigned long)peer);
+ mod_timer(&peer->timer, jiffies + VIRT_CABLE_PLUG_TIMEOUT);
+ spin_unlock_bh(&peer->lock);
+
+ rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+ spin_lock_bh(&peer->lock);
+ if (peer->state == FWPS_PLUG_PENDING && rcode != RCODE_COMPLETE) {
+ if (rcode == RCODE_CONFLICT_ERROR)
+ err = -EAGAIN;
+ else
+ err = -EIO;
+ goto cancel_timer;
+ }
+ spin_unlock_bh(&peer->lock);
+
+ kfree(pkt);
+ return 0;
+
+cancel_timer:
+ del_timer(&peer->timer);
+ peer_revert_state(peer);
+release_port:
+ spin_unlock_bh(&peer->lock);
+ fwserial_release_port(port, false);
+free_pkt:
+ kfree(pkt);
+ return err;
+}
+
+/**
+ * fwserial_close_port -
+ * HUP the tty (if the tty exists) and unregister the tty device.
+ * Only used by the unit driver upon unit removal to disconnect and
+ * cleanup all attached ports
+ *
+ * The port reference is put by fwtty_cleanup (if a reference was
+ * ever taken).
+ */
+static void fwserial_close_port(struct tty_driver *driver,
+ struct fwtty_port *port)
+{
+ struct tty_struct *tty;
+
+ mutex_lock(&port->port.mutex);
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
+ mutex_unlock(&port->port.mutex);
+
+ if (driver == fwloop_driver)
+ tty_unregister_device(driver, loop_idx(port));
+ else
+ tty_unregister_device(driver, port->index);
+}
+
+/**
+ * fwserial_lookup - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be holding fwserial_list_mutex
+ */
+static struct fw_serial *fwserial_lookup(struct fw_card *card)
+{
+ struct fw_serial *serial;
+
+ list_for_each_entry(serial, &fwserial_list, list) {
+ if (card == serial->card)
+ return serial;
+ }
+
+ return NULL;
+}
+
+/**
+ * __fwserial_lookup_rcu - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be inside rcu_read_lock() section
+ */
+static struct fw_serial *__fwserial_lookup_rcu(struct fw_card *card)
+{
+ struct fw_serial *serial;
+
+ list_for_each_entry_rcu(serial, &fwserial_list, list) {
+ if (card == serial->card)
+ return serial;
+ }
+
+ return NULL;
+}
+
+/**
+ * __fwserial_peer_by_node_id - finds a peer matching the given generation + id
+ *
+ * If a matching peer could not be found for the specified generation/node id,
+ * this could be because:
+ * a) the generation has changed and one of the nodes hasn't updated yet
+ * b) the remote node has created its remote unit device before this
+ * local node has created its corresponding remote unit device
+ * In either case, the remote node should retry
+ *
+ * Note: caller must be in rcu_read_lock() section
+ */
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+ int generation, int id)
+{
+ struct fw_serial *serial;
+ struct fwtty_peer *peer;
+
+ serial = __fwserial_lookup_rcu(card);
+ if (!serial) {
+ /*
+ * Something is very wrong - there should be a matching
+ * fw_serial structure for every fw_card. Maybe the remote node
+ * has created its remote unit device before this driver has
+ * been probed for any unit devices...
+ */
+ fwtty_err(card, "unknown card (guid %016llx)\n",
+ (unsigned long long) card->guid);
+ return NULL;
+ }
+
+ list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+ int g = peer->generation;
+ smp_rmb();
+ if (generation == g && id == peer->node_id)
+ return peer;
+ }
+
+ return NULL;
+}
+
+#ifdef DEBUG
+static void __dump_peer_list(struct fw_card *card)
+{
+ struct fw_serial *serial;
+ struct fwtty_peer *peer;
+
+ serial = __fwserial_lookup_rcu(card);
+ if (!serial)
+ return;
+
+ list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+ int g = peer->generation;
+ smp_rmb();
+ fwtty_dbg(card, "peer(%d:%x) guid: %016llx\n",
+ g, peer->node_id, (unsigned long long) peer->guid);
+ }
+}
+#else
+#define __dump_peer_list(s)
+#endif
+
+static void fwserial_auto_connect(struct work_struct *work)
+{
+ struct fwtty_peer *peer = to_peer(to_delayed_work(work), connect);
+ int err;
+
+ err = fwserial_connect_peer(peer);
+ if (err == -EAGAIN && ++peer->connect_retries < MAX_CONNECT_RETRIES)
+ schedule_delayed_work(&peer->connect, CONNECT_RETRY_DELAY);
+}
+
+static void fwserial_peer_workfn(struct work_struct *work)
+{
+ struct fwtty_peer *peer = to_peer(work, work);
+
+ peer->workfn(work);
+}
+
+/**
+ * fwserial_add_peer - add a newly probed 'serial' unit device as a 'peer'
+ * @serial: aggregate representing the specific fw_card to add the peer to
+ * @unit: 'peer' to create and add to peer_list of serial
+ *
+ * Adds a 'peer' (ie, a local or remote 'serial' unit device) to the list of
+ * peers for a specific fw_card. Optionally, auto-attach this peer to an
+ * available tty port. This function is called either directly or indirectly
+ * as a result of a 'serial' unit device being created & probed.
+ *
+ * Note: this function is serialized with fwserial_remove_peer() by the
+ * fwserial_list_mutex held in fwserial_probe().
+ *
+ * A 1:1 correspondence between an fw_unit and an fwtty_peer is maintained
+ * via the dev_set_drvdata() for the device of the fw_unit.
+ */
+static int fwserial_add_peer(struct fw_serial *serial, struct fw_unit *unit)
+{
+ struct device *dev = &unit->device;
+ struct fw_device *parent = fw_parent_device(unit);
+ struct fwtty_peer *peer;
+ struct fw_csr_iterator ci;
+ int key, val;
+ int generation;
+
+ peer = kzalloc(sizeof(*peer), GFP_KERNEL);
+ if (!peer)
+ return -ENOMEM;
+
+ peer_set_state(peer, FWPS_NOT_ATTACHED);
+
+ dev_set_drvdata(dev, peer);
+ peer->unit = unit;
+ peer->guid = (u64)parent->config_rom[3] << 32 | parent->config_rom[4];
+ peer->speed = parent->max_speed;
+ peer->max_payload = min(device_max_receive(parent),
+ link_speed_to_max_payload(peer->speed));
+
+ generation = parent->generation;
+ smp_rmb();
+ peer->node_id = parent->node_id;
+ smp_wmb();
+ peer->generation = generation;
+
+ /* retrieve the mgmt bus addr from the unit directory */
+ fw_csr_iterator_init(&ci, unit->directory);
+ while (fw_csr_iterator_next(&ci, &key, &val)) {
+ if (key == (CSR_OFFSET | CSR_DEPENDENT_INFO)) {
+ peer->mgmt_addr = CSR_REGISTER_BASE + 4 * val;
+ break;
+ }
+ }
+ if (peer->mgmt_addr == 0ULL) {
+ /*
+ * No mgmt address effectively disables VIRT_CABLE_PLUG -
+ * this peer will not be able to attach to a remote
+ */
+ peer_set_state(peer, FWPS_NO_MGMT_ADDR);
+ }
+
+ spin_lock_init(&peer->lock);
+ peer->port = NULL;
+
+ init_timer(&peer->timer);
+ INIT_WORK(&peer->work, fwserial_peer_workfn);
+ INIT_DELAYED_WORK(&peer->connect, fwserial_auto_connect);
+
+ /* associate peer with specific fw_card */
+ peer->serial = serial;
+ list_add_rcu(&peer->list, &serial->peer_list);
+
+ fwtty_info(&peer->unit, "peer added (guid:%016llx)\n",
+ (unsigned long long)peer->guid);
+
+ /* identify the local unit & virt cable to loopback port */
+ if (parent->is_local) {
+ serial->self = peer;
+ if (create_loop_dev) {
+ struct fwtty_port *port;
+ port = fwserial_claim_port(peer, num_ttys);
+ if (!IS_ERR(port)) {
+ struct virt_plug_params params;
+
+ spin_lock_bh(&peer->lock);
+ peer->port = port;
+ fill_plug_params(&params, port);
+ fwserial_virt_plug_complete(peer, &params);
+ spin_unlock_bh(&peer->lock);
+
+ fwtty_write_port_status(port);
+ }
+ }
+
+ } else if (auto_connect) {
+ /* auto-attach to remote units only (if policy allows) */
+ schedule_delayed_work(&peer->connect, 1);
+ }
+
+ return 0;
+}
+
+/**
+ * fwserial_remove_peer - remove a 'serial' unit device as a 'peer'
+ *
+ * Remove a 'peer' from its list of peers. This function is only
+ * called by fwserial_remove() on bus removal of the unit device.
+ *
+ * Note: this function is serialized with fwserial_add_peer() by the
+ * fwserial_list_mutex held in fwserial_remove().
+ */
+static void fwserial_remove_peer(struct fwtty_peer *peer)
+{
+ struct fwtty_port *port;
+
+ spin_lock_bh(&peer->lock);
+ peer_set_state(peer, FWPS_GONE);
+ spin_unlock_bh(&peer->lock);
+
+ cancel_delayed_work_sync(&peer->connect);
+ cancel_work_sync(&peer->work);
+
+ spin_lock_bh(&peer->lock);
+ /* if this unit is the local unit, clear link */
+ if (peer == peer->serial->self)
+ peer->serial->self = NULL;
+
+ /* cancel the request timeout timer (if running) */
+ del_timer(&peer->timer);
+
+ port = peer->port;
+ peer->port = NULL;
+
+ list_del_rcu(&peer->list);
+
+ fwtty_info(&peer->unit, "peer removed (guid:%016llx)\n",
+ (unsigned long long)peer->guid);
+
+ spin_unlock_bh(&peer->lock);
+
+ if (port)
+ fwserial_release_port(port, true);
+
+ synchronize_rcu();
+ kfree(peer);
+}
+
+/**
+ * fwserial_create - init everything to create TTYs for a specific fw_card
+ * @unit: fw_unit for first 'serial' unit device probed for this fw_card
+ *
+ * This function inits the aggregate structure (an fw_serial instance)
+ * used to manage the TTY ports registered by a specific fw_card. Also, the
+ * unit device is added as the first 'peer'.
+ *
+ * This unit device may represent a local unit device (as specified by the
+ * config ROM unit directory) or it may represent a remote unit device
+ * (as specified by the reading of the remote node's config ROM).
+ *
+ * Returns 0 to indicate "ownership" of the unit device, or a negative errno
+ * value to indicate which error.
+ */
+static int fwserial_create(struct fw_unit *unit)
+{
+ struct fw_device *parent = fw_parent_device(unit);
+ struct fw_card *card = parent->card;
+ struct fw_serial *serial;
+ struct fwtty_port *port;
+ struct device *tty_dev;
+ int i, j;
+ int err;
+
+ serial = kzalloc(sizeof(*serial), GFP_KERNEL);
+ if (!serial)
+ return -ENOMEM;
+
+ kref_init(&serial->kref);
+ serial->card = card;
+ INIT_LIST_HEAD(&serial->peer_list);
+
+ for (i = 0; i < num_ports; ++i) {
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ err = -ENOMEM;
+ goto free_ports;
+ }
+ tty_port_init(&port->port);
+ port->index = FWTTY_INVALID_INDEX;
+ port->port.ops = &fwtty_port_ops;
+ port->serial = serial;
+ tty_buffer_set_limit(&port->port, 128 * 1024);
+
+ spin_lock_init(&port->lock);
+ INIT_DELAYED_WORK(&port->drain, fwtty_drain_tx);
+ INIT_DELAYED_WORK(&port->emit_breaks, fwtty_emit_breaks);
+ INIT_WORK(&port->hangup, fwtty_do_hangup);
+ init_waitqueue_head(&port->wait_tx);
+ port->max_payload = link_speed_to_max_payload(SCODE_100);
+ dma_fifo_init(&port->tx_fifo);
+
+ RCU_INIT_POINTER(port->peer, NULL);
+ serial->ports[i] = port;
+
+ /* get unique bus addr region for port's status & recv fifo */
+ port->rx_handler.length = FWTTY_PORT_RXFIFO_LEN + 4;
+ port->rx_handler.address_callback = fwtty_port_handler;
+ port->rx_handler.callback_data = port;
+ /*
+ * XXX: use custom memory region above cpu physical memory addrs
+ * this will ease porting to 64-bit firewire adapters
+ */
+ err = fw_core_add_address_handler(&port->rx_handler,
+ &fw_high_memory_region);
+ if (err) {
+ kfree(port);
+ goto free_ports;
+ }
+ }
+ /* preserve i for error cleanup */
+
+ err = fwtty_ports_add(serial);
+ if (err) {
+ fwtty_err(&unit, "no space in port table\n");
+ goto free_ports;
+ }
+
+ for (j = 0; j < num_ttys; ++j) {
+ tty_dev = tty_port_register_device(&serial->ports[j]->port,
+ fwtty_driver,
+ serial->ports[j]->index,
+ card->device);
+ if (IS_ERR(tty_dev)) {
+ err = PTR_ERR(tty_dev);
+ fwtty_err(&unit, "register tty device error (%d)\n",
+ err);
+ goto unregister_ttys;
+ }
+
+ serial->ports[j]->device = tty_dev;
+ }
+ /* preserve j for error cleanup */
+
+ if (create_loop_dev) {
+ struct device *loop_dev;
+
+ loop_dev = tty_port_register_device(&serial->ports[j]->port,
+ fwloop_driver,
+ loop_idx(serial->ports[j]),
+ card->device);
+ if (IS_ERR(loop_dev)) {
+ err = PTR_ERR(loop_dev);
+ fwtty_err(&unit, "create loop device failed (%d)\n",
+ err);
+ goto unregister_ttys;
+ }
+ serial->ports[j]->device = loop_dev;
+ serial->ports[j]->loopback = true;
+ }
+
+ if (!IS_ERR_OR_NULL(fwserial_debugfs)) {
+ serial->debugfs = debugfs_create_dir(dev_name(&unit->device),
+ fwserial_debugfs);
+ if (!IS_ERR_OR_NULL(serial->debugfs)) {
+ debugfs_create_file("peers", 0444, serial->debugfs,
+ serial, &fwtty_peers_fops);
+ debugfs_create_file("stats", 0444, serial->debugfs,
+ serial, &fwtty_stats_fops);
+ }
+ }
+
+ list_add_rcu(&serial->list, &fwserial_list);
+
+ fwtty_notice(&unit, "TTY over FireWire on device %s (guid %016llx)\n",
+ dev_name(card->device), (unsigned long long) card->guid);
+
+ err = fwserial_add_peer(serial, unit);
+ if (!err)
+ return 0;
+
+ fwtty_err(&unit, "unable to add peer unit device (%d)\n", err);
+
+ /* fall-through to error processing */
+ debugfs_remove_recursive(serial->debugfs);
+
+ list_del_rcu(&serial->list);
+ if (create_loop_dev)
+ tty_unregister_device(fwloop_driver,
+ loop_idx(serial->ports[j]));
+unregister_ttys:
+ for (--j; j >= 0; --j)
+ tty_unregister_device(fwtty_driver, serial->ports[j]->index);
+ kref_put(&serial->kref, fwserial_destroy);
+ return err;
+
+free_ports:
+ for (--i; i >= 0; --i) {
+ tty_port_destroy(&serial->ports[i]->port);
+ kfree(serial->ports[i]);
+ }
+ kfree(serial);
+ return err;
+}
+
+/**
+ * fwserial_probe: bus probe function for firewire 'serial' unit devices
+ *
+ * A 'serial' unit device is created and probed as a result of:
+ * - declaring a ieee1394 bus id table for 'devices' matching a fabricated
+ * 'serial' unit specifier id
+ * - adding a unit directory to the config ROM(s) for a 'serial' unit
+ *
+ * The firewire core registers unit devices by enumerating unit directories
+ * of a node's config ROM after reading the config ROM when a new node is
+ * added to the bus topology after a bus reset.
+ *
+ * The practical implications of this are:
+ * - this probe is called for both local and remote nodes that have a 'serial'
+ * unit directory in their config ROM (that matches the specifiers in
+ * fwserial_id_table).
+ * - no specific order is enforced for local vs. remote unit devices
+ *
+ * This unit driver copes with the lack of specific order in the same way the
+ * firewire net driver does -- each probe, for either a local or remote unit
+ * device, is treated as a 'peer' (has a struct fwtty_peer instance) and the
+ * first peer created for a given fw_card (tracked by the global fwserial_list)
+ * creates the underlying TTYs (aggregated in a fw_serial instance).
+ *
+ * NB: an early attempt to differentiate local & remote unit devices by creating
+ * peers only for remote units and fw_serial instances (with their
+ * associated TTY devices) only for local units was discarded. Managing
+ * the peer lifetimes on device removal proved too complicated.
+ *
+ * fwserial_probe/fwserial_remove are effectively serialized by the
+ * fwserial_list_mutex. This is necessary because the addition of the first peer
+ * for a given fw_card will trigger the creation of the fw_serial for that
+ * fw_card, which must not simultaneously contend with the removal of the
+ * last peer for a given fw_card triggering the destruction of the same
+ * fw_serial for the same fw_card.
+ */
+static int fwserial_probe(struct fw_unit *unit,
+ const struct ieee1394_device_id *id)
+{
+ struct fw_serial *serial;
+ int err;
+
+ mutex_lock(&fwserial_list_mutex);
+ serial = fwserial_lookup(fw_parent_device(unit)->card);
+ if (!serial)
+ err = fwserial_create(unit);
+ else
+ err = fwserial_add_peer(serial, unit);
+ mutex_unlock(&fwserial_list_mutex);
+ return err;
+}
+
+/**
+ * fwserial_remove: bus removal function for firewire 'serial' unit devices
+ *
+ * The corresponding 'peer' for this unit device is removed from the list of
+ * peers for the associated fw_serial (which has a 1:1 correspondence with a
+ * specific fw_card). If this is the last peer being removed, then trigger
+ * the destruction of the underlying TTYs.
+ */
+static void fwserial_remove(struct fw_unit *unit)
+{
+ struct fwtty_peer *peer = dev_get_drvdata(&unit->device);
+ struct fw_serial *serial = peer->serial;
+ int i;
+
+ mutex_lock(&fwserial_list_mutex);
+ fwserial_remove_peer(peer);
+
+ if (list_empty(&serial->peer_list)) {
+ /* unlink from the fwserial_list here */
+ list_del_rcu(&serial->list);
+
+ debugfs_remove_recursive(serial->debugfs);
+
+ for (i = 0; i < num_ttys; ++i)
+ fwserial_close_port(fwtty_driver, serial->ports[i]);
+ if (create_loop_dev)
+ fwserial_close_port(fwloop_driver, serial->ports[i]);
+ kref_put(&serial->kref, fwserial_destroy);
+ }
+ mutex_unlock(&fwserial_list_mutex);
+}
+
+/**
+ * fwserial_update: bus update function for 'firewire' serial unit devices
+ *
+ * Updates the new node_id and bus generation for this peer. Note that locking
+ * is unnecessary; but careful memory barrier usage is important to enforce the
+ * load and store order of generation & node_id.
+ *
+ * The fw-core orders the write of node_id before generation in the parent
+ * fw_device to ensure that a stale node_id cannot be used with a current
+ * bus generation. So the generation value must be read before the node_id.
+ *
+ * In turn, this orders the write of node_id before generation in the peer to
+ * also ensure a stale node_id cannot be used with a current bus generation.
+ */
+static void fwserial_update(struct fw_unit *unit)
+{
+ struct fw_device *parent = fw_parent_device(unit);
+ struct fwtty_peer *peer = dev_get_drvdata(&unit->device);
+ int generation;
+
+ generation = parent->generation;
+ smp_rmb();
+ peer->node_id = parent->node_id;
+ smp_wmb();
+ peer->generation = generation;
+}
+
+static const struct ieee1394_device_id fwserial_id_table[] = {
+ {
+ .match_flags = IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .specifier_id = LINUX_VENDOR_ID,
+ .version = FWSERIAL_VERSION,
+ },
+ { }
+};
+
+static struct fw_driver fwserial_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .bus = &fw_bus_type,
+ },
+ .probe = fwserial_probe,
+ .update = fwserial_update,
+ .remove = fwserial_remove,
+ .id_table = fwserial_id_table,
+};
+
+#define FW_UNIT_SPECIFIER(id) ((CSR_SPECIFIER_ID << 24) | (id))
+#define FW_UNIT_VERSION(ver) ((CSR_VERSION << 24) | (ver))
+#define FW_UNIT_ADDRESS(ofs) (((CSR_OFFSET | CSR_DEPENDENT_INFO) << 24) \
+ | (((ofs) - CSR_REGISTER_BASE) >> 2))
+/* XXX: config ROM definitons could be improved with semi-automated offset
+ * and length calculation
+ */
+#define FW_ROM_LEN(quads) ((quads) << 16)
+#define FW_ROM_DESCRIPTOR(ofs) (((CSR_LEAF | CSR_DESCRIPTOR) << 24) | (ofs))
+
+struct fwserial_unit_directory_data {
+ u32 len_crc;
+ u32 unit_specifier;
+ u32 unit_sw_version;
+ u32 unit_addr_offset;
+ u32 desc1_ofs;
+ u32 desc1_len_crc;
+ u32 desc1_data[5];
+} __packed;
+
+static struct fwserial_unit_directory_data fwserial_unit_directory_data = {
+ .len_crc = FW_ROM_LEN(4),
+ .unit_specifier = FW_UNIT_SPECIFIER(LINUX_VENDOR_ID),
+ .unit_sw_version = FW_UNIT_VERSION(FWSERIAL_VERSION),
+ .desc1_ofs = FW_ROM_DESCRIPTOR(1),
+ .desc1_len_crc = FW_ROM_LEN(5),
+ .desc1_data = {
+ 0x00000000, /* type = text */
+ 0x00000000, /* enc = ASCII, lang EN */
+ 0x4c696e75, /* 'Linux TTY' */
+ 0x78205454,
+ 0x59000000,
+ },
+};
+
+static struct fw_descriptor fwserial_unit_directory = {
+ .length = sizeof(fwserial_unit_directory_data) / sizeof(u32),
+ .key = (CSR_DIRECTORY | CSR_UNIT) << 24,
+ .data = (u32 *)&fwserial_unit_directory_data,
+};
+
+/*
+ * The management address is in the unit space region but above other known
+ * address users (to keep wild writes from causing havoc)
+ */
+static const struct fw_address_region fwserial_mgmt_addr_region = {
+ .start = CSR_REGISTER_BASE + 0x1e0000ULL,
+ .end = 0x1000000000000ULL,
+};
+
+static struct fw_address_handler fwserial_mgmt_addr_handler;
+
+/**
+ * fwserial_handle_plug_req - handle VIRT_CABLE_PLUG request work
+ * @work: ptr to peer->work
+ *
+ * Attempts to complete the VIRT_CABLE_PLUG handshake sequence for this peer.
+ *
+ * This checks for a collided request-- ie, that a VIRT_CABLE_PLUG request was
+ * already sent to this peer. If so, the collision is resolved by comparing
+ * guid values; the loser sends the plug response.
+ *
+ * Note: if an error prevents a response, don't do anything -- the
+ * remote will timeout its request.
+ */
+static void fwserial_handle_plug_req(struct work_struct *work)
+{
+ struct fwtty_peer *peer = to_peer(work, work);
+ struct virt_plug_params *plug_req = &peer->work_params.plug_req;
+ struct fwtty_port *port;
+ struct fwserial_mgmt_pkt *pkt;
+ int rcode;
+
+ pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+ if (!pkt)
+ return;
+
+ port = fwserial_find_port(peer);
+
+ spin_lock_bh(&peer->lock);
+
+ switch (peer->state) {
+ case FWPS_NOT_ATTACHED:
+ if (!port) {
+ fwtty_err(&peer->unit, "no more ports avail\n");
+ fill_plug_rsp_nack(pkt);
+ } else {
+ peer->port = port;
+ fill_plug_rsp_ok(pkt, peer->port);
+ peer_set_state(peer, FWPS_PLUG_RESPONDING);
+ /* don't release claimed port */
+ port = NULL;
+ }
+ break;
+
+ case FWPS_PLUG_PENDING:
+ if (peer->serial->card->guid > peer->guid)
+ goto cleanup;
+
+ /* We lost - hijack the already-claimed port and send ok */
+ del_timer(&peer->timer);
+ fill_plug_rsp_ok(pkt, peer->port);
+ peer_set_state(peer, FWPS_PLUG_RESPONDING);
+ break;
+
+ default:
+ fill_plug_rsp_nack(pkt);
+ }
+
+ spin_unlock_bh(&peer->lock);
+ if (port)
+ fwserial_release_port(port, false);
+
+ rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+ spin_lock_bh(&peer->lock);
+ if (peer->state == FWPS_PLUG_RESPONDING) {
+ if (rcode == RCODE_COMPLETE) {
+ struct fwtty_port *tmp = peer->port;
+
+ fwserial_virt_plug_complete(peer, plug_req);
+ spin_unlock_bh(&peer->lock);
+
+ fwtty_write_port_status(tmp);
+ spin_lock_bh(&peer->lock);
+ } else {
+ fwtty_err(&peer->unit, "PLUG_RSP error (%d)\n", rcode);
+ port = peer_revert_state(peer);
+ }
+ }
+cleanup:
+ spin_unlock_bh(&peer->lock);
+ if (port)
+ fwserial_release_port(port, false);
+ kfree(pkt);
+ return;
+}
+
+static void fwserial_handle_unplug_req(struct work_struct *work)
+{
+ struct fwtty_peer *peer = to_peer(work, work);
+ struct fwtty_port *port = NULL;
+ struct fwserial_mgmt_pkt *pkt;
+ int rcode;
+
+ pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+ if (!pkt)
+ return;
+
+ spin_lock_bh(&peer->lock);
+
+ switch (peer->state) {
+ case FWPS_ATTACHED:
+ fill_unplug_rsp_ok(pkt);
+ peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+ break;
+
+ case FWPS_UNPLUG_PENDING:
+ if (peer->serial->card->guid > peer->guid)
+ goto cleanup;
+
+ /* We lost - send unplug rsp */
+ del_timer(&peer->timer);
+ fill_unplug_rsp_ok(pkt);
+ peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+ break;
+
+ default:
+ fill_unplug_rsp_nack(pkt);
+ }
+
+ spin_unlock_bh(&peer->lock);
+
+ rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+ spin_lock_bh(&peer->lock);
+ if (peer->state == FWPS_UNPLUG_RESPONDING) {
+ if (rcode != RCODE_COMPLETE)
+ fwtty_err(&peer->unit, "UNPLUG_RSP error (%d)\n",
+ rcode);
+ port = peer_revert_state(peer);
+ }
+cleanup:
+ spin_unlock_bh(&peer->lock);
+ if (port)
+ fwserial_release_port(port, true);
+ kfree(pkt);
+ return;
+}
+
+static int fwserial_parse_mgmt_write(struct fwtty_peer *peer,
+ struct fwserial_mgmt_pkt *pkt,
+ unsigned long long addr,
+ size_t len)
+{
+ struct fwtty_port *port = NULL;
+ bool reset = false;
+ int rcode;
+
+ if (addr != fwserial_mgmt_addr_handler.offset || len < sizeof(pkt->hdr))
+ return RCODE_ADDRESS_ERROR;
+
+ if (len != be16_to_cpu(pkt->hdr.len) ||
+ len != mgmt_pkt_expected_len(pkt->hdr.code))
+ return RCODE_DATA_ERROR;
+
+ spin_lock_bh(&peer->lock);
+ if (peer->state == FWPS_GONE) {
+ /*
+ * This should never happen - it would mean that the
+ * remote unit that just wrote this transaction was
+ * already removed from the bus -- and the removal was
+ * processed before we rec'd this transaction
+ */
+ fwtty_err(&peer->unit, "peer already removed\n");
+ spin_unlock_bh(&peer->lock);
+ return RCODE_ADDRESS_ERROR;
+ }
+
+ rcode = RCODE_COMPLETE;
+
+ fwtty_dbg(&peer->unit, "mgmt: hdr.code: %04hx\n", pkt->hdr.code);
+
+ switch (be16_to_cpu(pkt->hdr.code) & FWSC_CODE_MASK) {
+ case FWSC_VIRT_CABLE_PLUG:
+ if (work_pending(&peer->work)) {
+ fwtty_err(&peer->unit, "plug req: busy\n");
+ rcode = RCODE_CONFLICT_ERROR;
+
+ } else {
+ peer->work_params.plug_req = pkt->plug_req;
+ peer->workfn = fwserial_handle_plug_req;
+ queue_work(system_unbound_wq, &peer->work);
+ }
+ break;
+
+ case FWSC_VIRT_CABLE_PLUG_RSP:
+ if (peer->state != FWPS_PLUG_PENDING) {
+ rcode = RCODE_CONFLICT_ERROR;
+
+ } else if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK) {
+ fwtty_notice(&peer->unit, "NACK plug rsp\n");
+ port = peer_revert_state(peer);
+
+ } else {
+ struct fwtty_port *tmp = peer->port;
+
+ fwserial_virt_plug_complete(peer, &pkt->plug_rsp);
+ spin_unlock_bh(&peer->lock);
+
+ fwtty_write_port_status(tmp);
+ spin_lock_bh(&peer->lock);
+ }
+ break;
+
+ case FWSC_VIRT_CABLE_UNPLUG:
+ if (work_pending(&peer->work)) {
+ fwtty_err(&peer->unit, "unplug req: busy\n");
+ rcode = RCODE_CONFLICT_ERROR;
+ } else {
+ peer->workfn = fwserial_handle_unplug_req;
+ queue_work(system_unbound_wq, &peer->work);
+ }
+ break;
+
+ case FWSC_VIRT_CABLE_UNPLUG_RSP:
+ if (peer->state != FWPS_UNPLUG_PENDING) {
+ rcode = RCODE_CONFLICT_ERROR;
+ } else {
+ if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK)
+ fwtty_notice(&peer->unit, "NACK unplug?\n");
+ port = peer_revert_state(peer);
+ reset = true;
+ }
+ break;
+
+ default:
+ fwtty_err(&peer->unit, "unknown mgmt code %d\n",
+ be16_to_cpu(pkt->hdr.code));
+ rcode = RCODE_DATA_ERROR;
+ }
+ spin_unlock_bh(&peer->lock);
+
+ if (port)
+ fwserial_release_port(port, reset);
+
+ return rcode;
+}
+
+/**
+ * fwserial_mgmt_handler: bus address handler for mgmt requests
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling virtual cable requests from remotes
+ * for all cards.
+ */
+static void fwserial_mgmt_handler(struct fw_card *card,
+ struct fw_request *request,
+ int tcode, int destination, int source,
+ int generation,
+ unsigned long long addr,
+ void *data, size_t len,
+ void *callback_data)
+{
+ struct fwserial_mgmt_pkt *pkt = data;
+ struct fwtty_peer *peer;
+ int rcode;
+
+ rcu_read_lock();
+ peer = __fwserial_peer_by_node_id(card, generation, source);
+ if (!peer) {
+ fwtty_dbg(card, "peer(%d:%x) not found\n", generation, source);
+ __dump_peer_list(card);
+ rcode = RCODE_CONFLICT_ERROR;
+
+ } else {
+ switch (tcode) {
+ case TCODE_WRITE_BLOCK_REQUEST:
+ rcode = fwserial_parse_mgmt_write(peer, pkt, addr, len);
+ break;
+
+ default:
+ rcode = RCODE_TYPE_ERROR;
+ }
+ }
+
+ rcu_read_unlock();
+ fw_send_response(card, request, rcode);
+}
+
+static int __init fwserial_init(void)
+{
+ int err, num_loops = !!(create_loop_dev);
+
+ /* XXX: placeholder for a "firewire" debugfs node */
+ fwserial_debugfs = debugfs_create_dir(KBUILD_MODNAME, NULL);
+
+ /* num_ttys/num_ports must not be set above the static alloc avail */
+ if (num_ttys + num_loops > MAX_CARD_PORTS)
+ num_ttys = MAX_CARD_PORTS - num_loops;
+ num_ports = num_ttys + num_loops;
+
+ fwtty_driver = tty_alloc_driver(MAX_TOTAL_PORTS, TTY_DRIVER_REAL_RAW
+ | TTY_DRIVER_DYNAMIC_DEV);
+ if (IS_ERR(fwtty_driver)) {
+ err = PTR_ERR(fwtty_driver);
+ return err;
+ }
+
+ fwtty_driver->driver_name = KBUILD_MODNAME;
+ fwtty_driver->name = tty_dev_name;
+ fwtty_driver->major = 0;
+ fwtty_driver->minor_start = 0;
+ fwtty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ fwtty_driver->subtype = SERIAL_TYPE_NORMAL;
+ fwtty_driver->init_termios = tty_std_termios;
+ fwtty_driver->init_termios.c_cflag |= CLOCAL;
+ tty_set_operations(fwtty_driver, &fwtty_ops);
+
+ err = tty_register_driver(fwtty_driver);
+ if (err) {
+ pr_err("register tty driver failed (%d)\n", err);
+ goto put_tty;
+ }
+
+ if (create_loop_dev) {
+ fwloop_driver = tty_alloc_driver(MAX_TOTAL_PORTS / num_ports,
+ TTY_DRIVER_REAL_RAW
+ | TTY_DRIVER_DYNAMIC_DEV);
+ if (IS_ERR(fwloop_driver)) {
+ err = PTR_ERR(fwloop_driver);
+ goto unregister_driver;
+ }
+
+ fwloop_driver->driver_name = KBUILD_MODNAME "_loop";
+ fwloop_driver->name = loop_dev_name;
+ fwloop_driver->major = 0;
+ fwloop_driver->minor_start = 0;
+ fwloop_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ fwloop_driver->subtype = SERIAL_TYPE_NORMAL;
+ fwloop_driver->init_termios = tty_std_termios;
+ fwloop_driver->init_termios.c_cflag |= CLOCAL;
+ tty_set_operations(fwloop_driver, &fwloop_ops);
+
+ err = tty_register_driver(fwloop_driver);
+ if (err) {
+ pr_err("register loop driver failed (%d)\n", err);
+ goto put_loop;
+ }
+ }
+
+ fwtty_txn_cache = kmem_cache_create("fwtty_txn_cache",
+ sizeof(struct fwtty_transaction),
+ 0, 0, fwtty_txn_constructor);
+ if (!fwtty_txn_cache) {
+ err = -ENOMEM;
+ goto unregister_loop;
+ }
+
+ /*
+ * Ideally, this address handler would be registered per local node
+ * (rather than the same handler for all local nodes). However,
+ * since the firewire core requires the config rom descriptor *before*
+ * the local unit device(s) are created, a single management handler
+ * must suffice for all local serial units.
+ */
+ fwserial_mgmt_addr_handler.length = sizeof(struct fwserial_mgmt_pkt);
+ fwserial_mgmt_addr_handler.address_callback = fwserial_mgmt_handler;
+
+ err = fw_core_add_address_handler(&fwserial_mgmt_addr_handler,
+ &fwserial_mgmt_addr_region);
+ if (err) {
+ pr_err("add management handler failed (%d)\n", err);
+ goto destroy_cache;
+ }
+
+ fwserial_unit_directory_data.unit_addr_offset =
+ FW_UNIT_ADDRESS(fwserial_mgmt_addr_handler.offset);
+ err = fw_core_add_descriptor(&fwserial_unit_directory);
+ if (err) {
+ pr_err("add unit descriptor failed (%d)\n", err);
+ goto remove_handler;
+ }
+
+ err = driver_register(&fwserial_driver.driver);
+ if (err) {
+ pr_err("register fwserial driver failed (%d)\n", err);
+ goto remove_descriptor;
+ }
+
+ return 0;
+
+remove_descriptor:
+ fw_core_remove_descriptor(&fwserial_unit_directory);
+remove_handler:
+ fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+destroy_cache:
+ kmem_cache_destroy(fwtty_txn_cache);
+unregister_loop:
+ if (create_loop_dev)
+ tty_unregister_driver(fwloop_driver);
+put_loop:
+ if (create_loop_dev)
+ put_tty_driver(fwloop_driver);
+unregister_driver:
+ tty_unregister_driver(fwtty_driver);
+put_tty:
+ put_tty_driver(fwtty_driver);
+ debugfs_remove_recursive(fwserial_debugfs);
+ return err;
+}
+
+static void __exit fwserial_exit(void)
+{
+ driver_unregister(&fwserial_driver.driver);
+ fw_core_remove_descriptor(&fwserial_unit_directory);
+ fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+ kmem_cache_destroy(fwtty_txn_cache);
+ if (create_loop_dev) {
+ tty_unregister_driver(fwloop_driver);
+ put_tty_driver(fwloop_driver);
+ }
+ tty_unregister_driver(fwtty_driver);
+ put_tty_driver(fwtty_driver);
+ debugfs_remove_recursive(fwserial_debugfs);
+}
+
+module_init(fwserial_init);
+module_exit(fwserial_exit);
+
+MODULE_AUTHOR("Peter Hurley (peter@hurleysoftware.com)");
+MODULE_DESCRIPTION("FireWire Serial TTY Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ieee1394, fwserial_id_table);
+MODULE_PARM_DESC(ttys, "Number of ttys to create for each local firewire node");
+MODULE_PARM_DESC(auto, "Auto-connect a tty to each firewire node discovered");
+MODULE_PARM_DESC(loop, "Create a loopback device, fwloop<n>, with ttys");
diff --git a/drivers/staging/fwserial/fwserial.h b/drivers/staging/fwserial/fwserial.h
new file mode 100644
index 00000000000..98b853d4acb
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.h
@@ -0,0 +1,369 @@
+#ifndef _FIREWIRE_FWSERIAL_H
+#define _FIREWIRE_FWSERIAL_H
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/list.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
+#include <linux/mutex.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+
+#include "dma_fifo.h"
+
+#ifdef FWTTY_PROFILING
+#define DISTRIBUTION_MAX_SIZE 8192
+#define DISTRIBUTION_MAX_INDEX (ilog2(DISTRIBUTION_MAX_SIZE) + 1)
+static inline void fwtty_profile_data(unsigned stat[], unsigned val)
+{
+ int n = (val) ? min(ilog2(val) + 1, DISTRIBUTION_MAX_INDEX) : 0;
+ ++stat[n];
+}
+#else
+#define DISTRIBUTION_MAX_INDEX 0
+#define fwtty_profile_data(st, n)
+#endif
+
+/* Parameters for both VIRT_CABLE_PLUG & VIRT_CABLE_PLUG_RSP mgmt codes */
+struct virt_plug_params {
+ __be32 status_hi;
+ __be32 status_lo;
+ __be32 fifo_hi;
+ __be32 fifo_lo;
+ __be32 fifo_len;
+};
+
+struct peer_work_params {
+ union {
+ struct virt_plug_params plug_req;
+ };
+};
+
+/**
+ * fwtty_peer: structure representing local & remote unit devices
+ * @unit: unit child device of fw_device node
+ * @serial: back pointer to associated fw_serial aggregate
+ * @guid: unique 64-bit guid for this unit device
+ * @generation: most recent bus generation
+ * @node_id: most recent node_id
+ * @speed: link speed of peer (0 = S100, 2 = S400, ... 5 = S3200)
+ * @mgmt_addr: bus addr region to write mgmt packets to
+ * @status_addr: bus addr register to write line status to
+ * @fifo_addr: bus addr region to write serial output to
+ * @fifo_len: max length for single write to fifo_addr
+ * @list: link for insertion into fw_serial's peer_list
+ * @rcu: for deferring peer reclamation
+ * @lock: spinlock to synchonize changes to state & port fields
+ * @work: only one work item can be queued at any one time
+ * Note: pending work is canceled prior to removal, so this
+ * peer is valid for at least the lifetime of the work function
+ * @work_params: parameter block for work functions
+ * @timer: timer for resetting peer state if remote request times out
+ * @state: current state
+ * @connect: work item for auto-connecting
+ * @connect_retries: # of connections already attempted
+ * @port: associated tty_port (usable if state == FWSC_ATTACHED)
+ */
+struct fwtty_peer {
+ struct fw_unit *unit;
+ struct fw_serial *serial;
+ u64 guid;
+ int generation;
+ int node_id;
+ unsigned speed;
+ int max_payload;
+ u64 mgmt_addr;
+
+ /* these are usable only if state == FWSC_ATTACHED */
+ u64 status_addr;
+ u64 fifo_addr;
+ int fifo_len;
+
+ struct list_head list;
+ struct rcu_head rcu;
+
+ spinlock_t lock;
+ work_func_t workfn;
+ struct work_struct work;
+ struct peer_work_params work_params;
+ struct timer_list timer;
+ int state;
+ struct delayed_work connect;
+ int connect_retries;
+
+ struct fwtty_port *port;
+};
+
+#define to_peer(ptr, field) (container_of(ptr, struct fwtty_peer, field))
+
+/* state values for fwtty_peer.state field */
+enum fwtty_peer_state {
+ FWPS_GONE,
+ FWPS_NOT_ATTACHED,
+ FWPS_ATTACHED,
+ FWPS_PLUG_PENDING,
+ FWPS_PLUG_RESPONDING,
+ FWPS_UNPLUG_PENDING,
+ FWPS_UNPLUG_RESPONDING,
+
+ FWPS_NO_MGMT_ADDR = -1,
+};
+
+#define CONNECT_RETRY_DELAY HZ
+#define MAX_CONNECT_RETRIES 10
+
+/* must be holding peer lock for these state funclets */
+static inline void peer_set_state(struct fwtty_peer *peer, int new)
+{
+ peer->state = new;
+}
+
+static inline struct fwtty_port *peer_revert_state(struct fwtty_peer *peer)
+{
+ struct fwtty_port *port = peer->port;
+
+ peer->port = NULL;
+ peer_set_state(peer, FWPS_NOT_ATTACHED);
+ return port;
+}
+
+struct fwserial_mgmt_pkt {
+ struct {
+ __be16 len;
+ __be16 code;
+ } hdr;
+ union {
+ struct virt_plug_params plug_req;
+ struct virt_plug_params plug_rsp;
+ };
+} __packed;
+
+/* fwserial_mgmt_packet codes */
+#define FWSC_RSP_OK 0x0000
+#define FWSC_RSP_NACK 0x8000
+#define FWSC_CODE_MASK 0x0fff
+
+#define FWSC_VIRT_CABLE_PLUG 1
+#define FWSC_VIRT_CABLE_UNPLUG 2
+#define FWSC_VIRT_CABLE_PLUG_RSP 3
+#define FWSC_VIRT_CABLE_UNPLUG_RSP 4
+
+/* 1 min. plug timeout -- suitable for userland authorization */
+#define VIRT_CABLE_PLUG_TIMEOUT (60 * HZ)
+
+struct stats {
+ unsigned xchars;
+ unsigned dropped;
+ unsigned tx_stall;
+ unsigned fifo_errs;
+ unsigned sent;
+ unsigned lost;
+ unsigned throttled;
+ unsigned reads[DISTRIBUTION_MAX_INDEX + 1];
+ unsigned writes[DISTRIBUTION_MAX_INDEX + 1];
+ unsigned txns[DISTRIBUTION_MAX_INDEX + 1];
+ unsigned unthrottle[DISTRIBUTION_MAX_INDEX + 1];
+};
+
+struct fwconsole_ops {
+ void (*notify)(int code, void *data);
+ void (*stats)(struct stats *stats, void *data);
+ void (*proc_show)(struct seq_file *m, void *data);
+};
+
+/* codes for console ops notify */
+#define FWCON_NOTIFY_ATTACH 1
+#define FWCON_NOTIFY_DETACH 2
+
+/**
+ * fwtty_port: structure used to track/represent underlying tty_port
+ * @port: underlying tty_port
+ * @device: tty device
+ * @index: index into port_table for this particular port
+ * note: minor = index + minor_start assigned by tty_alloc_driver()
+ * @serial: back pointer to the containing fw_serial
+ * @rx_handler: bus address handler for unique addr region used by remotes
+ * to communicate with this port. Every port uses
+ * fwtty_port_handler() for per port transactions.
+ * @fwcon_ops: ops for attached fw_console (if any)
+ * @con_data: private data for fw_console
+ * @wait_tx: waitqueue for sleeping until writer/drain completes tx
+ * @emit_breaks: delayed work responsible for generating breaks when the
+ * break line status is active
+ * @cps : characters per second computed from the termios settings
+ * @break_last: timestamp in jiffies from last emit_breaks
+ * @hangup: work responsible for HUPing when carrier is dropped/lost
+ * @mstatus: loose virtualization of LSR/MSR
+ * bits 15..0 correspond to TIOCM_* bits
+ * bits 19..16 reserved for mctrl
+ * bit 20 OOB_TX_THROTTLE
+ * bits 23..21 reserved
+ * bits 31..24 correspond to UART_LSR_* bits
+ * @lock: spinlock for protecting concurrent access to fields below it
+ * @mctrl: loose virtualization of MCR
+ * bits 15..0 correspond to TIOCM_* bits
+ * bit 16 OOB_RX_THROTTLE
+ * bits 19..17 reserved
+ * bits 31..20 reserved for mstatus
+ * @drain: delayed work scheduled to ensure that writes are flushed.
+ * The work can race with the writer but concurrent sending is
+ * prevented with the IN_TX flag. Scheduled under lock to
+ * limit scheduling when fifo has just been drained.
+ * @tx_fifo: fifo used to store & block-up writes for dma to remote
+ * @max_payload: max bytes transmissable per dma (based on peer's max_payload)
+ * @status_mask: UART_LSR_* bitmask significant to rx (based on termios)
+ * @ignore_mask: UART_LSR_* bitmask of states to ignore (also based on termios)
+ * @break_ctl: if set, port is 'sending break' to remote
+ * @write_only: self-explanatory
+ * @overrun: previous rx was lost (partially or completely)
+ * @loopback: if set, port is in loopback mode
+ * @flags: atomic bit flags
+ * bit 0: IN_TX - gate to allow only one cpu to send from the dma fifo
+ * at a time.
+ * bit 1: STOP_TX - force tx to exit while sending
+ * @peer: rcu-pointer to associated fwtty_peer (if attached)
+ * NULL if no peer attached
+ * @icount: predefined statistics reported by the TIOCGICOUNT ioctl
+ * @stats: additional statistics reported in /proc/tty/driver/firewire_serial
+ */
+struct fwtty_port {
+ struct tty_port port;
+ struct device *device;
+ unsigned index;
+ struct fw_serial *serial;
+ struct fw_address_handler rx_handler;
+
+ struct fwconsole_ops *fwcon_ops;
+ void *con_data;
+
+ wait_queue_head_t wait_tx;
+ struct delayed_work emit_breaks;
+ unsigned cps;
+ unsigned long break_last;
+
+ struct work_struct hangup;
+
+ unsigned mstatus;
+
+ spinlock_t lock;
+ unsigned mctrl;
+ struct delayed_work drain;
+ struct dma_fifo tx_fifo;
+ int max_payload;
+ unsigned status_mask;
+ unsigned ignore_mask;
+ unsigned break_ctl:1,
+ write_only:1,
+ overrun:1,
+ loopback:1;
+ unsigned long flags;
+
+ struct fwtty_peer __rcu *peer;
+
+ struct async_icount icount;
+ struct stats stats;
+};
+
+#define to_port(ptr, field) (container_of(ptr, struct fwtty_port, field))
+
+/* bit #s for flags field */
+#define IN_TX 0
+#define STOP_TX 1
+
+/* bitmasks for special mctrl/mstatus bits */
+#define OOB_RX_THROTTLE 0x00010000
+#define MCTRL_RSRVD 0x000e0000
+#define OOB_TX_THROTTLE 0x00100000
+#define MSTATUS_RSRVD 0x00e00000
+
+#define MCTRL_MASK (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | TIOCM_OUT2 | \
+ TIOCM_LOOP | OOB_RX_THROTTLE | MCTRL_RSRVD)
+
+/* XXX even every 1/50th secs. may be unnecessarily accurate */
+/* delay in jiffies between brk emits */
+#define FREQ_BREAKS (HZ / 50)
+
+/* Ports are allocated in blocks of num_ports for each fw_card */
+#define MAX_CARD_PORTS CONFIG_FWTTY_MAX_CARD_PORTS
+#define MAX_TOTAL_PORTS CONFIG_FWTTY_MAX_TOTAL_PORTS
+
+/* tuning parameters */
+#define FWTTY_PORT_TXFIFO_LEN 4096
+#define FWTTY_PORT_MAX_PEND_DMA 8 /* costs a cache line per pend */
+#define DRAIN_THRESHOLD 1024
+#define MAX_ASYNC_PAYLOAD 4096 /* ohci-defined limit */
+#define WRITER_MINIMUM 128
+/* TODO: how to set watermark to AR context size? see fwtty_rx() */
+#define HIGH_WATERMARK 32768 /* AR context is 32K */
+
+/*
+ * Size of bus addr region above 4GB used per port as the recv addr
+ * - must be at least as big as the MAX_ASYNC_PAYLOAD
+ */
+#define FWTTY_PORT_RXFIFO_LEN MAX_ASYNC_PAYLOAD
+
+/**
+ * fw_serial: aggregate used to associate tty ports with specific fw_card
+ * @card: fw_card associated with this fw_serial device (1:1 association)
+ * @kref: reference-counted multi-port management allows delayed destroy
+ * @self: local unit device as 'peer'. Not valid until local unit device
+ * is enumerated.
+ * @list: link for insertion into fwserial_list
+ * @peer_list: list of local & remote unit devices attached to this card
+ * @ports: fixed array of tty_ports provided by this serial device
+ */
+struct fw_serial {
+ struct fw_card *card;
+ struct kref kref;
+
+ struct dentry *debugfs;
+ struct fwtty_peer *self;
+
+ struct list_head list;
+ struct list_head peer_list;
+
+ struct fwtty_port *ports[MAX_CARD_PORTS];
+};
+
+#define to_serial(ptr, field) (container_of(ptr, struct fw_serial, field))
+
+#define TTY_DEV_NAME "fwtty" /* ttyFW was taken */
+static const char tty_dev_name[] = TTY_DEV_NAME;
+static const char loop_dev_name[] = "fwloop";
+
+extern struct tty_driver *fwtty_driver;
+
+struct fwtty_port *fwtty_port_get(unsigned index);
+void fwtty_port_put(struct fwtty_port *port);
+
+static inline void fwtty_bind_console(struct fwtty_port *port,
+ struct fwconsole_ops *fwcon_ops,
+ void *data)
+{
+ port->con_data = data;
+ port->fwcon_ops = fwcon_ops;
+}
+
+/*
+ * Returns the max send async payload size in bytes based on the unit device
+ * link speed. Self-limiting asynchronous bandwidth (via reducing the payload)
+ * is not necessary and does not work, because
+ * 1) asynchronous traffic will absorb all available bandwidth (less that
+ * being used for isochronous traffic)
+ * 2) isochronous arbitration always wins.
+ */
+static inline int link_speed_to_max_payload(unsigned speed)
+{
+ /* Max async payload is 4096 - see IEEE 1394-2008 tables 6-4, 16-18 */
+ return min(512 << speed, 4096);
+}
+
+#endif /* _FIREWIRE_FWSERIAL_H */
diff --git a/drivers/staging/gdm724x/Kconfig b/drivers/staging/gdm724x/Kconfig
new file mode 100644
index 00000000000..0a1f090bbf3
--- /dev/null
+++ b/drivers/staging/gdm724x/Kconfig
@@ -0,0 +1,15 @@
+#
+# GCT GDM724x LTE driver configuration
+#
+
+config LTE_GDM724X
+ tristate "GCT GDM724x LTE support"
+ depends on NET && USB && TTY && m
+ help
+ This driver supports GCT GDM724x LTE chip based USB modem devices.
+ It exposes 4 network devices to be used per PDN and 2 tty devices to be
+ used for AT commands and DM monitoring applications.
+ The modules will be called gdmulte.ko and gdmtty.ko
+
+ GCT-ATCx can be used for AT Commands
+ GCT-DMx can be used for LTE protocol monitoring
diff --git a/drivers/staging/gdm724x/Makefile b/drivers/staging/gdm724x/Makefile
new file mode 100644
index 00000000000..ba7f11a6a09
--- /dev/null
+++ b/drivers/staging/gdm724x/Makefile
@@ -0,0 +1,7 @@
+obj-$(CONFIG_LTE_GDM724X) := gdmulte.o
+gdmulte-y += gdm_lte.o netlink_k.o
+gdmulte-y += gdm_usb.o gdm_endian.o
+
+obj-$(CONFIG_LTE_GDM724X) += gdmtty.o
+gdmtty-y := gdm_tty.o gdm_mux.o
+
diff --git a/drivers/staging/gdm724x/TODO b/drivers/staging/gdm724x/TODO
new file mode 100644
index 00000000000..b2b571ecb06
--- /dev/null
+++ b/drivers/staging/gdm724x/TODO
@@ -0,0 +1,16 @@
+TODO:
+- Clean up coding style to meet kernel standard. (80 line limit, netdev_err)
+- Remove test for host endian
+- Remove confusing macros (endian, hci_send, sdu_send, rcv_with_cb)
+- Fixes for every instances of function returning -1
+- Check for skb->len in gdm_lte_emulate_arp()
+- Use ALIGN() macro for dummy_cnt in up_to_host()
+- Error handling in init_usb()
+- Explain reason for multiples of 512 bytes in alloc_tx_struct()
+- Review use of atomic allocation for tx structs
+- No error checking for alloc_tx_struct in do_tx()
+- fix up static tty port allocation to be dynamic
+
+Patches to:
+ Jonathan Kim <jonathankim@gctsemi.com>
+ Dean ahn <deanahn@gctsemi.com>
diff --git a/drivers/staging/gdm724x/gdm_endian.c b/drivers/staging/gdm724x/gdm_endian.c
new file mode 100644
index 00000000000..f6cc90ae9ba
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_endian.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 <linux/slab.h>
+#include "gdm_endian.h"
+
+void gdm_set_endian(struct gdm_endian *ed, u8 dev_endian)
+{
+ u8 a[2] = {0x12, 0x34};
+ u8 b[2] = {0, };
+ u16 c = 0x1234;
+
+ if (dev_endian == ENDIANNESS_BIG)
+ ed->dev_ed = ENDIANNESS_BIG;
+ else
+ ed->dev_ed = ENDIANNESS_LITTLE;
+
+ memcpy(b, &c, 2);
+
+ if (a[0] != b[0])
+ ed->host_ed = ENDIANNESS_LITTLE;
+ else
+ ed->host_ed = ENDIANNESS_BIG;
+
+}
+
+u16 gdm_cpu_to_dev16(struct gdm_endian *ed, u16 x)
+{
+ if (ed->dev_ed == ed->host_ed)
+ return x;
+
+ return Endian16_Swap(x);
+}
+
+u16 gdm_dev16_to_cpu(struct gdm_endian *ed, u16 x)
+{
+ if (ed->dev_ed == ed->host_ed)
+ return x;
+
+ return Endian16_Swap(x);
+}
+
+u32 gdm_cpu_to_dev32(struct gdm_endian *ed, u32 x)
+{
+ if (ed->dev_ed == ed->host_ed)
+ return x;
+
+ return Endian32_Swap(x);
+}
+
+u32 gdm_dev32_to_cpu(struct gdm_endian *ed, u32 x)
+{
+ if (ed->dev_ed == ed->host_ed)
+ return x;
+
+ return Endian32_Swap(x);
+}
diff --git a/drivers/staging/gdm724x/gdm_endian.h b/drivers/staging/gdm724x/gdm_endian.h
new file mode 100644
index 00000000000..9b2531ff908
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_endian.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __GDM_ENDIAN_H__
+#define __GDM_ENDIAN_H__
+
+#include <linux/types.h>
+
+#define Endian16_Swap(value) \
+ ((((u16)((value) & 0x00FF)) << 8) | \
+ (((u16)((value) & 0xFF00)) >> 8))
+
+#define Endian32_Swap(value) \
+ ((((u32)((value) & 0x000000FF)) << 24) | \
+ (((u32)((value) & 0x0000FF00)) << 8) | \
+ (((u32)((value) & 0x00FF0000)) >> 8) | \
+ (((u32)((value) & 0xFF000000)) >> 24))
+
+enum {
+ ENDIANNESS_MIN = 0,
+ ENDIANNESS_UNKNOWN,
+ ENDIANNESS_LITTLE,
+ ENDIANNESS_BIG,
+ ENDIANNESS_MIDDLE,
+ ENDIANNESS_MAX
+};
+
+struct gdm_endian {
+ u8 dev_ed;
+ u8 host_ed;
+};
+
+void gdm_set_endian(struct gdm_endian *ed, u8 dev_endian);
+u16 gdm_cpu_to_dev16(struct gdm_endian *ed, u16 x);
+u16 gdm_dev16_to_cpu(struct gdm_endian *ed, u16 x);
+u32 gdm_cpu_to_dev32(struct gdm_endian *ed, u32 x);
+u32 gdm_dev32_to_cpu(struct gdm_endian *ed, u32 x);
+
+#endif /*__GDM_ENDIAN_H__*/
diff --git a/drivers/staging/gdm724x/gdm_lte.c b/drivers/staging/gdm724x/gdm_lte.c
new file mode 100644
index 00000000000..64c55b99fda
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_lte.c
@@ -0,0 +1,944 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/in6.h>
+#include <linux/tcp.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <linux/uaccess.h>
+#include <net/ndisc.h>
+
+#include "gdm_lte.h"
+#include "netlink_k.h"
+#include "hci.h"
+#include "hci_packet.h"
+#include "gdm_endian.h"
+
+/*
+ * Netlink protocol number
+ */
+#define NETLINK_LTE 30
+
+/*
+ * Default MTU Size
+ */
+#define DEFAULT_MTU_SIZE 1500
+
+#define IP_VERSION_4 4
+#define IP_VERSION_6 6
+
+static struct {
+ int ref_cnt;
+ struct sock *sock;
+} lte_event;
+
+static struct device_type wwan_type = {
+ .name = "wwan",
+};
+
+static int gdm_lte_open(struct net_device *dev)
+{
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int gdm_lte_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+static int gdm_lte_set_config(struct net_device *dev, struct ifmap *map)
+{
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+ return 0;
+}
+
+static void tx_complete(void *arg)
+{
+ struct nic *nic = arg;
+
+ if (netif_queue_stopped(nic->netdev))
+ netif_wake_queue(nic->netdev);
+}
+
+static int gdm_lte_rx(struct sk_buff *skb, struct nic *nic, int nic_type)
+{
+ int ret;
+
+ ret = netif_rx_ni(skb);
+ if (ret == NET_RX_DROP) {
+ nic->stats.rx_dropped++;
+ } else {
+ nic->stats.rx_packets++;
+ nic->stats.rx_bytes += skb->len + ETH_HLEN;
+ }
+
+ return 0;
+}
+
+static int gdm_lte_emulate_arp(struct sk_buff *skb_in, u32 nic_type)
+{
+ struct nic *nic = netdev_priv(skb_in->dev);
+ struct sk_buff *skb_out;
+ struct ethhdr eth;
+ struct vlan_ethhdr vlan_eth;
+ struct arphdr *arp_in;
+ struct arphdr *arp_out;
+ struct arpdata {
+ u8 ar_sha[ETH_ALEN];
+ u8 ar_sip[4];
+ u8 ar_tha[ETH_ALEN];
+ u8 ar_tip[4];
+ };
+ struct arpdata *arp_data_in;
+ struct arpdata *arp_data_out;
+ u8 arp_temp[60];
+ void *mac_header_data;
+ u32 mac_header_len;
+
+ /* Format the mac header so that it can be put to skb */
+ if (ntohs(((struct ethhdr *)skb_in->data)->h_proto) == ETH_P_8021Q) {
+ memcpy(&vlan_eth, skb_in->data, sizeof(struct vlan_ethhdr));
+ mac_header_data = &vlan_eth;
+ mac_header_len = VLAN_ETH_HLEN;
+ } else {
+ memcpy(&eth, skb_in->data, sizeof(struct ethhdr));
+ mac_header_data = &eth;
+ mac_header_len = ETH_HLEN;
+ }
+
+ /* Get the pointer of the original request */
+ arp_in = (struct arphdr *)(skb_in->data + mac_header_len);
+ arp_data_in = (struct arpdata *)(skb_in->data + mac_header_len +
+ sizeof(struct arphdr));
+
+ /* Get the pointer of the outgoing response */
+ arp_out = (struct arphdr *)arp_temp;
+ arp_data_out = (struct arpdata *)(arp_temp + sizeof(struct arphdr));
+
+ /* Copy the arp header */
+ memcpy(arp_out, arp_in, sizeof(struct arphdr));
+ arp_out->ar_op = htons(ARPOP_REPLY);
+
+ /* Copy the arp payload: based on 2 bytes of mac and fill the IP */
+ arp_data_out->ar_sha[0] = arp_data_in->ar_sha[0];
+ arp_data_out->ar_sha[1] = arp_data_in->ar_sha[1];
+ memcpy(&arp_data_out->ar_sha[2], &arp_data_in->ar_tip[0], 4);
+ memcpy(&arp_data_out->ar_sip[0], &arp_data_in->ar_tip[0], 4);
+ memcpy(&arp_data_out->ar_tha[0], &arp_data_in->ar_sha[0], 6);
+ memcpy(&arp_data_out->ar_tip[0], &arp_data_in->ar_sip[0], 4);
+
+ /* Fill the destination mac with source mac of the received packet */
+ memcpy(mac_header_data, mac_header_data + ETH_ALEN, ETH_ALEN);
+ /* Fill the source mac with nic's source mac */
+ memcpy(mac_header_data + ETH_ALEN, nic->src_mac_addr, ETH_ALEN);
+
+ /* Alloc skb and reserve align */
+ skb_out = dev_alloc_skb(skb_in->len);
+ if (!skb_out)
+ return -ENOMEM;
+ skb_reserve(skb_out, NET_IP_ALIGN);
+
+ memcpy(skb_put(skb_out, mac_header_len), mac_header_data,
+ mac_header_len);
+ memcpy(skb_put(skb_out, sizeof(struct arphdr)), arp_out,
+ sizeof(struct arphdr));
+ memcpy(skb_put(skb_out, sizeof(struct arpdata)), arp_data_out,
+ sizeof(struct arpdata));
+
+ skb_out->protocol = ((struct ethhdr *)mac_header_data)->h_proto;
+ skb_out->dev = skb_in->dev;
+ skb_reset_mac_header(skb_out);
+ skb_pull(skb_out, ETH_HLEN);
+
+ gdm_lte_rx(skb_out, nic, nic_type);
+
+ return 0;
+}
+
+static int icmp6_checksum(struct ipv6hdr *ipv6, u16 *ptr, int len)
+{
+ unsigned short *w = ptr;
+ int sum = 0;
+ int i;
+
+ union {
+ struct {
+ u8 ph_src[16];
+ u8 ph_dst[16];
+ u32 ph_len;
+ u8 ph_zero[3];
+ u8 ph_nxt;
+ } ph __packed;
+ u16 pa[20];
+ } pseudo_header;
+
+ memset(&pseudo_header, 0, sizeof(pseudo_header));
+ memcpy(&pseudo_header.ph.ph_src, &ipv6->saddr.in6_u.u6_addr8, 16);
+ memcpy(&pseudo_header.ph.ph_dst, &ipv6->daddr.in6_u.u6_addr8, 16);
+ pseudo_header.ph.ph_len = ipv6->payload_len;
+ pseudo_header.ph.ph_nxt = ipv6->nexthdr;
+
+ w = (u16 *)&pseudo_header;
+ for (i = 0; i < ARRAY_SIZE(pseudo_header.pa); i++)
+ sum += pseudo_header.pa[i];
+
+ w = ptr;
+ while (len > 1) {
+ sum += *w++;
+ len -= 2;
+ }
+
+ sum = (sum >> 16) + (sum & 0xFFFF);
+ sum += (sum >> 16);
+ sum = ~sum & 0xffff;
+
+ return sum;
+}
+
+static int gdm_lte_emulate_ndp(struct sk_buff *skb_in, u32 nic_type)
+{
+ struct nic *nic = netdev_priv(skb_in->dev);
+ struct sk_buff *skb_out;
+ struct ethhdr eth;
+ struct vlan_ethhdr vlan_eth;
+ struct neighbour_advertisement {
+ u8 target_address[16];
+ u8 type;
+ u8 length;
+ u8 link_layer_address[6];
+ };
+ struct neighbour_advertisement na;
+ struct neighbour_solicitation {
+ u8 target_address[16];
+ };
+ struct neighbour_solicitation *ns;
+ struct ipv6hdr *ipv6_in;
+ struct ipv6hdr ipv6_out;
+ struct icmp6hdr *icmp6_in;
+ struct icmp6hdr icmp6_out;
+
+ void *mac_header_data;
+ u32 mac_header_len;
+
+ /* Format the mac header so that it can be put to skb */
+ if (ntohs(((struct ethhdr *)skb_in->data)->h_proto) == ETH_P_8021Q) {
+ memcpy(&vlan_eth, skb_in->data, sizeof(struct vlan_ethhdr));
+ if (ntohs(vlan_eth.h_vlan_encapsulated_proto) != ETH_P_IPV6)
+ return -1;
+ mac_header_data = &vlan_eth;
+ mac_header_len = VLAN_ETH_HLEN;
+ } else {
+ memcpy(&eth, skb_in->data, sizeof(struct ethhdr));
+ if (ntohs(eth.h_proto) != ETH_P_IPV6)
+ return -1;
+ mac_header_data = &eth;
+ mac_header_len = ETH_HLEN;
+ }
+
+ /* Check if this is IPv6 ICMP packet */
+ ipv6_in = (struct ipv6hdr *)(skb_in->data + mac_header_len);
+ if (ipv6_in->version != 6 || ipv6_in->nexthdr != IPPROTO_ICMPV6)
+ return -1;
+
+ /* Check if this is NDP packet */
+ icmp6_in = (struct icmp6hdr *)(skb_in->data + mac_header_len +
+ sizeof(struct ipv6hdr));
+ if (icmp6_in->icmp6_type == NDISC_ROUTER_SOLICITATION) { /* Check RS */
+ return -1;
+ } else if (icmp6_in->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) {
+ /* Check NS */
+ u8 icmp_na[sizeof(struct icmp6hdr) +
+ sizeof(struct neighbour_advertisement)];
+ u8 zero_addr8[16] = {0,};
+
+ if (memcmp(ipv6_in->saddr.in6_u.u6_addr8, zero_addr8, 16) == 0)
+ /* Duplicate Address Detection: Source IP is all zero */
+ return 0;
+
+ icmp6_out.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT;
+ icmp6_out.icmp6_code = 0;
+ icmp6_out.icmp6_cksum = 0;
+ icmp6_out.icmp6_dataun.un_data32[0] = htonl(0x60000000); /* R=0, S=1, O=1 */
+
+ ns = (struct neighbour_solicitation *)
+ (skb_in->data + mac_header_len +
+ sizeof(struct ipv6hdr) + sizeof(struct icmp6hdr));
+ memcpy(&na.target_address, ns->target_address, 16);
+ na.type = 0x02;
+ na.length = 1;
+ na.link_layer_address[0] = 0x00;
+ na.link_layer_address[1] = 0x0a;
+ na.link_layer_address[2] = 0x3b;
+ na.link_layer_address[3] = 0xaf;
+ na.link_layer_address[4] = 0x63;
+ na.link_layer_address[5] = 0xc7;
+
+ memcpy(&ipv6_out, ipv6_in, sizeof(struct ipv6hdr));
+ memcpy(ipv6_out.saddr.in6_u.u6_addr8, &na.target_address, 16);
+ memcpy(ipv6_out.daddr.in6_u.u6_addr8,
+ ipv6_in->saddr.in6_u.u6_addr8, 16);
+ ipv6_out.payload_len = htons(sizeof(struct icmp6hdr) +
+ sizeof(struct neighbour_advertisement));
+
+ memcpy(icmp_na, &icmp6_out, sizeof(struct icmp6hdr));
+ memcpy(icmp_na + sizeof(struct icmp6hdr), &na,
+ sizeof(struct neighbour_advertisement));
+
+ icmp6_out.icmp6_cksum = icmp6_checksum(&ipv6_out,
+ (u16 *)icmp_na, sizeof(icmp_na));
+ } else {
+ return -1;
+ }
+
+ /* Fill the destination mac with source mac of the received packet */
+ memcpy(mac_header_data, mac_header_data + ETH_ALEN, ETH_ALEN);
+ /* Fill the source mac with nic's source mac */
+ memcpy(mac_header_data + ETH_ALEN, nic->src_mac_addr, ETH_ALEN);
+
+ /* Alloc skb and reserve align */
+ skb_out = dev_alloc_skb(skb_in->len);
+ if (!skb_out)
+ return -ENOMEM;
+ skb_reserve(skb_out, NET_IP_ALIGN);
+
+ memcpy(skb_put(skb_out, mac_header_len), mac_header_data,
+ mac_header_len);
+ memcpy(skb_put(skb_out, sizeof(struct ipv6hdr)), &ipv6_out,
+ sizeof(struct ipv6hdr));
+ memcpy(skb_put(skb_out, sizeof(struct icmp6hdr)), &icmp6_out,
+ sizeof(struct icmp6hdr));
+ memcpy(skb_put(skb_out, sizeof(struct neighbour_advertisement)), &na,
+ sizeof(struct neighbour_advertisement));
+
+ skb_out->protocol = ((struct ethhdr *)mac_header_data)->h_proto;
+ skb_out->dev = skb_in->dev;
+ skb_reset_mac_header(skb_out);
+ skb_pull(skb_out, ETH_HLEN);
+
+ gdm_lte_rx(skb_out, nic, nic_type);
+
+ return 0;
+}
+
+static s32 gdm_lte_tx_nic_type(struct net_device *dev, struct sk_buff *skb)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct ethhdr *eth;
+ struct vlan_ethhdr *vlan_eth;
+ struct iphdr *ip;
+ struct ipv6hdr *ipv6;
+ int mac_proto;
+ void *network_data;
+ u32 nic_type = 0;
+
+ /* NIC TYPE is based on the nic_id of this net_device */
+ nic_type = 0x00000010 | nic->nic_id;
+
+ /* Get ethernet protocol */
+ eth = (struct ethhdr *)skb->data;
+ if (ntohs(eth->h_proto) == ETH_P_8021Q) {
+ vlan_eth = (struct vlan_ethhdr *)skb->data;
+ mac_proto = ntohs(vlan_eth->h_vlan_encapsulated_proto);
+ network_data = skb->data + VLAN_ETH_HLEN;
+ nic_type |= NIC_TYPE_F_VLAN;
+ } else {
+ mac_proto = ntohs(eth->h_proto);
+ network_data = skb->data + ETH_HLEN;
+ }
+
+ /* Process packet for nic type */
+ switch (mac_proto) {
+ case ETH_P_ARP:
+ nic_type |= NIC_TYPE_ARP;
+ break;
+ case ETH_P_IP:
+ nic_type |= NIC_TYPE_F_IPV4;
+ ip = (struct iphdr *)network_data;
+
+ /* Check DHCPv4 */
+ if (ip->protocol == IPPROTO_UDP) {
+ struct udphdr *udp = (struct udphdr *)
+ (network_data + sizeof(struct iphdr));
+ if (ntohs(udp->dest) == 67 || ntohs(udp->dest) == 68)
+ nic_type |= NIC_TYPE_F_DHCP;
+ }
+ break;
+ case ETH_P_IPV6:
+ nic_type |= NIC_TYPE_F_IPV6;
+ ipv6 = (struct ipv6hdr *)network_data;
+
+ if (ipv6->nexthdr == IPPROTO_ICMPV6) /* Check NDP request */ {
+ struct icmp6hdr *icmp6 = (struct icmp6hdr *)
+ (network_data + sizeof(struct ipv6hdr));
+ if (icmp6->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION)
+ nic_type |= NIC_TYPE_ICMPV6;
+ } else if (ipv6->nexthdr == IPPROTO_UDP) /* Check DHCPv6 */ {
+ struct udphdr *udp = (struct udphdr *)
+ (network_data + sizeof(struct ipv6hdr));
+ if (ntohs(udp->dest) == 546 || ntohs(udp->dest) == 547)
+ nic_type |= NIC_TYPE_F_DHCP;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return nic_type;
+}
+
+static int gdm_lte_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ u32 nic_type;
+ void *data_buf;
+ int data_len;
+ int idx;
+ int ret = 0;
+
+ nic_type = gdm_lte_tx_nic_type(dev, skb);
+ if (nic_type == 0) {
+ netdev_err(dev, "tx - invalid nic_type\n");
+ return -1;
+ }
+
+ if (nic_type & NIC_TYPE_ARP) {
+ if (gdm_lte_emulate_arp(skb, nic_type) == 0) {
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ }
+
+ if (nic_type & NIC_TYPE_ICMPV6) {
+ if (gdm_lte_emulate_ndp(skb, nic_type) == 0) {
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ }
+
+ /*
+ * Need byte shift (that is, remove VLAN tag) if there is one
+ * For the case of ARP, this breaks the offset as vlan_ethhdr+4
+ * is treated as ethhdr However, it shouldn't be a problem as
+ * the response starts from arp_hdr and ethhdr is created by this
+ * driver based on the NIC mac
+ */
+ if (nic_type & NIC_TYPE_F_VLAN) {
+ struct vlan_ethhdr *vlan_eth = (struct vlan_ethhdr *)skb->data;
+ nic->vlan_id = ntohs(vlan_eth->h_vlan_TCI) & VLAN_VID_MASK;
+ data_buf = skb->data + (VLAN_ETH_HLEN - ETH_HLEN);
+ data_len = skb->len - (VLAN_ETH_HLEN - ETH_HLEN);
+ } else {
+ nic->vlan_id = 0;
+ data_buf = skb->data;
+ data_len = skb->len;
+ }
+
+ /* If it is a ICMPV6 packet, clear all the other bits :
+ * for backward compatibility with the firmware
+ */
+ if (nic_type & NIC_TYPE_ICMPV6)
+ nic_type = NIC_TYPE_ICMPV6;
+
+ /* If it is not a dhcp packet, clear all the flag bits :
+ * original NIC, otherwise the special flag (IPVX | DHCP)
+ */
+ if (!(nic_type & NIC_TYPE_F_DHCP))
+ nic_type &= NIC_TYPE_MASK;
+
+ ret = sscanf(dev->name, "lte%d", &idx);
+ if (ret != 1) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ ret = nic->phy_dev->send_sdu_func(nic->phy_dev->priv_dev,
+ data_buf, data_len,
+ nic->pdn_table.dft_eps_id, 0,
+ tx_complete, nic, idx,
+ nic_type);
+
+ if (ret == TX_NO_BUFFER || ret == TX_NO_SPC) {
+ netif_stop_queue(dev);
+ if (ret == TX_NO_BUFFER)
+ ret = 0;
+ else
+ ret = -ENOSPC;
+ } else if (ret == TX_NO_DEV) {
+ ret = -ENODEV;
+ }
+
+ /* Updates tx stats */
+ if (ret) {
+ nic->stats.tx_dropped++;
+ } else {
+ nic->stats.tx_packets++;
+ nic->stats.tx_bytes += data_len;
+ }
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static struct net_device_stats *gdm_lte_stats(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ return &nic->stats;
+}
+
+static int gdm_lte_event_send(struct net_device *dev, char *buf, int len)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct hci_packet *hci = (struct hci_packet *)buf;
+ int idx;
+ int ret;
+
+ ret = sscanf(dev->name, "lte%d", &idx);
+ if (ret != 1)
+ return -EINVAL;
+
+ return netlink_send(lte_event.sock, idx, 0, buf,
+ gdm_dev16_to_cpu(
+ nic->phy_dev->get_endian(
+ nic->phy_dev->priv_dev), hci->len)
+ + HCI_HEADER_SIZE);
+}
+
+static void gdm_lte_event_rcv(struct net_device *dev, u16 type,
+ void *msg, int len)
+{
+ struct nic *nic = netdev_priv(dev);
+
+ nic->phy_dev->send_hci_func(nic->phy_dev->priv_dev, msg, len, NULL,
+ NULL);
+}
+
+int gdm_lte_event_init(void)
+{
+ if (lte_event.ref_cnt == 0)
+ lte_event.sock = netlink_init(NETLINK_LTE, gdm_lte_event_rcv);
+
+ if (lte_event.sock) {
+ lte_event.ref_cnt++;
+ return 0;
+ }
+
+ pr_err("event init failed\n");
+ return -1;
+}
+
+void gdm_lte_event_exit(void)
+{
+ if (lte_event.sock && --lte_event.ref_cnt == 0) {
+ netlink_exit(lte_event.sock);
+ lte_event.sock = NULL;
+ }
+}
+
+static u8 find_dev_index(u32 nic_type)
+{
+ u8 index;
+
+ index = (u8)(nic_type & 0x0000000f);
+ if (index > MAX_NIC_TYPE)
+ index = 0;
+
+ return index;
+}
+
+static void gdm_lte_netif_rx(struct net_device *dev, char *buf,
+ int len, int flagged_nic_type)
+{
+ u32 nic_type;
+ struct nic *nic;
+ struct sk_buff *skb;
+ struct ethhdr eth;
+ struct vlan_ethhdr vlan_eth;
+ void *mac_header_data;
+ u32 mac_header_len;
+ char ip_version = 0;
+
+ nic_type = flagged_nic_type & NIC_TYPE_MASK;
+ nic = netdev_priv(dev);
+
+ if (flagged_nic_type & NIC_TYPE_F_DHCP) {
+ /* Change the destination mac address
+ * with the one requested the IP
+ */
+ if (flagged_nic_type & NIC_TYPE_F_IPV4) {
+ struct dhcp_packet {
+ u8 op; /* BOOTREQUEST or BOOTREPLY */
+ u8 htype; /* hardware address type.
+ * 1 = 10mb ethernet
+ */
+ u8 hlen; /* hardware address length */
+ u8 hops; /* used by relay agents only */
+ u32 xid; /* unique id */
+ u16 secs; /* elapsed since client began
+ * acquisition/renewal
+ */
+ u16 flags; /* only one flag so far: */
+ #define BROADCAST_FLAG 0x8000
+ /* "I need broadcast replies" */
+ u32 ciaddr; /* client IP (if client is in
+ * BOUND, RENEW or REBINDING state)
+ */
+ u32 yiaddr; /* 'your' (client) IP address */
+ /* IP address of next server to use in
+ * bootstrap, returned in DHCPOFFER,
+ * DHCPACK by server
+ */
+ u32 siaddr_nip;
+ u32 gateway_nip; /* relay agent IP address */
+ u8 chaddr[16]; /* link-layer client hardware
+ * address (MAC)
+ */
+ u8 sname[64]; /* server host name (ASCIZ) */
+ u8 file[128]; /* boot file name (ASCIZ) */
+ u32 cookie; /* fixed first four option
+ * bytes (99,130,83,99 dec)
+ */
+ } __packed;
+ void *addr = buf + sizeof(struct iphdr) +
+ sizeof(struct udphdr) +
+ offsetof(struct dhcp_packet, chaddr);
+ memcpy(nic->dest_mac_addr, addr, ETH_ALEN);
+ }
+ }
+
+ if (nic->vlan_id > 0) {
+ mac_header_data = (void *)&vlan_eth;
+ mac_header_len = VLAN_ETH_HLEN;
+ } else {
+ mac_header_data = (void *)&eth;
+ mac_header_len = ETH_HLEN;
+ }
+
+ /* Format the data so that it can be put to skb */
+ memcpy(mac_header_data, nic->dest_mac_addr, ETH_ALEN);
+ memcpy(mac_header_data + ETH_ALEN, nic->src_mac_addr, ETH_ALEN);
+
+ vlan_eth.h_vlan_TCI = htons(nic->vlan_id);
+ vlan_eth.h_vlan_proto = htons(ETH_P_8021Q);
+
+ if (nic_type == NIC_TYPE_ARP) {
+ /* Should be response: Only happens because
+ * there was a request from the host
+ */
+ eth.h_proto = htons(ETH_P_ARP);
+ vlan_eth.h_vlan_encapsulated_proto = htons(ETH_P_ARP);
+ } else {
+ ip_version = buf[0] >> 4;
+ if (ip_version == IP_VERSION_4) {
+ eth.h_proto = htons(ETH_P_IP);
+ vlan_eth.h_vlan_encapsulated_proto = htons(ETH_P_IP);
+ } else if (ip_version == IP_VERSION_6) {
+ eth.h_proto = htons(ETH_P_IPV6);
+ vlan_eth.h_vlan_encapsulated_proto = htons(ETH_P_IPV6);
+ } else {
+ netdev_err(dev, "Unknown IP version %d\n", ip_version);
+ return;
+ }
+ }
+
+ /* Alloc skb and reserve align */
+ skb = dev_alloc_skb(len + mac_header_len + NET_IP_ALIGN);
+ if (!skb)
+ return;
+ skb_reserve(skb, NET_IP_ALIGN);
+
+ memcpy(skb_put(skb, mac_header_len), mac_header_data, mac_header_len);
+ memcpy(skb_put(skb, len), buf, len);
+
+ skb->protocol = ((struct ethhdr *)mac_header_data)->h_proto;
+ skb->dev = dev;
+ skb_reset_mac_header(skb);
+ skb_pull(skb, ETH_HLEN);
+
+ gdm_lte_rx(skb, nic, nic_type);
+}
+
+static void gdm_lte_multi_sdu_pkt(struct phy_dev *phy_dev, char *buf, int len)
+{
+ struct net_device *dev;
+ struct multi_sdu *multi_sdu = (struct multi_sdu *)buf;
+ struct sdu *sdu = NULL;
+ u8 *data = (u8 *)multi_sdu->data;
+ u16 i = 0;
+ u16 num_packet;
+ u16 hci_len;
+ u16 cmd_evt;
+ u32 nic_type;
+ u8 index;
+
+ hci_len = gdm_dev16_to_cpu(phy_dev->get_endian(phy_dev->priv_dev),
+ multi_sdu->len);
+ num_packet = gdm_dev16_to_cpu(phy_dev->get_endian(phy_dev->priv_dev),
+ multi_sdu->num_packet);
+
+ for (i = 0; i < num_packet; i++) {
+ sdu = (struct sdu *)data;
+
+ cmd_evt = gdm_dev16_to_cpu(phy_dev->
+ get_endian(phy_dev->priv_dev), sdu->cmd_evt);
+ hci_len = gdm_dev16_to_cpu(phy_dev->
+ get_endian(phy_dev->priv_dev), sdu->len);
+ nic_type = gdm_dev32_to_cpu(phy_dev->
+ get_endian(phy_dev->priv_dev), sdu->nic_type);
+
+ if (cmd_evt != LTE_RX_SDU) {
+ pr_err("rx sdu wrong hci %04x\n", cmd_evt);
+ return;
+ }
+ if (hci_len < 12) {
+ pr_err("rx sdu invalid len %d\n", hci_len);
+ return;
+ }
+
+ index = find_dev_index(nic_type);
+ if (index < MAX_NIC_TYPE) {
+ dev = phy_dev->dev[index];
+ gdm_lte_netif_rx(dev, (char *)sdu->data,
+ (int)(hci_len-12), nic_type);
+ } else {
+ pr_err("rx sdu invalid nic_type :%x\n", nic_type);
+ }
+
+ data += ((hci_len+3) & 0xfffc) + HCI_HEADER_SIZE;
+ }
+}
+
+static void gdm_lte_pdn_table(struct net_device *dev, char *buf, int len)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct hci_pdn_table_ind *pdn_table = (struct hci_pdn_table_ind *)buf;
+
+ if (pdn_table->activate) {
+ nic->pdn_table.activate = pdn_table->activate;
+ nic->pdn_table.dft_eps_id = gdm_dev32_to_cpu(
+ nic->phy_dev->get_endian(
+ nic->phy_dev->priv_dev),
+ pdn_table->dft_eps_id);
+ nic->pdn_table.nic_type = gdm_dev32_to_cpu(
+ nic->phy_dev->get_endian(
+ nic->phy_dev->priv_dev),
+ pdn_table->nic_type);
+
+ netdev_info(dev, "pdn activated, nic_type=0x%x\n",
+ nic->pdn_table.nic_type);
+ } else {
+ memset(&nic->pdn_table, 0x00, sizeof(struct pdn_table));
+ netdev_info(dev, "pdn deactivated\n");
+ }
+}
+
+static int gdm_lte_receive_pkt(struct phy_dev *phy_dev, char *buf, int len)
+{
+ struct hci_packet *hci = (struct hci_packet *)buf;
+ struct hci_pdn_table_ind *pdn_table = (struct hci_pdn_table_ind *)buf;
+ struct sdu *sdu;
+ struct net_device *dev;
+ int ret = 0;
+ u16 cmd_evt;
+ u32 nic_type;
+ u8 index;
+
+ if (!len)
+ return ret;
+
+ cmd_evt = gdm_dev16_to_cpu(phy_dev->get_endian(phy_dev->priv_dev),
+ hci->cmd_evt);
+
+ dev = phy_dev->dev[0];
+ if (dev == NULL)
+ return 0;
+
+ switch (cmd_evt) {
+ case LTE_RX_SDU:
+ sdu = (struct sdu *)hci->data;
+ nic_type = gdm_dev32_to_cpu(phy_dev->
+ get_endian(phy_dev->priv_dev), sdu->nic_type);
+ index = find_dev_index(nic_type);
+ dev = phy_dev->dev[index];
+ gdm_lte_netif_rx(dev, hci->data, len, nic_type);
+ break;
+ case LTE_RX_MULTI_SDU:
+ gdm_lte_multi_sdu_pkt(phy_dev, buf, len);
+ break;
+ case LTE_LINK_ON_OFF_INDICATION:
+ netdev_info(dev, "link %s\n",
+ ((struct hci_connect_ind *)buf)->connect
+ ? "on" : "off");
+ break;
+ case LTE_PDN_TABLE_IND:
+ pdn_table = (struct hci_pdn_table_ind *)buf;
+ nic_type = gdm_dev32_to_cpu(phy_dev->
+ get_endian(phy_dev->priv_dev),
+ pdn_table->nic_type);
+ index = find_dev_index(nic_type);
+ dev = phy_dev->dev[index];
+ gdm_lte_pdn_table(dev, buf, len);
+ /* Fall through */
+ default:
+ ret = gdm_lte_event_send(dev, buf, len);
+ break;
+ }
+
+ return ret;
+}
+
+static int rx_complete(void *arg, void *data, int len, int context)
+{
+ struct phy_dev *phy_dev = (struct phy_dev *)arg;
+
+ return gdm_lte_receive_pkt(phy_dev, (char *)data, len);
+}
+
+void start_rx_proc(struct phy_dev *phy_dev)
+{
+ int i;
+
+ for (i = 0; i < MAX_RX_SUBMIT_COUNT; i++)
+ phy_dev->rcv_func(phy_dev->priv_dev,
+ rx_complete, phy_dev, USB_COMPLETE);
+}
+
+static struct net_device_ops gdm_netdev_ops = {
+ .ndo_open = gdm_lte_open,
+ .ndo_stop = gdm_lte_close,
+ .ndo_set_config = gdm_lte_set_config,
+ .ndo_start_xmit = gdm_lte_tx,
+ .ndo_get_stats = gdm_lte_stats,
+};
+
+static u8 gdm_lte_macaddr[ETH_ALEN] = {0x00, 0x0a, 0x3b, 0x00, 0x00, 0x00};
+
+static void form_mac_address(u8 *dev_addr, u8 *nic_src, u8 *nic_dest,
+ u8 *mac_address, u8 index)
+{
+ /* Form the dev_addr */
+ if (!mac_address)
+ memcpy(dev_addr, gdm_lte_macaddr, ETH_ALEN);
+ else
+ memcpy(dev_addr, mac_address, ETH_ALEN);
+
+ /* The last byte of the mac address
+ * should be less than or equal to 0xFC
+ */
+ dev_addr[ETH_ALEN-1] += index;
+
+ /* Create random nic src and copy the first
+ * 3 bytes to be the same as dev_addr
+ */
+ random_ether_addr(nic_src);
+ memcpy(nic_src, dev_addr, 3);
+
+ /* Copy the nic_dest from dev_addr*/
+ memcpy(nic_dest, dev_addr, ETH_ALEN);
+}
+
+static void validate_mac_address(u8 *mac_address)
+{
+ /* if zero address or multicast bit set, restore the default value */
+ if (is_zero_ether_addr(mac_address) || (mac_address[0] & 0x01)) {
+ pr_err("MAC invalid, restoring default\n");
+ memcpy(mac_address, gdm_lte_macaddr, 6);
+ }
+}
+
+int register_lte_device(struct phy_dev *phy_dev,
+ struct device *dev, u8 *mac_address)
+{
+ struct nic *nic;
+ struct net_device *net;
+ char pdn_dev_name[16];
+ int ret = 0;
+ u8 index;
+
+ validate_mac_address(mac_address);
+
+ for (index = 0; index < MAX_NIC_TYPE; index++) {
+ /* Create device name lteXpdnX */
+ sprintf(pdn_dev_name, "lte%%dpdn%d", index);
+
+ /* Allocate netdev */
+ net = alloc_netdev(sizeof(struct nic), pdn_dev_name,
+ ether_setup);
+ if (net == NULL) {
+ pr_err("alloc_netdev failed\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+ net->netdev_ops = &gdm_netdev_ops;
+ net->flags &= ~IFF_MULTICAST;
+ net->mtu = DEFAULT_MTU_SIZE;
+
+ nic = netdev_priv(net);
+ memset(nic, 0, sizeof(struct nic));
+ nic->netdev = net;
+ nic->phy_dev = phy_dev;
+ nic->nic_id = index;
+
+ form_mac_address(
+ net->dev_addr,
+ nic->src_mac_addr,
+ nic->dest_mac_addr,
+ mac_address,
+ index);
+
+ SET_NETDEV_DEV(net, dev);
+ SET_NETDEV_DEVTYPE(net, &wwan_type);
+
+ ret = register_netdev(net);
+ if (ret)
+ goto err;
+
+ netif_carrier_on(net);
+
+ phy_dev->dev[index] = net;
+ }
+
+ return 0;
+
+err:
+ unregister_lte_device(phy_dev);
+
+ return ret;
+}
+
+void unregister_lte_device(struct phy_dev *phy_dev)
+{
+ struct net_device *net;
+ int index;
+
+ for (index = 0; index < MAX_NIC_TYPE; index++) {
+ net = phy_dev->dev[index];
+ if (net == NULL)
+ continue;
+
+ unregister_netdev(net);
+ free_netdev(net);
+ }
+}
diff --git a/drivers/staging/gdm724x/gdm_lte.h b/drivers/staging/gdm724x/gdm_lte.h
new file mode 100644
index 00000000000..88414e5a70c
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_lte.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _GDM_LTE_H_
+#define _GDM_LTE_H_
+
+#include <linux/netdevice.h>
+#include <linux/types.h>
+
+#include "gdm_endian.h"
+
+#define MAX_NIC_TYPE 4
+#define MAX_RX_SUBMIT_COUNT 3
+#define DRIVER_VERSION "3.7.17.0"
+
+enum TX_ERROR_CODE {
+ TX_NO_ERROR = 0,
+ TX_NO_DEV,
+ TX_NO_SPC,
+ TX_NO_BUFFER,
+};
+
+enum CALLBACK_CONTEXT {
+ KERNEL_THREAD = 0,
+ USB_COMPLETE,
+};
+
+struct pdn_table {
+ u8 activate;
+ u32 dft_eps_id;
+ u32 nic_type;
+} __packed;
+
+struct nic;
+
+struct phy_dev {
+ void *priv_dev;
+ struct net_device *dev[MAX_NIC_TYPE];
+ int (*send_hci_func)(void *priv_dev, void *data, int len,
+ void (*cb)(void *cb_data), void *cb_data);
+ int (*send_sdu_func)(void *priv_dev, void *data, int len,
+ unsigned int dftEpsId, unsigned int epsId,
+ void (*cb)(void *cb_data), void *cb_data,
+ int dev_idx, int nic_type);
+ int (*rcv_func)(void *priv_dev,
+ int (*cb)(void *cb_data, void *data, int len,
+ int context),
+ void *cb_data, int context);
+ struct gdm_endian * (*get_endian)(void *priv_dev);
+};
+
+struct nic {
+ struct net_device *netdev;
+ struct phy_dev *phy_dev;
+ struct net_device_stats stats;
+ struct pdn_table pdn_table;
+ u8 dest_mac_addr[ETH_ALEN];
+ u8 src_mac_addr[ETH_ALEN];
+ u32 nic_id;
+ u16 vlan_id;
+};
+
+int gdm_lte_event_init(void);
+void gdm_lte_event_exit(void);
+
+void start_rx_proc(struct phy_dev *phy_dev);
+int register_lte_device(struct phy_dev *phy_dev, struct device *dev,
+ u8 *mac_address);
+void unregister_lte_device(struct phy_dev *phy_dev);
+
+#endif /* _GDM_LTE_H_ */
diff --git a/drivers/staging/gdm724x/gdm_mux.c b/drivers/staging/gdm724x/gdm_mux.c
new file mode 100644
index 00000000000..10ce2c1805b
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_mux.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/usb/cdc.h>
+
+#include "gdm_mux.h"
+
+static struct workqueue_struct *mux_rx_wq;
+
+static u16 packet_type[TTY_MAX_COUNT] = {0xF011, 0xF010};
+
+#define USB_DEVICE_CDC_DATA(vid, pid) \
+ .match_flags = \
+ USB_DEVICE_ID_MATCH_DEVICE |\
+ USB_DEVICE_ID_MATCH_INT_CLASS |\
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,\
+ .idVendor = vid,\
+ .idProduct = pid,\
+ .bInterfaceClass = USB_CLASS_COMM,\
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE_CDC_DATA(0x1076, 0x8000) }, /* GCT GDM7240 */
+ { USB_DEVICE_CDC_DATA(0x1076, 0x8f00) }, /* GCT GDM7243 */
+ { USB_DEVICE_CDC_DATA(0x1076, 0x9000) }, /* GCT GDM7243 */
+ { USB_DEVICE_CDC_DATA(0x1d74, 0x2300) }, /* LGIT Phoenix */
+ {}
+};
+
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int packet_type_to_index(u16 packetType)
+{
+ int i;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+ if (packet_type[i] == packetType)
+ return i;
+ }
+
+ return -1;
+}
+
+static struct mux_tx *alloc_mux_tx(int len)
+{
+ struct mux_tx *t = NULL;
+
+ t = kzalloc(sizeof(struct mux_tx), GFP_ATOMIC);
+ if (!t)
+ return NULL;
+
+ t->urb = usb_alloc_urb(0, GFP_ATOMIC);
+ t->buf = kmalloc(MUX_TX_MAX_SIZE, GFP_ATOMIC);
+ if (!t->urb || !t->buf) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ return NULL;
+ }
+
+ return t;
+}
+
+static void free_mux_tx(struct mux_tx *t)
+{
+ if (t) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ }
+}
+
+static struct mux_rx *alloc_mux_rx(void)
+{
+ struct mux_rx *r = NULL;
+
+ r = kzalloc(sizeof(struct mux_rx), GFP_KERNEL);
+ if (!r)
+ return NULL;
+
+ r->urb = usb_alloc_urb(0, GFP_KERNEL);
+ r->buf = kmalloc(MUX_RX_MAX_SIZE, GFP_KERNEL);
+ if (!r->urb || !r->buf) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ return NULL;
+ }
+
+ return r;
+}
+
+static void free_mux_rx(struct mux_rx *r)
+{
+ if (r) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ }
+}
+
+static struct mux_rx *get_rx_struct(struct rx_cxt *rx)
+{
+ struct mux_rx *r;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rx->free_list_lock, flags);
+
+ if (list_empty(&rx->rx_free_list)) {
+ spin_unlock_irqrestore(&rx->free_list_lock, flags);
+ return NULL;
+ }
+
+ r = list_entry(rx->rx_free_list.prev, struct mux_rx, free_list);
+ list_del(&r->free_list);
+
+ spin_unlock_irqrestore(&rx->free_list_lock, flags);
+
+ return r;
+}
+
+static void put_rx_struct(struct rx_cxt *rx, struct mux_rx *r)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rx->free_list_lock, flags);
+ list_add_tail(&r->free_list, &rx->rx_free_list);
+ spin_unlock_irqrestore(&rx->free_list_lock, flags);
+}
+
+
+static int up_to_host(struct mux_rx *r)
+{
+ struct mux_dev *mux_dev = (struct mux_dev *)r->mux_dev;
+ struct mux_pkt_header *mux_header;
+ unsigned int start_flag;
+ unsigned int payload_size;
+ unsigned short packet_type;
+ int dummy_cnt;
+ u32 packet_size_sum = r->offset;
+ int index;
+ int ret = TO_HOST_INVALID_PACKET;
+ int len = r->len;
+
+ while (1) {
+ mux_header = (struct mux_pkt_header *)(r->buf +
+ packet_size_sum);
+ start_flag = __le32_to_cpu(mux_header->start_flag);
+ payload_size = __le32_to_cpu(mux_header->payload_size);
+ packet_type = __le16_to_cpu(mux_header->packet_type);
+
+ if (start_flag != START_FLAG) {
+ pr_err("invalid START_FLAG %x\n", start_flag);
+ break;
+ }
+
+ dummy_cnt = ALIGN(MUX_HEADER_SIZE + payload_size, 4);
+
+ if (len - packet_size_sum <
+ MUX_HEADER_SIZE + payload_size + dummy_cnt) {
+ pr_err("invalid payload : %d %d %04x\n",
+ payload_size, len, packet_type);
+ break;
+ }
+
+ index = packet_type_to_index(packet_type);
+ if (index < 0) {
+ pr_err("invalid index %d\n", index);
+ break;
+ }
+
+ ret = r->callback(mux_header->data,
+ payload_size,
+ index,
+ mux_dev->tty_dev,
+ RECV_PACKET_PROCESS_CONTINUE
+ );
+ if (ret == TO_HOST_BUFFER_REQUEST_FAIL) {
+ r->offset += packet_size_sum;
+ break;
+ }
+
+ packet_size_sum += MUX_HEADER_SIZE + payload_size + dummy_cnt;
+ if (len - packet_size_sum <= MUX_HEADER_SIZE + 2) {
+ ret = r->callback(NULL,
+ 0,
+ index,
+ mux_dev->tty_dev,
+ RECV_PACKET_PROCESS_COMPLETE
+ );
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void do_rx(struct work_struct *work)
+{
+ struct mux_dev *mux_dev =
+ container_of(work, struct mux_dev , work_rx.work);
+ struct mux_rx *r;
+ struct rx_cxt *rx = (struct rx_cxt *)&mux_dev->rx;
+ unsigned long flags;
+ int ret = 0;
+
+ while (1) {
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ if (list_empty(&rx->to_host_list)) {
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+ break;
+ }
+ r = list_entry(rx->to_host_list.next, struct mux_rx,
+ to_host_list);
+ list_del(&r->to_host_list);
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+
+ ret = up_to_host(r);
+ if (ret == TO_HOST_BUFFER_REQUEST_FAIL)
+ pr_err("failed to send mux data to host\n");
+ else
+ put_rx_struct(rx, r);
+ }
+}
+
+static void remove_rx_submit_list(struct mux_rx *r, struct rx_cxt *rx)
+{
+ unsigned long flags;
+ struct mux_rx *r_remove, *r_remove_next;
+
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ list_for_each_entry_safe(r_remove, r_remove_next, &rx->rx_submit_list,
+ rx_submit_list) {
+ if (r == r_remove)
+ list_del(&r->rx_submit_list);
+ }
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+}
+
+static void gdm_mux_rcv_complete(struct urb *urb)
+{
+ struct mux_rx *r = urb->context;
+ struct mux_dev *mux_dev = (struct mux_dev *)r->mux_dev;
+ struct rx_cxt *rx = &mux_dev->rx;
+ unsigned long flags;
+
+ remove_rx_submit_list(r, rx);
+
+ if (urb->status) {
+ if (mux_dev->usb_state == PM_NORMAL)
+ pr_err("%s: urb status error %d\n",
+ __func__, urb->status);
+ put_rx_struct(rx, r);
+ } else {
+ r->len = r->urb->actual_length;
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ list_add_tail(&r->to_host_list, &rx->to_host_list);
+ queue_work(mux_rx_wq, &mux_dev->work_rx.work);
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+ }
+}
+
+static int gdm_mux_recv(void *priv_dev, int (*cb)(void *data, int len,
+ int tty_index, struct tty_dev *tty_dev, int complete))
+{
+ struct mux_dev *mux_dev = priv_dev;
+ struct usb_device *usbdev = mux_dev->usbdev;
+ struct mux_rx *r;
+ struct rx_cxt *rx = &mux_dev->rx;
+ unsigned long flags;
+ int ret;
+
+ if (!usbdev) {
+ pr_err("device is disconnected\n");
+ return -ENODEV;
+ }
+
+ r = get_rx_struct(rx);
+ if (!r) {
+ pr_err("get_rx_struct fail\n");
+ return -ENOMEM;
+ }
+
+ r->offset = 0;
+ r->mux_dev = (void *)mux_dev;
+ r->callback = cb;
+ mux_dev->rx_cb = cb;
+
+ usb_fill_bulk_urb(r->urb,
+ usbdev,
+ usb_rcvbulkpipe(usbdev, 0x86),
+ r->buf,
+ MUX_RX_MAX_SIZE,
+ gdm_mux_rcv_complete,
+ r);
+
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ list_add_tail(&r->rx_submit_list, &rx->rx_submit_list);
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+ ret = usb_submit_urb(r->urb, GFP_KERNEL);
+
+ if (ret) {
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ list_del(&r->rx_submit_list);
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+ put_rx_struct(rx, r);
+
+ pr_err("usb_submit_urb ret=%d\n", ret);
+ }
+
+ usb_mark_last_busy(usbdev);
+
+ return ret;
+}
+
+static void gdm_mux_send_complete(struct urb *urb)
+{
+ struct mux_tx *t = urb->context;
+
+ if (urb->status == -ECONNRESET) {
+ pr_info("CONNRESET\n");
+ free_mux_tx(t);
+ return;
+ }
+
+ if (t->callback)
+ t->callback(t->cb_data);
+
+ free_mux_tx(t);
+}
+
+static int gdm_mux_send(void *priv_dev, void *data, int len, int tty_index,
+ void (*cb)(void *data), void *cb_data)
+{
+ struct mux_dev *mux_dev = priv_dev;
+ struct usb_device *usbdev = mux_dev->usbdev;
+ struct mux_pkt_header *mux_header;
+ struct mux_tx *t = NULL;
+ static u32 seq_num = 1;
+ int dummy_cnt;
+ int total_len;
+ int ret;
+ unsigned long flags;
+
+ if (mux_dev->usb_state == PM_SUSPEND) {
+ ret = usb_autopm_get_interface(mux_dev->intf);
+ if (!ret)
+ usb_autopm_put_interface(mux_dev->intf);
+ }
+
+ spin_lock_irqsave(&mux_dev->write_lock, flags);
+
+ dummy_cnt = ALIGN(MUX_HEADER_SIZE + len, 4);
+
+ total_len = len + MUX_HEADER_SIZE + dummy_cnt;
+
+ t = alloc_mux_tx(total_len);
+ if (!t) {
+ pr_err("alloc_mux_tx fail\n");
+ spin_unlock_irqrestore(&mux_dev->write_lock, flags);
+ return -ENOMEM;
+ }
+
+ mux_header = (struct mux_pkt_header *)t->buf;
+ mux_header->start_flag = __cpu_to_le32(START_FLAG);
+ mux_header->seq_num = __cpu_to_le32(seq_num++);
+ mux_header->payload_size = __cpu_to_le32((u32)len);
+ mux_header->packet_type = __cpu_to_le16(packet_type[tty_index]);
+
+ memcpy(t->buf+MUX_HEADER_SIZE, data, len);
+ memset(t->buf+MUX_HEADER_SIZE+len, 0, dummy_cnt);
+
+ t->len = total_len;
+ t->callback = cb;
+ t->cb_data = cb_data;
+
+ usb_fill_bulk_urb(t->urb,
+ usbdev,
+ usb_sndbulkpipe(usbdev, 5),
+ t->buf,
+ total_len,
+ gdm_mux_send_complete,
+ t);
+
+ ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+
+ spin_unlock_irqrestore(&mux_dev->write_lock, flags);
+
+ if (ret)
+ pr_err("usb_submit_urb Error: %d\n", ret);
+
+ usb_mark_last_busy(usbdev);
+
+ return ret;
+}
+
+static int gdm_mux_send_control(void *priv_dev, int request, int value,
+ void *buf, int len)
+{
+ struct mux_dev *mux_dev = priv_dev;
+ struct usb_device *usbdev = mux_dev->usbdev;
+ int ret;
+
+ ret = usb_control_msg(usbdev,
+ usb_sndctrlpipe(usbdev, 0),
+ request,
+ USB_RT_ACM,
+ value,
+ 2,
+ buf,
+ len,
+ 5000
+ );
+
+ if (ret < 0)
+ pr_err("usb_control_msg error: %d\n", ret);
+
+ return ret < 0 ? ret : 0;
+}
+
+static void release_usb(struct mux_dev *mux_dev)
+{
+ struct rx_cxt *rx = &mux_dev->rx;
+ struct mux_rx *r, *r_next;
+ unsigned long flags;
+
+ cancel_delayed_work(&mux_dev->work_rx);
+
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->rx_submit_list,
+ rx_submit_list) {
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+ usb_kill_urb(r->urb);
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ }
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+ spin_lock_irqsave(&rx->free_list_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->rx_free_list, free_list) {
+ list_del(&r->free_list);
+ free_mux_rx(r);
+ }
+ spin_unlock_irqrestore(&rx->free_list_lock, flags);
+
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->to_host_list, to_host_list) {
+ if (r->mux_dev == (void *)mux_dev) {
+ list_del(&r->to_host_list);
+ free_mux_rx(r);
+ }
+ }
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+}
+
+
+static int init_usb(struct mux_dev *mux_dev)
+{
+ struct mux_rx *r;
+ struct rx_cxt *rx = &mux_dev->rx;
+ int ret = 0;
+ int i;
+
+ spin_lock_init(&mux_dev->write_lock);
+ INIT_LIST_HEAD(&rx->to_host_list);
+ INIT_LIST_HEAD(&rx->rx_submit_list);
+ INIT_LIST_HEAD(&rx->rx_free_list);
+ spin_lock_init(&rx->to_host_lock);
+ spin_lock_init(&rx->submit_list_lock);
+ spin_lock_init(&rx->free_list_lock);
+
+ for (i = 0; i < MAX_ISSUE_NUM * 2; i++) {
+ r = alloc_mux_rx();
+ if (r == NULL) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ list_add(&r->free_list, &rx->rx_free_list);
+ }
+
+ INIT_DELAYED_WORK(&mux_dev->work_rx, do_rx);
+
+ return ret;
+}
+
+static int gdm_mux_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct mux_dev *mux_dev;
+ struct tty_dev *tty_dev;
+ u16 idVendor, idProduct;
+ int bInterfaceNumber;
+ int ret;
+ int i;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+ bInterfaceNumber = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ idVendor = __le16_to_cpu(usbdev->descriptor.idVendor);
+ idProduct = __le16_to_cpu(usbdev->descriptor.idProduct);
+
+ pr_info("mux vid = 0x%04x pid = 0x%04x\n", idVendor, idProduct);
+
+ if (bInterfaceNumber != 2)
+ return -ENODEV;
+
+ mux_dev = kzalloc(sizeof(struct mux_dev), GFP_KERNEL);
+ if (!mux_dev)
+ return -ENOMEM;
+
+ tty_dev = kzalloc(sizeof(struct tty_dev), GFP_KERNEL);
+ if (!tty_dev) {
+ ret = -ENOMEM;
+ goto err_free_mux;
+ }
+
+ mux_dev->usbdev = usbdev;
+ mux_dev->control_intf = intf;
+
+ ret = init_usb(mux_dev);
+ if (ret)
+ goto err_free_usb;
+
+ tty_dev->priv_dev = (void *)mux_dev;
+ tty_dev->send_func = gdm_mux_send;
+ tty_dev->recv_func = gdm_mux_recv;
+ tty_dev->send_control = gdm_mux_send_control;
+
+ ret = register_lte_tty_device(tty_dev, &intf->dev);
+ if (ret)
+ goto err_unregister_tty;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++)
+ mux_dev->tty_dev = tty_dev;
+
+ mux_dev->intf = intf;
+ mux_dev->usb_state = PM_NORMAL;
+
+ usb_get_dev(usbdev);
+ usb_set_intfdata(intf, tty_dev);
+
+ return 0;
+
+err_unregister_tty:
+ unregister_lte_tty_device(tty_dev);
+err_free_usb:
+ release_usb(mux_dev);
+ kfree(tty_dev);
+err_free_mux:
+ kfree(mux_dev);
+
+ return ret;
+}
+
+static void gdm_mux_disconnect(struct usb_interface *intf)
+{
+ struct tty_dev *tty_dev;
+ struct mux_dev *mux_dev;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+
+ tty_dev = usb_get_intfdata(intf);
+
+ mux_dev = tty_dev->priv_dev;
+
+ release_usb(mux_dev);
+ unregister_lte_tty_device(tty_dev);
+
+ kfree(mux_dev);
+ kfree(tty_dev);
+
+ usb_put_dev(usbdev);
+}
+
+static int gdm_mux_suspend(struct usb_interface *intf, pm_message_t pm_msg)
+{
+ struct tty_dev *tty_dev;
+ struct mux_dev *mux_dev;
+ struct rx_cxt *rx;
+ struct mux_rx *r, *r_next;
+ unsigned long flags;
+
+ tty_dev = usb_get_intfdata(intf);
+ mux_dev = tty_dev->priv_dev;
+ rx = &mux_dev->rx;
+
+ if (mux_dev->usb_state != PM_NORMAL) {
+ pr_err("usb suspend - invalid state\n");
+ return -1;
+ }
+
+ mux_dev->usb_state = PM_SUSPEND;
+
+
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->rx_submit_list,
+ rx_submit_list) {
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+ usb_kill_urb(r->urb);
+ spin_lock_irqsave(&rx->submit_list_lock, flags);
+ }
+ spin_unlock_irqrestore(&rx->submit_list_lock, flags);
+
+ return 0;
+}
+
+static int gdm_mux_resume(struct usb_interface *intf)
+{
+ struct tty_dev *tty_dev;
+ struct mux_dev *mux_dev;
+ u8 i;
+
+ tty_dev = usb_get_intfdata(intf);
+ mux_dev = tty_dev->priv_dev;
+
+ if (mux_dev->usb_state != PM_SUSPEND) {
+ pr_err("usb resume - invalid state\n");
+ return -1;
+ }
+
+ mux_dev->usb_state = PM_NORMAL;
+
+ for (i = 0; i < MAX_ISSUE_NUM; i++)
+ gdm_mux_recv(mux_dev, mux_dev->rx_cb);
+
+ return 0;
+}
+
+static struct usb_driver gdm_mux_driver = {
+ .name = "gdm_mux",
+ .probe = gdm_mux_probe,
+ .disconnect = gdm_mux_disconnect,
+ .id_table = id_table,
+ .supports_autosuspend = 1,
+ .suspend = gdm_mux_suspend,
+ .resume = gdm_mux_resume,
+ .reset_resume = gdm_mux_resume,
+};
+
+static int __init gdm_usb_mux_init(void)
+{
+
+ mux_rx_wq = create_workqueue("mux_rx_wq");
+ if (mux_rx_wq == NULL) {
+ pr_err("work queue create fail\n");
+ return -1;
+ }
+
+ register_lte_tty_driver();
+
+ return usb_register(&gdm_mux_driver);
+}
+
+static void __exit gdm_usb_mux_exit(void)
+{
+ unregister_lte_tty_driver();
+
+ if (mux_rx_wq) {
+ flush_workqueue(mux_rx_wq);
+ destroy_workqueue(mux_rx_wq);
+ }
+
+ usb_deregister(&gdm_mux_driver);
+}
+
+module_init(gdm_usb_mux_init);
+module_exit(gdm_usb_mux_exit);
+
+MODULE_DESCRIPTION("GCT LTE TTY Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/gdm724x/gdm_mux.h b/drivers/staging/gdm724x/gdm_mux.h
new file mode 100644
index 00000000000..0163b243d3e
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_mux.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _GDM_MUX_H_
+#define _GDM_MUX_H_
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/list.h>
+
+#include "gdm_tty.h"
+
+#define PM_NORMAL 0
+#define PM_SUSPEND 1
+
+#define USB_RT_ACM (USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+
+#define START_FLAG 0xA512485A
+#define MUX_HEADER_SIZE 14
+#define MUX_TX_MAX_SIZE (1024*10)
+#define MUX_RX_MAX_SIZE (1024*30)
+#define AT_PKT_TYPE 0xF011
+#define DM_PKT_TYPE 0xF010
+
+#define RETRY_TIMER 30 /* msec */
+
+struct mux_pkt_header {
+ unsigned int start_flag;
+ unsigned int seq_num;
+ unsigned int payload_size;
+ unsigned short packet_type;
+ unsigned char data[0];
+};
+
+struct mux_tx {
+ struct urb *urb;
+ u8 *buf;
+ int len;
+ void (*callback)(void *cb_data);
+ void *cb_data;
+};
+
+struct mux_rx {
+ struct list_head free_list;
+ struct list_head rx_submit_list;
+ struct list_head to_host_list;
+ struct urb *urb;
+ u8 *buf;
+ void *mux_dev;
+ u32 offset;
+ u32 len;
+ int (*callback)(void *data,
+ int len,
+ int tty_index,
+ struct tty_dev *tty_dev,
+ int complete);
+};
+
+struct rx_cxt {
+ struct list_head to_host_list;
+ struct list_head rx_submit_list;
+ struct list_head rx_free_list;
+ spinlock_t to_host_lock;
+ spinlock_t submit_list_lock;
+ spinlock_t free_list_lock;
+};
+
+struct mux_dev {
+ struct usb_device *usbdev;
+ struct usb_interface *control_intf;
+ struct usb_interface *data_intf;
+ struct rx_cxt rx;
+ struct delayed_work work_rx;
+ struct usb_interface *intf;
+ int usb_state;
+ int (*rx_cb)(void *data,
+ int len,
+ int tty_index,
+ struct tty_dev *tty_dev,
+ int complete);
+ spinlock_t write_lock;
+ struct tty_dev *tty_dev;
+};
+
+#endif /* _GDM_MUX_H_ */
diff --git a/drivers/staging/gdm724x/gdm_tty.c b/drivers/staging/gdm724x/gdm_tty.c
new file mode 100644
index 00000000000..fe47cd3eb2e
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_tty.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb/cdc.h>
+#include <linux/serial.h>
+#include "gdm_tty.h"
+
+#define GDM_TTY_MAJOR 0
+#define GDM_TTY_MINOR 32
+
+#define ACM_CTRL_DTR 0x01
+#define ACM_CTRL_RTS 0x02
+#define ACM_CTRL_DSR 0x02
+#define ACM_CTRL_RI 0x08
+#define ACM_CTRL_DCD 0x01
+
+#define WRITE_SIZE 2048
+
+#define MUX_TX_MAX_SIZE 2048
+
+#define gdm_tty_send(n, d, l, i, c, b) (\
+ n->tty_dev->send_func(n->tty_dev->priv_dev, d, l, i, c, b))
+#define gdm_tty_recv(n, c) (\
+ n->tty_dev->recv_func(n->tty_dev->priv_dev, c))
+#define gdm_tty_send_control(n, r, v, d, l) (\
+ n->tty_dev->send_control(n->tty_dev->priv_dev, r, v, d, l))
+
+#define GDM_TTY_READY(gdm) (gdm && gdm->tty_dev && gdm->port.count)
+
+static struct tty_driver *gdm_driver[TTY_MAX_COUNT];
+static struct gdm *gdm_table[TTY_MAX_COUNT][GDM_TTY_MINOR];
+static DEFINE_MUTEX(gdm_table_lock);
+
+static char *DRIVER_STRING[TTY_MAX_COUNT] = {"GCTATC", "GCTDM"};
+static char *DEVICE_STRING[TTY_MAX_COUNT] = {"GCT-ATC", "GCT-DM"};
+
+static void gdm_port_destruct(struct tty_port *port)
+{
+ struct gdm *gdm = container_of(port, struct gdm, port);
+
+ mutex_lock(&gdm_table_lock);
+ gdm_table[gdm->index][gdm->minor] = NULL;
+ mutex_unlock(&gdm_table_lock);
+
+ kfree(gdm);
+}
+
+static struct tty_port_operations gdm_port_ops = {
+ .destruct = gdm_port_destruct,
+};
+
+static int gdm_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct gdm *gdm = NULL;
+ int ret;
+ int i;
+ int j;
+
+ j = GDM_TTY_MINOR;
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+ if (!strcmp(tty->driver->driver_name, DRIVER_STRING[i])) {
+ j = tty->index;
+ break;
+ }
+ }
+
+ if (j == GDM_TTY_MINOR)
+ return -ENODEV;
+
+ mutex_lock(&gdm_table_lock);
+ gdm = gdm_table[i][j];
+ if (gdm == NULL) {
+ mutex_unlock(&gdm_table_lock);
+ return -ENODEV;
+ }
+
+ tty_port_get(&gdm->port);
+
+ ret = tty_standard_install(driver, tty);
+ if (ret) {
+ tty_port_put(&gdm->port);
+ mutex_unlock(&gdm_table_lock);
+ return ret;
+ }
+
+ tty->driver_data = gdm;
+ mutex_unlock(&gdm_table_lock);
+
+ return 0;
+}
+
+static int gdm_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct gdm *gdm = tty->driver_data;
+ return tty_port_open(&gdm->port, tty, filp);
+}
+
+static void gdm_tty_cleanup(struct tty_struct *tty)
+{
+ struct gdm *gdm = tty->driver_data;
+ tty_port_put(&gdm->port);
+}
+
+static void gdm_tty_hangup(struct tty_struct *tty)
+{
+ struct gdm *gdm = tty->driver_data;
+ tty_port_hangup(&gdm->port);
+}
+
+static void gdm_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ struct gdm *gdm = tty->driver_data;
+ tty_port_close(&gdm->port, tty, filp);
+}
+
+static int gdm_tty_recv_complete(void *data,
+ int len,
+ int index,
+ struct tty_dev *tty_dev,
+ int complete)
+{
+ struct gdm *gdm = tty_dev->gdm[index];
+ if (!GDM_TTY_READY(gdm)) {
+ if (complete == RECV_PACKET_PROCESS_COMPLETE)
+ gdm_tty_recv(gdm, gdm_tty_recv_complete);
+ return TO_HOST_PORT_CLOSE;
+ }
+
+ if (data && len) {
+ if (tty_buffer_request_room(&gdm->port, len) == len) {
+ tty_insert_flip_string(&gdm->port, data, len);
+ tty_flip_buffer_push(&gdm->port);
+ } else {
+ return TO_HOST_BUFFER_REQUEST_FAIL;
+ }
+ }
+
+ if (complete == RECV_PACKET_PROCESS_COMPLETE)
+ gdm_tty_recv(gdm, gdm_tty_recv_complete);
+
+ return 0;
+}
+
+static void gdm_tty_send_complete(void *arg)
+{
+ struct gdm *gdm = (struct gdm *)arg;
+
+ if (!GDM_TTY_READY(gdm))
+ return;
+
+ tty_port_tty_wakeup(&gdm->port);
+}
+
+static int gdm_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int len)
+{
+ struct gdm *gdm = tty->driver_data;
+ int remain = len;
+ int sent_len = 0;
+ int sending_len = 0;
+
+ if (!GDM_TTY_READY(gdm))
+ return -ENODEV;
+
+ if (!len)
+ return 0;
+
+ while (1) {
+ sending_len = remain > MUX_TX_MAX_SIZE ? MUX_TX_MAX_SIZE :
+ remain;
+ gdm_tty_send(gdm,
+ (void *)(buf+sent_len),
+ sending_len,
+ gdm->index,
+ gdm_tty_send_complete,
+ gdm
+ );
+ sent_len += sending_len;
+ remain -= sending_len;
+ if (remain <= 0)
+ break;
+ }
+
+ return len;
+}
+
+static int gdm_tty_write_room(struct tty_struct *tty)
+{
+ struct gdm *gdm = tty->driver_data;
+
+ if (!GDM_TTY_READY(gdm))
+ return -ENODEV;
+
+ return WRITE_SIZE;
+}
+
+int register_lte_tty_device(struct tty_dev *tty_dev, struct device *device)
+{
+ struct gdm *gdm;
+ int i;
+ int j;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+
+ gdm = kmalloc(sizeof(struct gdm), GFP_KERNEL);
+ if (!gdm)
+ return -ENOMEM;
+
+ mutex_lock(&gdm_table_lock);
+ for (j = 0; j < GDM_TTY_MINOR; j++) {
+ if (!gdm_table[i][j])
+ break;
+ }
+
+ if (j == GDM_TTY_MINOR) {
+ kfree(gdm);
+ mutex_unlock(&gdm_table_lock);
+ return -EINVAL;
+ }
+
+ gdm_table[i][j] = gdm;
+ mutex_unlock(&gdm_table_lock);
+
+ tty_dev->gdm[i] = gdm;
+ tty_port_init(&gdm->port);
+
+ gdm->port.ops = &gdm_port_ops;
+ gdm->index = i;
+ gdm->minor = j;
+ gdm->tty_dev = tty_dev;
+
+ tty_port_register_device(&gdm->port, gdm_driver[i],
+ gdm->minor, device);
+ }
+
+ for (i = 0; i < MAX_ISSUE_NUM; i++)
+ gdm_tty_recv(gdm, gdm_tty_recv_complete);
+
+ return 0;
+}
+
+void unregister_lte_tty_device(struct tty_dev *tty_dev)
+{
+ struct gdm *gdm;
+ struct tty_struct *tty;
+ int i;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+ gdm = tty_dev->gdm[i];
+ if (!gdm)
+ continue;
+
+ mutex_lock(&gdm_table_lock);
+ gdm_table[gdm->index][gdm->minor] = NULL;
+ mutex_unlock(&gdm_table_lock);
+
+ tty = tty_port_tty_get(&gdm->port);
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
+
+ tty_unregister_device(gdm_driver[i], gdm->minor);
+ tty_port_put(&gdm->port);
+ }
+}
+
+static const struct tty_operations gdm_tty_ops = {
+ .install = gdm_tty_install,
+ .open = gdm_tty_open,
+ .close = gdm_tty_close,
+ .cleanup = gdm_tty_cleanup,
+ .hangup = gdm_tty_hangup,
+ .write = gdm_tty_write,
+ .write_room = gdm_tty_write_room,
+};
+
+int register_lte_tty_driver(void)
+{
+ struct tty_driver *tty_driver;
+ int i;
+ int ret;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+ tty_driver = alloc_tty_driver(GDM_TTY_MINOR);
+ if (!tty_driver)
+ return -ENOMEM;
+
+ tty_driver->owner = THIS_MODULE;
+ tty_driver->driver_name = DRIVER_STRING[i];
+ tty_driver->name = DEVICE_STRING[i];
+ tty_driver->major = GDM_TTY_MAJOR;
+ tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ tty_driver->flags = TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV;
+ tty_driver->init_termios = tty_std_termios;
+ tty_driver->init_termios.c_cflag = B9600 | CS8 | HUPCL | CLOCAL;
+ tty_driver->init_termios.c_lflag = ISIG | ICANON | IEXTEN;
+ tty_set_operations(tty_driver, &gdm_tty_ops);
+
+ ret = tty_register_driver(tty_driver);
+ if (ret) {
+ put_tty_driver(tty_driver);
+ return ret;
+ }
+
+ gdm_driver[i] = tty_driver;
+ }
+
+ return ret;
+}
+
+void unregister_lte_tty_driver(void)
+{
+ struct tty_driver *tty_driver;
+ int i;
+
+ for (i = 0; i < TTY_MAX_COUNT; i++) {
+ tty_driver = gdm_driver[i];
+ if (tty_driver) {
+ tty_unregister_driver(tty_driver);
+ put_tty_driver(tty_driver);
+ }
+ }
+}
+
diff --git a/drivers/staging/gdm724x/gdm_tty.h b/drivers/staging/gdm724x/gdm_tty.h
new file mode 100644
index 00000000000..297438b4ddc
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_tty.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _GDM_TTY_H_
+#define _GDM_TTY_H_
+
+#include <linux/types.h>
+#include <linux/tty.h>
+
+
+#define TTY_MAX_COUNT 2
+
+#define MAX_ISSUE_NUM 3
+
+enum TO_HOST_RESULT {
+ TO_HOST_BUFFER_REQUEST_FAIL = 1,
+ TO_HOST_PORT_CLOSE = 2,
+ TO_HOST_INVALID_PACKET = 3,
+};
+
+enum RECV_PACKET_PROCESS {
+ RECV_PACKET_PROCESS_COMPLETE = 0,
+ RECV_PACKET_PROCESS_CONTINUE = 1,
+};
+
+struct gdm {
+ struct tty_dev *tty_dev;
+ struct tty_port port;
+ unsigned int index;
+ unsigned int minor;
+};
+
+struct tty_dev {
+ void *priv_dev;
+ int (*send_func)(void *priv_dev,
+ void *data,
+ int len,
+ int tty_index,
+ void (*cb)(void *cb_data),
+ void *cb_data);
+ int (*recv_func)(void *priv_dev,
+ int (*cb)(void *data,
+ int len,
+ int tty_index,
+ struct tty_dev *tty_dev,
+ int complete));
+ int (*send_control)(void *priv_dev,
+ int request,
+ int value,
+ void *data,
+ int len);
+ struct gdm *gdm[2];
+};
+
+int register_lte_tty_driver(void);
+void unregister_lte_tty_driver(void);
+int register_lte_tty_device(struct tty_dev *tty_dev, struct device *dev);
+void unregister_lte_tty_device(struct tty_dev *tty_dev);
+
+#endif /* _GDM_USB_H_ */
+
diff --git a/drivers/staging/gdm724x/gdm_usb.c b/drivers/staging/gdm724x/gdm_usb.c
new file mode 100644
index 00000000000..ee6e40facca
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_usb.c
@@ -0,0 +1,1041 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/usb/cdc.h>
+#include <linux/wait.h>
+#include <linux/if_ether.h>
+#include <linux/pm_runtime.h>
+
+#include "gdm_usb.h"
+#include "gdm_lte.h"
+#include "hci.h"
+#include "hci_packet.h"
+#include "gdm_endian.h"
+
+#define USB_DEVICE_CDC_DATA(vid, pid) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_SUBCLASS,\
+ .idVendor = vid,\
+ .idProduct = pid,\
+ .bInterfaceClass = USB_CLASS_COMM,\
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET
+
+#define USB_DEVICE_MASS_DATA(vid, pid) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_INFO,\
+ .idVendor = vid,\
+ .idProduct = pid,\
+ .bInterfaceSubClass = USB_SC_SCSI, \
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,\
+ .bInterfaceProtocol = USB_PR_BULK
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE_CDC_DATA(VID_GCT, PID_GDM7240) }, /* GCT GDM7240 */
+ { USB_DEVICE_CDC_DATA(VID_GCT, PID_GDM7243) }, /* GCT GDM7243 */
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct workqueue_struct *usb_tx_wq;
+static struct workqueue_struct *usb_rx_wq;
+
+static void do_tx(struct work_struct *work);
+static void do_rx(struct work_struct *work);
+
+static int gdm_usb_recv(void *priv_dev,
+ int (*cb)(void *cb_data,
+ void *data, int len, int context),
+ void *cb_data,
+ int context);
+
+static int request_mac_address(struct lte_udev *udev)
+{
+ u8 buf[16] = {0,};
+ struct hci_packet *hci = (struct hci_packet *)buf;
+ struct usb_device *usbdev = udev->usbdev;
+ int actual;
+ int ret = -1;
+
+ hci->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_GET_INFORMATION);
+ hci->len = gdm_cpu_to_dev16(&udev->gdm_ed, 1);
+ hci->data[0] = MAC_ADDRESS;
+
+ ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 2), buf, 5,
+ &actual, 1000);
+
+ udev->request_mac_addr = 1;
+
+ return ret;
+}
+
+static struct usb_tx *alloc_tx_struct(int len)
+{
+ struct usb_tx *t = NULL;
+ int ret = 0;
+
+ t = kzalloc(sizeof(struct usb_tx), GFP_ATOMIC);
+ if (!t) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ t->urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!(len % 512))
+ len++;
+
+ t->buf = kmalloc(len, GFP_ATOMIC);
+ if (!t->urb || !t->buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+out:
+ if (ret < 0) {
+ if (t) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ }
+ return NULL;
+ }
+
+ return t;
+}
+
+static struct usb_tx_sdu *alloc_tx_sdu_struct(void)
+{
+ struct usb_tx_sdu *t_sdu;
+
+ t_sdu = kzalloc(sizeof(struct usb_tx_sdu), GFP_ATOMIC);
+ if (!t_sdu)
+ return NULL;
+
+ t_sdu->buf = kmalloc(SDU_BUF_SIZE, GFP_ATOMIC);
+ if (!t_sdu->buf) {
+ kfree(t_sdu);
+ return NULL;
+ }
+
+ return t_sdu;
+}
+
+static void free_tx_struct(struct usb_tx *t)
+{
+ if (t) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ }
+}
+
+static void free_tx_sdu_struct(struct usb_tx_sdu *t_sdu)
+{
+ if (t_sdu) {
+ kfree(t_sdu->buf);
+ kfree(t_sdu);
+ }
+}
+
+static struct usb_tx_sdu *get_tx_sdu_struct(struct tx_cxt *tx, int *no_spc)
+{
+ struct usb_tx_sdu *t_sdu;
+
+ if (list_empty(&tx->free_list))
+ return NULL;
+
+ t_sdu = list_entry(tx->free_list.next, struct usb_tx_sdu, list);
+ list_del(&t_sdu->list);
+
+ tx->avail_count--;
+
+ *no_spc = list_empty(&tx->free_list) ? 1 : 0;
+
+ return t_sdu;
+}
+
+static void put_tx_struct(struct tx_cxt *tx, struct usb_tx_sdu *t_sdu)
+{
+ list_add_tail(&t_sdu->list, &tx->free_list);
+ tx->avail_count++;
+}
+
+static struct usb_rx *alloc_rx_struct(void)
+{
+ struct usb_rx *r = NULL;
+ int ret = 0;
+
+ r = kmalloc(sizeof(struct usb_rx), GFP_ATOMIC);
+ if (!r) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ r->urb = usb_alloc_urb(0, GFP_ATOMIC);
+ r->buf = kmalloc(RX_BUF_SIZE, GFP_ATOMIC);
+ if (!r->urb || !r->buf) {
+ ret = -ENOMEM;
+ goto out;
+ }
+out:
+
+ if (ret < 0) {
+ if (r) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ }
+ return NULL;
+ }
+
+ return r;
+}
+
+static void free_rx_struct(struct usb_rx *r)
+{
+ if (r) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ }
+}
+
+static struct usb_rx *get_rx_struct(struct rx_cxt *rx, int *no_spc)
+{
+ struct usb_rx *r;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rx->rx_lock, flags);
+
+ if (list_empty(&rx->free_list)) {
+ spin_unlock_irqrestore(&rx->rx_lock, flags);
+ return NULL;
+ }
+
+ r = list_entry(rx->free_list.next, struct usb_rx, free_list);
+ list_del(&r->free_list);
+
+ rx->avail_count--;
+
+ *no_spc = list_empty(&rx->free_list) ? 1 : 0;
+
+ spin_unlock_irqrestore(&rx->rx_lock, flags);
+
+ return r;
+}
+
+static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rx->rx_lock, flags);
+
+ list_add_tail(&r->free_list, &rx->free_list);
+ rx->avail_count++;
+
+ spin_unlock_irqrestore(&rx->rx_lock, flags);
+}
+
+static void release_usb(struct lte_udev *udev)
+{
+ struct rx_cxt *rx = &udev->rx;
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx *t, *t_next;
+ struct usb_rx *r, *r_next;
+ struct usb_tx_sdu *t_sdu, *t_sdu_next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ list_for_each_entry_safe(t_sdu, t_sdu_next, &tx->sdu_list, list)
+ {
+ list_del(&t_sdu->list);
+ free_tx_sdu_struct(t_sdu);
+ }
+
+ list_for_each_entry_safe(t, t_next, &tx->hci_list, list)
+ {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ list_for_each_entry_safe(t_sdu, t_sdu_next, &tx->free_list, list)
+ {
+ list_del(&t_sdu->list);
+ free_tx_sdu_struct(t_sdu);
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->rx_submit_list, rx_submit_list)
+ {
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+ usb_kill_urb(r->urb);
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ }
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+
+ spin_lock_irqsave(&rx->rx_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->free_list, free_list)
+ {
+ list_del(&r->free_list);
+ free_rx_struct(r);
+ }
+ spin_unlock_irqrestore(&rx->rx_lock, flags);
+
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->to_host_list, to_host_list)
+ {
+ if (r->index == (void *)udev) {
+ list_del(&r->to_host_list);
+ free_rx_struct(r);
+ }
+ }
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+}
+
+static int init_usb(struct lte_udev *udev)
+{
+ int ret = 0;
+ int i;
+ struct tx_cxt *tx = &udev->tx;
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_tx_sdu *t_sdu = NULL;
+ struct usb_rx *r = NULL;
+
+ udev->send_complete = 1;
+ udev->tx_stop = 0;
+ udev->request_mac_addr = 0;
+ udev->usb_state = PM_NORMAL;
+
+ INIT_LIST_HEAD(&tx->sdu_list);
+ INIT_LIST_HEAD(&tx->hci_list);
+ INIT_LIST_HEAD(&tx->free_list);
+ INIT_LIST_HEAD(&rx->rx_submit_list);
+ INIT_LIST_HEAD(&rx->free_list);
+ INIT_LIST_HEAD(&rx->to_host_list);
+ spin_lock_init(&tx->lock);
+ spin_lock_init(&rx->rx_lock);
+ spin_lock_init(&rx->submit_lock);
+ spin_lock_init(&rx->to_host_lock);
+
+ tx->avail_count = 0;
+ rx->avail_count = 0;
+
+ udev->rx_cb = NULL;
+
+ for (i = 0; i < MAX_NUM_SDU_BUF; i++) {
+ t_sdu = alloc_tx_sdu_struct();
+ if (t_sdu == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ list_add(&t_sdu->list, &tx->free_list);
+ tx->avail_count++;
+ }
+
+ for (i = 0; i < MAX_RX_SUBMIT_COUNT*2; i++) {
+ r = alloc_rx_struct();
+ if (r == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ list_add(&r->free_list, &rx->free_list);
+ rx->avail_count++;
+ }
+ INIT_DELAYED_WORK(&udev->work_tx, do_tx);
+ INIT_DELAYED_WORK(&udev->work_rx, do_rx);
+ return 0;
+fail:
+ return ret;
+}
+
+static int set_mac_address(u8 *data, void *arg)
+{
+ struct phy_dev *phy_dev = (struct phy_dev *)arg;
+ struct lte_udev *udev = phy_dev->priv_dev;
+ struct tlv *tlv = (struct tlv *)data;
+ u8 mac_address[ETH_ALEN] = {0, };
+
+ if (tlv->type == MAC_ADDRESS && udev->request_mac_addr) {
+ memcpy(mac_address, tlv->data, tlv->len);
+
+ if (register_lte_device(phy_dev,
+ &udev->intf->dev, mac_address) < 0)
+ pr_err("register lte device failed\n");
+
+ udev->request_mac_addr = 0;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static void do_rx(struct work_struct *work)
+{
+ struct lte_udev *udev =
+ container_of(work, struct lte_udev, work_rx.work);
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_rx *r;
+ struct hci_packet *hci;
+ struct phy_dev *phy_dev;
+ u16 cmd_evt;
+ int ret;
+ unsigned long flags;
+
+ while (1) {
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ if (list_empty(&rx->to_host_list)) {
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+ break;
+ }
+ r = list_entry(rx->to_host_list.next,
+ struct usb_rx, to_host_list);
+ list_del(&r->to_host_list);
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+
+ phy_dev = (struct phy_dev *)r->cb_data;
+ udev = (struct lte_udev *)phy_dev->priv_dev;
+ hci = (struct hci_packet *)r->buf;
+ cmd_evt = gdm_dev16_to_cpu(&udev->gdm_ed, hci->cmd_evt);
+
+ switch (cmd_evt) {
+ case LTE_GET_INFORMATION_RESULT:
+ if (set_mac_address(hci->data, r->cb_data) == 0) {
+ ret = r->callback(r->cb_data,
+ r->buf,
+ r->urb->actual_length,
+ KERNEL_THREAD);
+ }
+ break;
+
+ default:
+ if (r->callback) {
+ ret = r->callback(r->cb_data,
+ r->buf,
+ r->urb->actual_length,
+ KERNEL_THREAD);
+
+ if (ret == -EAGAIN)
+ pr_err("failed to send received data\n");
+ }
+ break;
+ }
+
+ put_rx_struct(rx, r);
+
+ gdm_usb_recv(udev,
+ r->callback,
+ r->cb_data,
+ USB_COMPLETE);
+ }
+}
+
+static void remove_rx_submit_list(struct usb_rx *r, struct rx_cxt *rx)
+{
+ unsigned long flags;
+ struct usb_rx *r_remove, *r_remove_next;
+
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ list_for_each_entry_safe(r_remove,
+ r_remove_next, &rx->rx_submit_list, rx_submit_list)
+ {
+ if (r == r_remove) {
+ list_del(&r->rx_submit_list);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+}
+
+static void gdm_usb_rcv_complete(struct urb *urb)
+{
+ struct usb_rx *r = urb->context;
+ struct rx_cxt *rx = r->rx;
+ unsigned long flags;
+ struct lte_udev *udev = container_of(r->rx, struct lte_udev, rx);
+ struct usb_device *usbdev = udev->usbdev;
+
+ remove_rx_submit_list(r, rx);
+
+ if (!urb->status && r->callback) {
+ spin_lock_irqsave(&rx->to_host_lock, flags);
+ list_add_tail(&r->to_host_list, &rx->to_host_list);
+ queue_work(usb_rx_wq, &udev->work_rx.work);
+ spin_unlock_irqrestore(&rx->to_host_lock, flags);
+ } else {
+ if (urb->status && udev->usb_state == PM_NORMAL)
+ pr_err("%s: urb status error %d\n",
+ __func__, urb->status);
+
+ put_rx_struct(rx, r);
+ }
+
+ usb_mark_last_busy(usbdev);
+}
+
+static int gdm_usb_recv(void *priv_dev,
+ int (*cb)(void *cb_data,
+ void *data, int len, int context),
+ void *cb_data,
+ int context)
+{
+ struct lte_udev *udev = priv_dev;
+ struct usb_device *usbdev = udev->usbdev;
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_rx *r;
+ int no_spc;
+ int ret;
+ unsigned long flags;
+
+ if (!udev->usbdev) {
+ pr_err("invalid device\n");
+ return -ENODEV;
+ }
+
+ r = get_rx_struct(rx, &no_spc);
+ if (!r) {
+ pr_err("Out of Memory\n");
+ return -ENOMEM;
+ }
+
+ udev->rx_cb = cb;
+ r->callback = cb;
+ r->cb_data = cb_data;
+ r->index = (void *)udev;
+ r->rx = rx;
+
+ usb_fill_bulk_urb(r->urb,
+ usbdev,
+ usb_rcvbulkpipe(usbdev, 0x83),
+ r->buf,
+ RX_BUF_SIZE,
+ gdm_usb_rcv_complete,
+ r);
+
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ list_add_tail(&r->rx_submit_list, &rx->rx_submit_list);
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+
+ if (context == KERNEL_THREAD)
+ ret = usb_submit_urb(r->urb, GFP_KERNEL);
+ else
+ ret = usb_submit_urb(r->urb, GFP_ATOMIC);
+
+ if (ret) {
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ list_del(&r->rx_submit_list);
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+
+ pr_err("usb_submit_urb failed (%p)\n", r);
+ put_rx_struct(rx, r);
+ }
+
+ return ret;
+}
+
+static void gdm_usb_send_complete(struct urb *urb)
+{
+ struct usb_tx *t = urb->context;
+ struct tx_cxt *tx = t->tx;
+ struct lte_udev *udev = container_of(tx, struct lte_udev, tx);
+ unsigned long flags;
+
+ if (urb->status == -ECONNRESET) {
+ pr_info("CONNRESET\n");
+ return;
+ }
+
+ if (t->callback)
+ t->callback(t->cb_data);
+
+ free_tx_struct(t);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ udev->send_complete = 1;
+ queue_work(usb_tx_wq, &udev->work_tx.work);
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+static int send_tx_packet(struct usb_device *usbdev, struct usb_tx *t, u32 len)
+{
+ int ret = 0;
+
+ if (!(len%512))
+ len++;
+
+ usb_fill_bulk_urb(t->urb,
+ usbdev,
+ usb_sndbulkpipe(usbdev, 2),
+ t->buf,
+ len,
+ gdm_usb_send_complete,
+ t);
+
+ ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+
+ if (ret)
+ pr_err("usb_submit_urb failed: %d\n", ret);
+
+ usb_mark_last_busy(usbdev);
+
+ return ret;
+}
+
+static u32 packet_aggregation(struct lte_udev *udev, u8 *send_buf)
+{
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx_sdu *t_sdu = NULL;
+ struct multi_sdu *multi_sdu = (struct multi_sdu *)send_buf;
+ u16 send_len = 0;
+ u16 num_packet = 0;
+ unsigned long flags;
+
+ multi_sdu->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_TX_MULTI_SDU);
+
+ while (num_packet < MAX_PACKET_IN_MULTI_SDU) {
+ spin_lock_irqsave(&tx->lock, flags);
+ if (list_empty(&tx->sdu_list)) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ break;
+ }
+
+ t_sdu = list_entry(tx->sdu_list.next, struct usb_tx_sdu, list);
+ if (send_len + t_sdu->len > MAX_SDU_SIZE) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ break;
+ }
+
+ list_del(&t_sdu->list);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ memcpy(multi_sdu->data + send_len, t_sdu->buf, t_sdu->len);
+
+ send_len += (t_sdu->len + 3) & 0xfffc;
+ num_packet++;
+
+ if (tx->avail_count > 10)
+ t_sdu->callback(t_sdu->cb_data);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ put_tx_struct(tx, t_sdu);
+ spin_unlock_irqrestore(&tx->lock, flags);
+ }
+
+ multi_sdu->len = gdm_cpu_to_dev16(&udev->gdm_ed, send_len);
+ multi_sdu->num_packet = gdm_cpu_to_dev16(&udev->gdm_ed, num_packet);
+
+ return send_len + offsetof(struct multi_sdu, data);
+}
+
+static void do_tx(struct work_struct *work)
+{
+ struct lte_udev *udev =
+ container_of(work, struct lte_udev, work_tx.work);
+ struct usb_device *usbdev = udev->usbdev;
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx *t = NULL;
+ int is_send = 0;
+ u32 len = 0;
+ unsigned long flags;
+
+ if (!usb_autopm_get_interface(udev->intf))
+ usb_autopm_put_interface(udev->intf);
+
+ if (udev->usb_state == PM_SUSPEND)
+ return;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ if (!udev->send_complete) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ } else {
+ udev->send_complete = 0;
+ }
+
+ if (!list_empty(&tx->hci_list)) {
+ t = list_entry(tx->hci_list.next, struct usb_tx, list);
+ list_del(&t->list);
+ len = t->len;
+ t->is_sdu = 0;
+ is_send = 1;
+ } else if (!list_empty(&tx->sdu_list)) {
+ if (udev->tx_stop) {
+ udev->send_complete = 1;
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ }
+
+ t = alloc_tx_struct(TX_BUF_SIZE);
+ t->callback = NULL;
+ t->tx = tx;
+ t->is_sdu = 1;
+ is_send = 1;
+ }
+
+ if (!is_send) {
+ udev->send_complete = 1;
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (t->is_sdu)
+ len = packet_aggregation(udev, t->buf);
+
+ if (send_tx_packet(usbdev, t, len)) {
+ pr_err("send_tx_packet failed\n");
+ t->callback = NULL;
+ gdm_usb_send_complete(t->urb);
+ }
+}
+
+#define SDU_PARAM_LEN 12
+static int gdm_usb_sdu_send(void *priv_dev, void *data, int len,
+ unsigned int dftEpsId, unsigned int epsId,
+ void (*cb)(void *data), void *cb_data,
+ int dev_idx, int nic_type)
+{
+ struct lte_udev *udev = priv_dev;
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx_sdu *t_sdu;
+ struct sdu *sdu = NULL;
+ unsigned long flags;
+ int no_spc = 0;
+ u16 send_len;
+
+ if (!udev->usbdev) {
+ pr_err("sdu send - invalid device\n");
+ return TX_NO_DEV;
+ }
+
+ spin_lock_irqsave(&tx->lock, flags);
+ t_sdu = get_tx_sdu_struct(tx, &no_spc);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (t_sdu == NULL) {
+ pr_err("sdu send - free list empty\n");
+ return TX_NO_SPC;
+ }
+
+ sdu = (struct sdu *)t_sdu->buf;
+ sdu->cmd_evt = gdm_cpu_to_dev16(&udev->gdm_ed, LTE_TX_SDU);
+ if (nic_type == NIC_TYPE_ARP) {
+ send_len = len + SDU_PARAM_LEN;
+ memcpy(sdu->data, data, len);
+ } else {
+ send_len = len - ETH_HLEN;
+ send_len += SDU_PARAM_LEN;
+ memcpy(sdu->data, data+ETH_HLEN, len-ETH_HLEN);
+ }
+
+ sdu->len = gdm_cpu_to_dev16(&udev->gdm_ed, send_len);
+ sdu->dftEpsId = gdm_cpu_to_dev32(&udev->gdm_ed, dftEpsId);
+ sdu->bearer_ID = gdm_cpu_to_dev32(&udev->gdm_ed, epsId);
+ sdu->nic_type = gdm_cpu_to_dev32(&udev->gdm_ed, nic_type);
+
+ t_sdu->len = send_len + HCI_HEADER_SIZE;
+ t_sdu->callback = cb;
+ t_sdu->cb_data = cb_data;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ list_add_tail(&t_sdu->list, &tx->sdu_list);
+ queue_work(usb_tx_wq, &udev->work_tx.work);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (no_spc)
+ return TX_NO_BUFFER;
+
+ return 0;
+}
+
+static int gdm_usb_hci_send(void *priv_dev, void *data, int len,
+ void (*cb)(void *data), void *cb_data)
+{
+ struct lte_udev *udev = priv_dev;
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx *t;
+ unsigned long flags;
+
+ if (!udev->usbdev) {
+ pr_err("hci send - invalid device\n");
+ return -ENODEV;
+ }
+
+ t = alloc_tx_struct(len);
+ if (t == NULL) {
+ pr_err("hci_send - out of memory\n");
+ return -ENOMEM;
+ }
+
+ memcpy(t->buf, data, len);
+ t->callback = cb;
+ t->cb_data = cb_data;
+ t->len = len;
+ t->tx = tx;
+ t->is_sdu = 0;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ list_add_tail(&t->list, &tx->hci_list);
+ queue_work(usb_tx_wq, &udev->work_tx.work);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return 0;
+}
+
+static struct gdm_endian *gdm_usb_get_endian(void *priv_dev)
+{
+ struct lte_udev *udev = priv_dev;
+
+ return &udev->gdm_ed;
+}
+
+static int gdm_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret = 0;
+ struct phy_dev *phy_dev = NULL;
+ struct lte_udev *udev = NULL;
+ u16 idVendor, idProduct;
+ int bInterfaceNumber;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+
+ bInterfaceNumber = intf->cur_altsetting->desc.bInterfaceNumber;
+ idVendor = __le16_to_cpu(usbdev->descriptor.idVendor);
+ idProduct = __le16_to_cpu(usbdev->descriptor.idProduct);
+
+ pr_info("net vid = 0x%04x pid = 0x%04x\n", idVendor, idProduct);
+
+ if (bInterfaceNumber > NETWORK_INTERFACE) {
+ pr_info("not a network device\n");
+ return -ENODEV;
+ }
+
+ phy_dev = kzalloc(sizeof(struct phy_dev), GFP_KERNEL);
+ if (!phy_dev)
+ return -ENOMEM;
+
+ udev = kzalloc(sizeof(struct lte_udev), GFP_KERNEL);
+ if (!udev) {
+ ret = -ENOMEM;
+ goto err_udev;
+ }
+
+ phy_dev->priv_dev = (void *)udev;
+ phy_dev->send_hci_func = gdm_usb_hci_send;
+ phy_dev->send_sdu_func = gdm_usb_sdu_send;
+ phy_dev->rcv_func = gdm_usb_recv;
+ phy_dev->get_endian = gdm_usb_get_endian;
+
+ udev->usbdev = usbdev;
+ ret = init_usb(udev);
+ if (ret < 0) {
+ pr_err("init_usb func failed\n");
+ goto err_init_usb;
+ }
+ udev->intf = intf;
+
+ intf->needs_remote_wakeup = 1;
+ usb_enable_autosuspend(usbdev);
+ pm_runtime_set_autosuspend_delay(&usbdev->dev, AUTO_SUSPEND_TIMER);
+
+ /* List up hosts with big endians, otherwise,
+ * defaults to little endian
+ */
+ if (idProduct == PID_GDM7243)
+ gdm_set_endian(&udev->gdm_ed, ENDIANNESS_BIG);
+ else
+ gdm_set_endian(&udev->gdm_ed, ENDIANNESS_LITTLE);
+
+ ret = request_mac_address(udev);
+ if (ret < 0) {
+ pr_err("request Mac address failed\n");
+ goto err_mac_address;
+ }
+
+ start_rx_proc(phy_dev);
+ usb_get_dev(usbdev);
+ usb_set_intfdata(intf, phy_dev);
+
+ return 0;
+
+err_mac_address:
+ release_usb(udev);
+err_init_usb:
+ kfree(udev);
+err_udev:
+ kfree(phy_dev);
+
+ return ret;
+}
+
+static void gdm_usb_disconnect(struct usb_interface *intf)
+{
+ struct phy_dev *phy_dev;
+ struct lte_udev *udev;
+ u16 idVendor, idProduct;
+ struct usb_device *usbdev;
+ usbdev = interface_to_usbdev(intf);
+
+ idVendor = __le16_to_cpu(usbdev->descriptor.idVendor);
+ idProduct = __le16_to_cpu(usbdev->descriptor.idProduct);
+
+ phy_dev = usb_get_intfdata(intf);
+
+ udev = phy_dev->priv_dev;
+ unregister_lte_device(phy_dev);
+
+ release_usb(udev);
+
+ kfree(udev);
+ udev = NULL;
+
+ kfree(phy_dev);
+ phy_dev = NULL;
+
+ usb_put_dev(usbdev);
+}
+
+static int gdm_usb_suspend(struct usb_interface *intf, pm_message_t pm_msg)
+{
+ struct phy_dev *phy_dev;
+ struct lte_udev *udev;
+ struct rx_cxt *rx;
+ struct usb_rx *r;
+ struct usb_rx *r_next;
+ unsigned long flags;
+
+ phy_dev = usb_get_intfdata(intf);
+ udev = phy_dev->priv_dev;
+ rx = &udev->rx;
+ if (udev->usb_state != PM_NORMAL) {
+ pr_err("usb suspend - invalid state\n");
+ return -1;
+ }
+
+ udev->usb_state = PM_SUSPEND;
+
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ list_for_each_entry_safe(r, r_next, &rx->rx_submit_list, rx_submit_list)
+ {
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+ usb_kill_urb(r->urb);
+ spin_lock_irqsave(&rx->submit_lock, flags);
+ }
+ spin_unlock_irqrestore(&rx->submit_lock, flags);
+
+ return 0;
+}
+
+static int gdm_usb_resume(struct usb_interface *intf)
+{
+ struct phy_dev *phy_dev;
+ struct lte_udev *udev;
+ struct tx_cxt *tx;
+ struct rx_cxt *rx;
+ unsigned long flags;
+ int issue_count;
+ int i;
+
+ phy_dev = usb_get_intfdata(intf);
+ udev = phy_dev->priv_dev;
+ rx = &udev->rx;
+
+ if (udev->usb_state != PM_SUSPEND) {
+ pr_err("usb resume - invalid state\n");
+ return -1;
+ }
+ udev->usb_state = PM_NORMAL;
+
+ spin_lock_irqsave(&rx->rx_lock, flags);
+ issue_count = rx->avail_count - MAX_RX_SUBMIT_COUNT;
+ spin_unlock_irqrestore(&rx->rx_lock, flags);
+
+ if (issue_count >= 0) {
+ for (i = 0; i < issue_count; i++)
+ gdm_usb_recv(phy_dev->priv_dev,
+ udev->rx_cb,
+ phy_dev,
+ USB_COMPLETE);
+ }
+
+ tx = &udev->tx;
+ spin_lock_irqsave(&tx->lock, flags);
+ queue_work(usb_tx_wq, &udev->work_tx.work);
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ return 0;
+}
+
+static struct usb_driver gdm_usb_lte_driver = {
+ .name = "gdm_lte",
+ .probe = gdm_usb_probe,
+ .disconnect = gdm_usb_disconnect,
+ .id_table = id_table,
+ .supports_autosuspend = 1,
+ .suspend = gdm_usb_suspend,
+ .resume = gdm_usb_resume,
+ .reset_resume = gdm_usb_resume,
+};
+
+static int __init gdm_usb_lte_init(void)
+{
+ if (gdm_lte_event_init() < 0) {
+ pr_err("error creating event\n");
+ return -1;
+ }
+
+ usb_tx_wq = create_workqueue("usb_tx_wq");
+ if (usb_tx_wq == NULL)
+ return -1;
+
+ usb_rx_wq = create_workqueue("usb_rx_wq");
+ if (usb_rx_wq == NULL)
+ return -1;
+
+ return usb_register(&gdm_usb_lte_driver);
+}
+
+static void __exit gdm_usb_lte_exit(void)
+{
+ gdm_lte_event_exit();
+
+ usb_deregister(&gdm_usb_lte_driver);
+
+ if (usb_tx_wq) {
+ flush_workqueue(usb_tx_wq);
+ destroy_workqueue(usb_tx_wq);
+ }
+
+ if (usb_rx_wq) {
+ flush_workqueue(usb_rx_wq);
+ destroy_workqueue(usb_rx_wq);
+ }
+}
+
+module_init(gdm_usb_lte_init);
+module_exit(gdm_usb_lte_exit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_DESCRIPTION("GCT LTE USB Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/gdm724x/gdm_usb.h b/drivers/staging/gdm724x/gdm_usb.h
new file mode 100644
index 00000000000..e6486e71a42
--- /dev/null
+++ b/drivers/staging/gdm724x/gdm_usb.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _GDM_USB_H_
+#define _GDM_USB_H_
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/list.h>
+#include <linux/time.h>
+
+#include "gdm_endian.h"
+#include "hci_packet.h"
+
+#define PM_NORMAL 0
+#define PM_SUSPEND 1
+#define AUTO_SUSPEND_TIMER 5000 /* ms */
+
+#define RX_BUF_SIZE (1024*32)
+#define TX_BUF_SIZE (1024*32)
+#define SDU_BUF_SIZE 2048
+#define MAX_SDU_SIZE (1024*30)
+#define MAX_PACKET_IN_MULTI_SDU 256
+
+#define VID_GCT 0x1076
+#define PID_GDM7240 0x8000
+#define PID_GDM7243 0x9000
+
+#define NETWORK_INTERFACE 1
+#define USB_SC_SCSI 0x06
+#define USB_PR_BULK 0x50
+
+#define MAX_NUM_SDU_BUF 64
+
+struct usb_tx {
+ struct list_head list;
+ struct urb *urb;
+ u8 *buf;
+ u32 len;
+ void (*callback)(void *cb_data);
+ void *cb_data;
+ struct tx_cxt *tx;
+ u8 is_sdu;
+};
+
+struct usb_tx_sdu {
+ struct list_head list;
+ u8 *buf;
+ u32 len;
+ void (*callback)(void *cb_data);
+ void *cb_data;
+};
+
+struct usb_rx {
+ struct list_head to_host_list;
+ struct list_head free_list;
+ struct list_head rx_submit_list;
+ struct rx_cxt *rx;
+ struct urb *urb;
+ u8 *buf;
+ int (*callback)(void *cb_data, void *data, int len, int context);
+ void *cb_data;
+ void *index;
+};
+
+struct tx_cxt {
+ struct list_head sdu_list;
+ struct list_head hci_list;
+ struct list_head free_list;
+ u32 avail_count;
+ spinlock_t lock;
+};
+
+struct rx_cxt {
+ struct list_head to_host_list;
+ struct list_head rx_submit_list;
+ struct list_head free_list;
+ u32 avail_count;
+ spinlock_t to_host_lock;
+ spinlock_t rx_lock;
+ spinlock_t submit_lock;
+};
+
+struct lte_udev {
+ struct usb_device *usbdev;
+ struct gdm_endian gdm_ed;
+ struct tx_cxt tx;
+ struct rx_cxt rx;
+ struct delayed_work work_tx;
+ struct delayed_work work_rx;
+ u8 send_complete;
+ u8 tx_stop;
+ struct usb_interface *intf;
+ int (*rx_cb)(void *cb_data, void *data, int len, int context);
+ int usb_state;
+ u8 request_mac_addr;
+};
+
+#endif /* _GDM_USB_H_ */
diff --git a/drivers/staging/gdm724x/hci.h b/drivers/staging/gdm724x/hci.h
new file mode 100644
index 00000000000..9a591b0db51
--- /dev/null
+++ b/drivers/staging/gdm724x/hci.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _HCI_H_
+#define _HCI_H_
+
+#define LTE_GET_INFORMATION 0x3002
+#define LTE_GET_INFORMATION_RESULT 0xB003
+ #define MAC_ADDRESS 0xA2
+
+#define LTE_LINK_ON_OFF_INDICATION 0xB133
+#define LTE_PDN_TABLE_IND 0xB143
+
+#define LTE_TX_SDU 0x3200
+#define LTE_RX_SDU 0xB201
+#define LTE_TX_MULTI_SDU 0x3202
+#define LTE_RX_MULTI_SDU 0xB203
+
+#define LTE_DL_SDU_FLOW_CONTROL 0x3305
+#define LTE_UL_SDU_FLOW_CONTROL 0xB306
+
+#define LTE_AT_CMD_TO_DEVICE 0x3307
+#define LTE_AT_CMD_FROM_DEVICE 0xB308
+
+#define LTE_SDIO_DM_SEND_PKT 0x3312
+#define LTE_SDIO_DM_RECV_PKT 0xB313
+
+#define LTE_NV_RESTORE_REQUEST 0xB30C
+#define LTE_NV_RESTORE_RESPONSE 0x330D
+#define LTE_NV_SAVE_REQUEST 0xB30E
+ #define NV_TYPE_LTE_INFO 0x00
+ #define NV_TYPE_BOARD_CONFIG 0x01
+ #define NV_TYPE_RF_CAL 0x02
+ #define NV_TYPE_TEMP 0x03
+ #define NV_TYPE_NET_INFO 0x04
+ #define NV_TYPE_SAFETY_INFO 0x05
+ #define NV_TYPE_CDMA_CAL 0x06
+ #define NV_TYPE_VENDOR 0x07
+ #define NV_TYPE_ALL 0xff
+#define LTE_NV_SAVE_RESPONSE 0x330F
+
+#define LTE_AT_CMD_TO_DEVICE_EXT 0x3323
+#define LTE_AT_CMD_FROM_DEVICE_EXT 0xB324
+
+#endif /* _HCI_H_ */
diff --git a/drivers/staging/gdm724x/hci_packet.h b/drivers/staging/gdm724x/hci_packet.h
new file mode 100644
index 00000000000..7fba8a687fa
--- /dev/null
+++ b/drivers/staging/gdm724x/hci_packet.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _HCI_PACKET_H_
+#define _HCI_PACKET_H_
+
+#define HCI_HEADER_SIZE 4
+
+/*
+ * The NIC type definition:
+ * For backward compatibility, lower 16 bits used as they were.
+ * Lower 16 bit: NIC_TYPE values
+ * Uppoer 16 bit: NIC_TYPE Flags
+ */
+#define NIC_TYPE_NIC0 0x00000010
+#define NIC_TYPE_NIC1 0x00000011
+#define NIC_TYPE_NIC2 0x00000012
+#define NIC_TYPE_NIC3 0x00000013
+#define NIC_TYPE_ARP 0x00000100
+#define NIC_TYPE_ICMPV6 0x00000200
+#define NIC_TYPE_MASK 0x0000FFFF
+#define NIC_TYPE_F_IPV4 0x00010000
+#define NIC_TYPE_F_IPV6 0x00020000
+#define NIC_TYPE_F_DHCP 0x00040000
+#define NIC_TYPE_F_NDP 0x00080000
+#define NIC_TYPE_F_VLAN 0x00100000
+
+struct hci_packet {
+ u16 cmd_evt;
+ u16 len;
+ u8 data[0];
+} __packed;
+
+struct tlv {
+ u8 type;
+ u8 len;
+ u8 *data[1];
+} __packed;
+
+struct sdu_header {
+ u16 cmd_evt;
+ u16 len;
+ u32 dftEpsId;
+ u32 bearer_ID;
+ u32 nic_type;
+} __packed;
+
+struct sdu {
+ u16 cmd_evt;
+ u16 len;
+ u32 dftEpsId;
+ u32 bearer_ID;
+ u32 nic_type;
+ u8 data[0];
+} __packed;
+
+struct multi_sdu {
+ u16 cmd_evt;
+ u16 len;
+ u16 num_packet;
+ u16 reserved;
+ u8 data[0];
+} __packed;
+
+struct hci_pdn_table_ind {
+ u16 cmd_evt;
+ u16 len;
+ u8 activate;
+ u32 dft_eps_id;
+ u32 nic_type;
+ u8 pdn_type;
+ u8 ipv4_addr[4];
+ u8 ipv6_intf_id[8];
+} __packed;
+
+struct hci_connect_ind {
+ u16 cmd_evt;
+ u16 len;
+ u32 connect;
+} __packed;
+
+
+#endif /* _HCI_PACKET_H_ */
diff --git a/drivers/staging/gdm724x/netlink_k.c b/drivers/staging/gdm724x/netlink_k.c
new file mode 100644
index 00000000000..5ddd36948a2
--- /dev/null
+++ b/drivers/staging/gdm724x/netlink_k.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/export.h>
+#include <linux/etherdevice.h>
+#include <linux/netlink.h>
+#include <asm/byteorder.h>
+#include <net/sock.h>
+
+#include "netlink_k.h"
+
+#if defined(DEFINE_MUTEX)
+static DEFINE_MUTEX(netlink_mutex);
+#else
+static struct semaphore netlink_mutex;
+#define mutex_lock(x) down(x)
+#define mutex_unlock(x) up(x)
+#endif
+
+#define ND_MAX_GROUP 30
+#define ND_IFINDEX_LEN sizeof(int)
+#define ND_NLMSG_SPACE(len) (NLMSG_SPACE(len) + ND_IFINDEX_LEN)
+#define ND_NLMSG_DATA(nlh) ((void *)((char *)NLMSG_DATA(nlh) + \
+ ND_IFINDEX_LEN))
+#define ND_NLMSG_S_LEN(len) (len+ND_IFINDEX_LEN)
+#define ND_NLMSG_R_LEN(nlh) (nlh->nlmsg_len-ND_IFINDEX_LEN)
+#define ND_NLMSG_IFIDX(nlh) NLMSG_DATA(nlh)
+#define ND_MAX_MSG_LEN (1024 * 32)
+
+static void (*rcv_cb)(struct net_device *dev, u16 type, void *msg, int len);
+
+static void netlink_rcv_cb(struct sk_buff *skb)
+{
+ struct nlmsghdr *nlh;
+ struct net_device *dev;
+ u32 mlen;
+ void *msg;
+ int ifindex;
+
+ if (!rcv_cb) {
+ pr_err("nl cb - unregistered\n");
+ return;
+ }
+
+ if (skb->len < NLMSG_SPACE(0)) {
+ pr_err("nl cb - invalid skb length\n");
+ return;
+ }
+
+ nlh = (struct nlmsghdr *)skb->data;
+
+ if (skb->len < nlh->nlmsg_len || nlh->nlmsg_len > ND_MAX_MSG_LEN) {
+ pr_err("nl cb - invalid length (%d,%d)\n",
+ skb->len, nlh->nlmsg_len);
+ return;
+ }
+
+ memcpy(&ifindex, ND_NLMSG_IFIDX(nlh), ND_IFINDEX_LEN);
+ msg = ND_NLMSG_DATA(nlh);
+ mlen = ND_NLMSG_R_LEN(nlh);
+
+ dev = dev_get_by_index(&init_net, ifindex);
+ if (dev) {
+ rcv_cb(dev, nlh->nlmsg_type, msg, mlen);
+ dev_put(dev);
+ } else {
+ pr_err("nl cb - dev (%d) not found\n", ifindex);
+ }
+}
+
+static void netlink_rcv(struct sk_buff *skb)
+{
+ mutex_lock(&netlink_mutex);
+ netlink_rcv_cb(skb);
+ mutex_unlock(&netlink_mutex);
+}
+
+struct sock *netlink_init(int unit,
+ void (*cb)(struct net_device *dev, u16 type, void *msg, int len))
+{
+ struct sock *sock;
+ struct netlink_kernel_cfg cfg = {
+ .input = netlink_rcv,
+ };
+
+#if !defined(DEFINE_MUTEX)
+ init_MUTEX(&netlink_mutex);
+#endif
+
+ sock = netlink_kernel_create(&init_net, unit, &cfg);
+
+ if (sock)
+ rcv_cb = cb;
+
+ return sock;
+}
+
+void netlink_exit(struct sock *sock)
+{
+ sock_release(sock->sk_socket);
+}
+
+int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len)
+{
+ static u32 seq;
+ struct sk_buff *skb = NULL;
+ struct nlmsghdr *nlh;
+ int ret = 0;
+
+ if (group > ND_MAX_GROUP)
+ return -EINVAL;
+
+ if (!netlink_has_listeners(sock, group+1))
+ return -ESRCH;
+
+ skb = alloc_skb(NLMSG_SPACE(len), GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ seq++;
+
+ nlh = nlmsg_put(skb, 0, seq, type, len, 0);
+ memcpy(NLMSG_DATA(nlh), msg, len);
+ NETLINK_CB(skb).portid = 0;
+ NETLINK_CB(skb).dst_group = 0;
+
+ ret = netlink_broadcast(sock, skb, 0, group+1, GFP_ATOMIC);
+ if (!ret)
+ return len;
+
+ if (ret != -ESRCH)
+ pr_err("nl broadcast g=%d, t=%d, l=%d, r=%d\n",
+ group, type, len, ret);
+ else if (netlink_has_listeners(sock, group+1))
+ return -EAGAIN;
+
+ return ret;
+}
diff --git a/drivers/staging/gdm724x/netlink_k.h b/drivers/staging/gdm724x/netlink_k.h
new file mode 100644
index 00000000000..589486d7671
--- /dev/null
+++ b/drivers/staging/gdm724x/netlink_k.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 _NETLINK_K_H
+#define _NETLINK_K_H
+
+#include <linux/netdevice.h>
+#include <net/sock.h>
+
+struct sock *netlink_init(int unit,
+ void (*cb)(struct net_device *dev, u16 type, void *msg, int len));
+void netlink_exit(struct sock *sock);
+int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len);
+
+#endif /* _NETLINK_K_H_ */
diff --git a/drivers/staging/gdm72xx/Kconfig b/drivers/staging/gdm72xx/Kconfig
new file mode 100644
index 00000000000..dd8a3913f6b
--- /dev/null
+++ b/drivers/staging/gdm72xx/Kconfig
@@ -0,0 +1,46 @@
+#
+# GCT GDM72xx WiMAX driver configuration
+#
+
+menuconfig WIMAX_GDM72XX
+ tristate "GCT GDM72xx WiMAX support"
+ depends on NET && (USB || MMC)
+ help
+ Support for the GCT GDM72xx WiMAX chip
+
+if WIMAX_GDM72XX
+
+config WIMAX_GDM72XX_QOS
+ bool "Enable QoS support"
+ default n
+
+config WIMAX_GDM72XX_K_MODE
+ bool "Enable K mode"
+ default n
+
+config WIMAX_GDM72XX_WIMAX2
+ bool "Enable WiMAX2 support"
+ default n
+
+choice
+ prompt "Select interface"
+
+config WIMAX_GDM72XX_USB
+ bool "USB interface"
+ depends on (USB = y || USB = WIMAX_GDM72XX)
+
+config WIMAX_GDM72XX_SDIO
+ bool "SDIO interface"
+ depends on (MMC = y || MMC = WIMAX_GDM72XX)
+
+endchoice
+
+if WIMAX_GDM72XX_USB
+
+config WIMAX_GDM72XX_USB_PM
+ bool "Enable power management support"
+ depends on PM_RUNTIME
+
+endif # WIMAX_GDM72XX_USB
+
+endif # WIMAX_GDM72XX
diff --git a/drivers/staging/gdm72xx/Makefile b/drivers/staging/gdm72xx/Makefile
new file mode 100644
index 00000000000..35da7b90b19
--- /dev/null
+++ b/drivers/staging/gdm72xx/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_WIMAX_GDM72XX) := gdmwm.o
+
+gdmwm-y += gdm_wimax.o netlink_k.o
+gdmwm-$(CONFIG_WIMAX_GDM72XX_QOS) += gdm_qos.o
+gdmwm-$(CONFIG_WIMAX_GDM72XX_SDIO) += gdm_sdio.o sdio_boot.o
+gdmwm-$(CONFIG_WIMAX_GDM72XX_USB) += gdm_usb.o usb_boot.o
diff --git a/drivers/staging/gdm72xx/TODO b/drivers/staging/gdm72xx/TODO
new file mode 100644
index 00000000000..62d0cd6225c
--- /dev/null
+++ b/drivers/staging/gdm72xx/TODO
@@ -0,0 +1,2 @@
+TODO:
+- Clean up coding style to meet kernel standard.
diff --git a/drivers/staging/gdm72xx/gdm_qos.c b/drivers/staging/gdm72xx/gdm_qos.c
new file mode 100644
index 00000000000..df6f000534d
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_qos.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/etherdevice.h>
+#include <asm/byteorder.h>
+
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/if_ether.h>
+
+#include "gdm_wimax.h"
+#include "hci.h"
+#include "gdm_qos.h"
+
+#define B2H(x) __be16_to_cpu(x)
+
+#define MAX_FREE_LIST_CNT 32
+static struct {
+ struct list_head head;
+ int cnt;
+ spinlock_t lock;
+} qos_free_list;
+
+static void init_qos_entry_list(void)
+{
+ qos_free_list.cnt = 0;
+ INIT_LIST_HEAD(&qos_free_list.head);
+ spin_lock_init(&qos_free_list.lock);
+}
+
+static void *alloc_qos_entry(void)
+{
+ struct qos_entry_s *entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&qos_free_list.lock, flags);
+ if (qos_free_list.cnt) {
+ entry = list_entry(qos_free_list.head.prev, struct qos_entry_s,
+ list);
+ list_del(&entry->list);
+ qos_free_list.cnt--;
+ spin_unlock_irqrestore(&qos_free_list.lock, flags);
+ return entry;
+ }
+ spin_unlock_irqrestore(&qos_free_list.lock, flags);
+
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ return entry;
+}
+
+static void free_qos_entry(void *entry)
+{
+ struct qos_entry_s *qentry = (struct qos_entry_s *)entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&qos_free_list.lock, flags);
+ if (qos_free_list.cnt < MAX_FREE_LIST_CNT) {
+ list_add(&qentry->list, &qos_free_list.head);
+ qos_free_list.cnt++;
+ spin_unlock_irqrestore(&qos_free_list.lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&qos_free_list.lock, flags);
+
+ kfree(entry);
+}
+
+static void free_qos_entry_list(struct list_head *free_list)
+{
+ struct qos_entry_s *entry, *n;
+ int total_free = 0;
+
+ list_for_each_entry_safe(entry, n, free_list, list) {
+ list_del(&entry->list);
+ kfree(entry);
+ total_free++;
+ }
+
+ pr_debug("%s: total_free_cnt=%d\n", __func__, total_free);
+}
+
+void gdm_qos_init(void *nic_ptr)
+{
+ struct nic *nic = nic_ptr;
+ struct qos_cb_s *qcb = &nic->qos;
+ int i;
+
+ for (i = 0; i < QOS_MAX; i++) {
+ INIT_LIST_HEAD(&qcb->qos_list[i]);
+ qcb->csr[i].qos_buf_count = 0;
+ qcb->csr[i].enabled = 0;
+ }
+
+ qcb->qos_list_cnt = 0;
+ qcb->qos_null_idx = QOS_MAX-1;
+ qcb->qos_limit_size = 255;
+
+ spin_lock_init(&qcb->qos_lock);
+
+ init_qos_entry_list();
+}
+
+void gdm_qos_release_list(void *nic_ptr)
+{
+ struct nic *nic = nic_ptr;
+ struct qos_cb_s *qcb = &nic->qos;
+ unsigned long flags;
+ struct qos_entry_s *entry, *n;
+ struct list_head free_list;
+ int i;
+
+ INIT_LIST_HEAD(&free_list);
+
+ spin_lock_irqsave(&qcb->qos_lock, flags);
+
+ for (i = 0; i < QOS_MAX; i++) {
+ qcb->csr[i].qos_buf_count = 0;
+ qcb->csr[i].enabled = 0;
+ }
+
+ qcb->qos_list_cnt = 0;
+ qcb->qos_null_idx = QOS_MAX-1;
+
+ for (i = 0; i < QOS_MAX; i++) {
+ list_for_each_entry_safe(entry, n, &qcb->qos_list[i], list) {
+ list_move_tail(&entry->list, &free_list);
+ }
+ }
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ free_qos_entry_list(&free_list);
+}
+
+static u32 chk_ipv4_rule(struct gdm_wimax_csr_s *csr, u8 *stream, u8 *port)
+{
+ int i;
+
+ if (csr->classifier_rule_en&IPTYPEOFSERVICE) {
+ if (((stream[1] & csr->ip2s_mask) < csr->ip2s_lo) ||
+ ((stream[1] & csr->ip2s_mask) > csr->ip2s_hi))
+ return 1;
+ }
+
+ if (csr->classifier_rule_en&PROTOCOL) {
+ if (stream[9] != csr->protocol)
+ return 1;
+ }
+
+ if (csr->classifier_rule_en&IPMASKEDSRCADDRESS) {
+ for (i = 0; i < 4; i++) {
+ if ((stream[12 + i] & csr->ipsrc_addrmask[i]) !=
+ (csr->ipsrc_addr[i] & csr->ipsrc_addrmask[i]))
+ return 1;
+ }
+ }
+
+ if (csr->classifier_rule_en&IPMASKEDDSTADDRESS) {
+ for (i = 0; i < 4; i++) {
+ if ((stream[16 + i] & csr->ipdst_addrmask[i]) !=
+ (csr->ipdst_addr[i] & csr->ipdst_addrmask[i]))
+ return 1;
+ }
+ }
+
+ if (csr->classifier_rule_en&PROTOCOLSRCPORTRANGE) {
+ i = ((port[0]<<8)&0xff00)+port[1];
+ if ((i < csr->srcport_lo) || (i > csr->srcport_hi))
+ return 1;
+ }
+
+ if (csr->classifier_rule_en&PROTOCOLDSTPORTRANGE) {
+ i = ((port[2]<<8)&0xff00)+port[3];
+ if ((i < csr->dstport_lo) || (i > csr->dstport_hi))
+ return 1;
+ }
+
+ return 0;
+}
+
+static u32 get_qos_index(struct nic *nic, u8 *iph, u8 *tcpudph)
+{
+ u32 IP_ver, i;
+ struct qos_cb_s *qcb = &nic->qos;
+
+ if (iph == NULL || tcpudph == NULL)
+ return -1;
+
+ IP_ver = (iph[0]>>4)&0xf;
+
+ if (IP_ver != 4)
+ return -1;
+
+ for (i = 0; i < QOS_MAX; i++) {
+ if (!qcb->csr[i].enabled)
+ continue;
+ if (!qcb->csr[i].classifier_rule_en)
+ continue;
+ if (chk_ipv4_rule(&qcb->csr[i], iph, tcpudph) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static u32 extract_qos_list(struct nic *nic, struct list_head *head)
+{
+ struct qos_cb_s *qcb = &nic->qos;
+ struct qos_entry_s *entry;
+ int i;
+
+ INIT_LIST_HEAD(head);
+
+ for (i = 0; i < QOS_MAX; i++) {
+ if (!qcb->csr[i].enabled)
+ continue;
+ if (qcb->csr[i].qos_buf_count >= qcb->qos_limit_size)
+ continue;
+ if (list_empty(&qcb->qos_list[i]))
+ continue;
+
+ entry = list_entry(qcb->qos_list[i].prev, struct qos_entry_s,
+ list);
+
+ list_move_tail(&entry->list, head);
+ qcb->csr[i].qos_buf_count++;
+
+ if (!list_empty(&qcb->qos_list[i]))
+ netdev_warn(nic->netdev, "Index(%d) is piled!!\n", i);
+ }
+
+ return 0;
+}
+
+static void send_qos_list(struct nic *nic, struct list_head *head)
+{
+ struct qos_entry_s *entry, *n;
+
+ list_for_each_entry_safe(entry, n, head, list) {
+ list_del(&entry->list);
+ gdm_wimax_send_tx(entry->skb, entry->dev);
+ free_qos_entry(entry);
+ }
+}
+
+int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ int index;
+ struct qos_cb_s *qcb = &nic->qos;
+ unsigned long flags;
+ struct ethhdr *ethh = (struct ethhdr *)(skb->data + HCI_HEADER_SIZE);
+ struct iphdr *iph = (struct iphdr *)((char *)ethh + ETH_HLEN);
+ struct tcphdr *tcph;
+ struct qos_entry_s *entry = NULL;
+ struct list_head send_list;
+ int ret = 0;
+
+ tcph = (struct tcphdr *)iph + iph->ihl*4;
+
+ if (B2H(ethh->h_proto) == ETH_P_IP) {
+ if (qcb->qos_list_cnt && !qos_free_list.cnt) {
+ entry = alloc_qos_entry();
+ entry->skb = skb;
+ entry->dev = dev;
+ netdev_dbg(dev, "qcb->qos_list_cnt=%d\n",
+ qcb->qos_list_cnt);
+ }
+
+ spin_lock_irqsave(&qcb->qos_lock, flags);
+ if (qcb->qos_list_cnt) {
+ index = get_qos_index(nic, (u8 *)iph, (u8 *)tcph);
+ if (index == -1)
+ index = qcb->qos_null_idx;
+
+ if (!entry) {
+ entry = alloc_qos_entry();
+ entry->skb = skb;
+ entry->dev = dev;
+ }
+
+ list_add_tail(&entry->list, &qcb->qos_list[index]);
+ extract_qos_list(nic, &send_list);
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ send_qos_list(nic, &send_list);
+ goto out;
+ }
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ if (entry)
+ free_qos_entry(entry);
+ }
+
+ ret = gdm_wimax_send_tx(skb, dev);
+out:
+ return ret;
+}
+
+static u32 get_csr(struct qos_cb_s *qcb, u32 SFID, int mode)
+{
+ int i;
+
+ for (i = 0; i < qcb->qos_list_cnt; i++) {
+ if (qcb->csr[i].SFID == SFID)
+ return i;
+ }
+
+ if (mode) {
+ for (i = 0; i < QOS_MAX; i++) {
+ if (qcb->csr[i].enabled == 0) {
+ qcb->csr[i].enabled = 1;
+ qcb->qos_list_cnt++;
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+#define QOS_CHANGE_DEL 0xFC
+#define QOS_ADD 0xFD
+#define QOS_REPORT 0xFE
+
+void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size)
+{
+ struct nic *nic = nic_ptr;
+ u32 i, SFID, index, pos;
+ u8 sub_cmd_evt;
+ struct qos_cb_s *qcb = &nic->qos;
+ struct qos_entry_s *entry, *n;
+ struct list_head send_list;
+ struct list_head free_list;
+ unsigned long flags;
+
+ sub_cmd_evt = (u8)buf[4];
+
+ if (sub_cmd_evt == QOS_REPORT) {
+ spin_lock_irqsave(&qcb->qos_lock, flags);
+ for (i = 0; i < qcb->qos_list_cnt; i++) {
+ SFID = ((buf[(i*5)+6]<<24)&0xff000000);
+ SFID += ((buf[(i*5)+7]<<16)&0xff0000);
+ SFID += ((buf[(i*5)+8]<<8)&0xff00);
+ SFID += (buf[(i*5)+9]);
+ index = get_csr(qcb, SFID, 0);
+ if (index == -1) {
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ netdev_err(nic->netdev, "QoS ERROR: No SF\n");
+ return;
+ }
+ qcb->csr[index].qos_buf_count = buf[(i*5)+10];
+ }
+
+ extract_qos_list(nic, &send_list);
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ send_qos_list(nic, &send_list);
+ return;
+ }
+
+ /* sub_cmd_evt == QOS_ADD || sub_cmd_evt == QOS_CHANG_DEL */
+ pos = 6;
+ SFID = ((buf[pos++]<<24)&0xff000000);
+ SFID += ((buf[pos++]<<16)&0xff0000);
+ SFID += ((buf[pos++]<<8)&0xff00);
+ SFID += (buf[pos++]);
+
+ index = get_csr(qcb, SFID, 1);
+ if (index == -1) {
+ netdev_err(nic->netdev,
+ "QoS ERROR: csr Update Error / Wrong index (%d)\n",
+ index);
+ return;
+ }
+
+ if (sub_cmd_evt == QOS_ADD) {
+ netdev_dbg(nic->netdev, "QOS_ADD SFID = 0x%x, index=%d\n",
+ SFID, index);
+
+ spin_lock_irqsave(&qcb->qos_lock, flags);
+ qcb->csr[index].SFID = SFID;
+ qcb->csr[index].classifier_rule_en = ((buf[pos++]<<8)&0xff00);
+ qcb->csr[index].classifier_rule_en += buf[pos++];
+ if (qcb->csr[index].classifier_rule_en == 0)
+ qcb->qos_null_idx = index;
+ qcb->csr[index].ip2s_mask = buf[pos++];
+ qcb->csr[index].ip2s_lo = buf[pos++];
+ qcb->csr[index].ip2s_hi = buf[pos++];
+ qcb->csr[index].protocol = buf[pos++];
+ qcb->csr[index].ipsrc_addrmask[0] = buf[pos++];
+ qcb->csr[index].ipsrc_addrmask[1] = buf[pos++];
+ qcb->csr[index].ipsrc_addrmask[2] = buf[pos++];
+ qcb->csr[index].ipsrc_addrmask[3] = buf[pos++];
+ qcb->csr[index].ipsrc_addr[0] = buf[pos++];
+ qcb->csr[index].ipsrc_addr[1] = buf[pos++];
+ qcb->csr[index].ipsrc_addr[2] = buf[pos++];
+ qcb->csr[index].ipsrc_addr[3] = buf[pos++];
+ qcb->csr[index].ipdst_addrmask[0] = buf[pos++];
+ qcb->csr[index].ipdst_addrmask[1] = buf[pos++];
+ qcb->csr[index].ipdst_addrmask[2] = buf[pos++];
+ qcb->csr[index].ipdst_addrmask[3] = buf[pos++];
+ qcb->csr[index].ipdst_addr[0] = buf[pos++];
+ qcb->csr[index].ipdst_addr[1] = buf[pos++];
+ qcb->csr[index].ipdst_addr[2] = buf[pos++];
+ qcb->csr[index].ipdst_addr[3] = buf[pos++];
+ qcb->csr[index].srcport_lo = ((buf[pos++]<<8)&0xff00);
+ qcb->csr[index].srcport_lo += buf[pos++];
+ qcb->csr[index].srcport_hi = ((buf[pos++]<<8)&0xff00);
+ qcb->csr[index].srcport_hi += buf[pos++];
+ qcb->csr[index].dstport_lo = ((buf[pos++]<<8)&0xff00);
+ qcb->csr[index].dstport_lo += buf[pos++];
+ qcb->csr[index].dstport_hi = ((buf[pos++]<<8)&0xff00);
+ qcb->csr[index].dstport_hi += buf[pos++];
+
+ qcb->qos_limit_size = 254/qcb->qos_list_cnt;
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ } else if (sub_cmd_evt == QOS_CHANGE_DEL) {
+ netdev_dbg(nic->netdev, "QOS_CHANGE_DEL SFID = 0x%x, index=%d\n",
+ SFID, index);
+
+ INIT_LIST_HEAD(&free_list);
+
+ spin_lock_irqsave(&qcb->qos_lock, flags);
+ qcb->csr[index].enabled = 0;
+ qcb->qos_list_cnt--;
+ qcb->qos_limit_size = 254/qcb->qos_list_cnt;
+
+ list_for_each_entry_safe(entry, n, &qcb->qos_list[index],
+ list) {
+ list_move_tail(&entry->list, &free_list);
+ }
+ spin_unlock_irqrestore(&qcb->qos_lock, flags);
+ free_qos_entry_list(&free_list);
+ }
+}
diff --git a/drivers/staging/gdm72xx/gdm_qos.h b/drivers/staging/gdm72xx/gdm_qos.h
new file mode 100644
index 00000000000..6543cff2a87
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_qos.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#if !defined(GDM_QOS_H_20090403)
+#define GDM_QOS_H_20090403
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/list.h>
+
+#define BOOLEAN u8
+
+#define QOS_MAX 16
+#define IPTYPEOFSERVICE 0x8000
+#define PROTOCOL 0x4000
+#define IPMASKEDSRCADDRESS 0x2000
+#define IPMASKEDDSTADDRESS 0x1000
+#define PROTOCOLSRCPORTRANGE 0x800
+#define PROTOCOLDSTPORTRANGE 0x400
+#define DSTMACADDR 0x200
+#define SRCMACADDR 0x100
+#define ETHERTYPE 0x80
+#define IEEE802_1DUSERPRIORITY 0x40
+#define IEEE802_1QVLANID 0x10
+
+struct gdm_wimax_csr_s {
+ BOOLEAN enabled;
+ u32 SFID;
+ u8 qos_buf_count;
+ u16 classifier_rule_en;
+ u8 ip2s_lo;
+ u8 ip2s_hi;
+ u8 ip2s_mask;
+ u8 protocol;
+ u8 ipsrc_addr[16];
+ u8 ipsrc_addrmask[16];
+ u8 ipdst_addr[16];
+ u8 ipdst_addrmask[16];
+ u16 srcport_lo;
+ u16 srcport_hi;
+ u16 dstport_lo;
+ u16 dstport_hi;
+};
+
+struct qos_entry_s {
+ struct list_head list;
+ struct sk_buff *skb;
+ struct net_device *dev;
+
+};
+
+struct qos_cb_s {
+ struct list_head qos_list[QOS_MAX];
+ u32 qos_list_cnt;
+ u32 qos_null_idx;
+ struct gdm_wimax_csr_s csr[QOS_MAX];
+ spinlock_t qos_lock;
+ u32 qos_limit_size;
+};
+
+void gdm_qos_init(void *nic_ptr);
+void gdm_qos_release_list(void *nic_ptr);
+int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev);
+void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size);
+
+#endif
diff --git a/drivers/staging/gdm72xx/gdm_sdio.c b/drivers/staging/gdm72xx/gdm_sdio.c
new file mode 100644
index 00000000000..7398d451ccc
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_sdio.c
@@ -0,0 +1,708 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#include "gdm_sdio.h"
+#include "gdm_wimax.h"
+#include "sdio_boot.h"
+#include "hci.h"
+
+#define TYPE_A_HEADER_SIZE 4
+#define TYPE_A_LOOKAHEAD_SIZE 16
+
+#define MAX_NR_RX_BUF 4
+
+#define SDU_TX_BUF_SIZE 2048
+#define TX_BUF_SIZE 2048
+#define TX_CHUNK_SIZE (2048 - TYPE_A_HEADER_SIZE)
+#define RX_BUF_SIZE (25*1024)
+
+#define TX_HZ 2000
+#define TX_INTERVAL (1000000/TX_HZ)
+
+static int init_sdio(struct sdiowm_dev *sdev);
+static void release_sdio(struct sdiowm_dev *sdev);
+
+static struct sdio_tx *alloc_tx_struct(struct tx_cxt *tx)
+{
+ struct sdio_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC);
+
+ if (!t)
+ return NULL;
+
+ t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC);
+ if (!t->buf) {
+ kfree(t);
+ return NULL;
+ }
+
+ t->tx_cxt = tx;
+
+ return t;
+}
+
+static void free_tx_struct(struct sdio_tx *t)
+{
+ if (t) {
+ kfree(t->buf);
+ kfree(t);
+ }
+}
+
+static struct sdio_rx *alloc_rx_struct(struct rx_cxt *rx)
+{
+ struct sdio_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC);
+
+ if (r)
+ r->rx_cxt = rx;
+
+ return r;
+}
+
+static void free_rx_struct(struct sdio_rx *r)
+{
+ kfree(r);
+}
+
+/* Before this function is called, spin lock should be locked. */
+static struct sdio_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc)
+{
+ struct sdio_tx *t;
+
+ if (list_empty(&tx->free_list))
+ return NULL;
+
+ t = list_entry(tx->free_list.prev, struct sdio_tx, list);
+ list_del(&t->list);
+
+ *no_spc = list_empty(&tx->free_list) ? 1 : 0;
+
+ return t;
+}
+
+/* Before this function is called, spin lock should be locked. */
+static void put_tx_struct(struct tx_cxt *tx, struct sdio_tx *t)
+{
+ list_add_tail(&t->list, &tx->free_list);
+}
+
+/* Before this function is called, spin lock should be locked. */
+static struct sdio_rx *get_rx_struct(struct rx_cxt *rx)
+{
+ struct sdio_rx *r;
+
+ if (list_empty(&rx->free_list))
+ return NULL;
+
+ r = list_entry(rx->free_list.prev, struct sdio_rx, list);
+ list_del(&r->list);
+
+ return r;
+}
+
+/* Before this function is called, spin lock should be locked. */
+static void put_rx_struct(struct rx_cxt *rx, struct sdio_rx *r)
+{
+ list_add_tail(&r->list, &rx->free_list);
+}
+
+static int init_sdio(struct sdiowm_dev *sdev)
+{
+ int ret = 0, i;
+ struct tx_cxt *tx = &sdev->tx;
+ struct rx_cxt *rx = &sdev->rx;
+ struct sdio_tx *t;
+ struct sdio_rx *r;
+
+ INIT_LIST_HEAD(&tx->free_list);
+ INIT_LIST_HEAD(&tx->sdu_list);
+ INIT_LIST_HEAD(&tx->hci_list);
+
+ spin_lock_init(&tx->lock);
+
+ tx->sdu_buf = kmalloc(SDU_TX_BUF_SIZE, GFP_KERNEL);
+ if (tx->sdu_buf == NULL)
+ goto fail;
+
+ for (i = 0; i < MAX_NR_SDU_BUF; i++) {
+ t = alloc_tx_struct(tx);
+ if (t == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ list_add(&t->list, &tx->free_list);
+ }
+
+ INIT_LIST_HEAD(&rx->free_list);
+ INIT_LIST_HEAD(&rx->req_list);
+
+ spin_lock_init(&rx->lock);
+
+ for (i = 0; i < MAX_NR_RX_BUF; i++) {
+ r = alloc_rx_struct(rx);
+ if (r == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+ list_add(&r->list, &rx->free_list);
+ }
+
+ rx->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL);
+ if (rx->rx_buf == NULL)
+ goto fail;
+
+ return 0;
+
+fail:
+ release_sdio(sdev);
+ return ret;
+}
+
+static void release_sdio(struct sdiowm_dev *sdev)
+{
+ struct tx_cxt *tx = &sdev->tx;
+ struct rx_cxt *rx = &sdev->rx;
+ struct sdio_tx *t, *t_next;
+ struct sdio_rx *r, *r_next;
+
+ kfree(tx->sdu_buf);
+
+ list_for_each_entry_safe(t, t_next, &tx->free_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ list_for_each_entry_safe(t, t_next, &tx->hci_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ kfree(rx->rx_buf);
+
+ list_for_each_entry_safe(r, r_next, &rx->free_list, list) {
+ list_del(&r->list);
+ free_rx_struct(r);
+ }
+
+ list_for_each_entry_safe(r, r_next, &rx->req_list, list) {
+ list_del(&r->list);
+ free_rx_struct(r);
+ }
+}
+
+static void send_sdio_pkt(struct sdio_func *func, u8 *data, int len)
+{
+ int n, blocks, ret, remain;
+
+ sdio_claim_host(func);
+
+ blocks = len / func->cur_blksize;
+ n = blocks * func->cur_blksize;
+ if (blocks) {
+ ret = sdio_memcpy_toio(func, 0, data, n);
+ if (ret < 0) {
+ if (ret != -ENOMEDIUM)
+ dev_err(&func->dev,
+ "gdmwms: %s error: ret = %d\n",
+ __func__, ret);
+ goto end_io;
+ }
+ }
+
+ remain = len - n;
+ remain = (remain + 3) & ~3;
+
+ if (remain) {
+ ret = sdio_memcpy_toio(func, 0, data + n, remain);
+ if (ret < 0) {
+ if (ret != -ENOMEDIUM)
+ dev_err(&func->dev,
+ "gdmwms: %s error: ret = %d\n",
+ __func__, ret);
+ goto end_io;
+ }
+ }
+
+end_io:
+ sdio_release_host(func);
+}
+
+static void send_sdu(struct sdio_func *func, struct tx_cxt *tx)
+{
+ struct list_head *l, *next;
+ struct hci_s *hci;
+ struct sdio_tx *t;
+ int pos, len, i, estlen, aggr_num = 0, aggr_len;
+ u8 *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ pos = TYPE_A_HEADER_SIZE + HCI_HEADER_SIZE;
+ list_for_each_entry(t, &tx->sdu_list, list) {
+ estlen = ((t->len + 3) & ~3) + 4;
+ if ((pos + estlen) > SDU_TX_BUF_SIZE)
+ break;
+
+ aggr_num++;
+ memcpy(tx->sdu_buf + pos, t->buf, t->len);
+ memset(tx->sdu_buf + pos + t->len, 0, estlen - t->len);
+ pos += estlen;
+ }
+ aggr_len = pos;
+
+ hci = (struct hci_s *)(tx->sdu_buf + TYPE_A_HEADER_SIZE);
+ hci->cmd_evt = H2B(WIMAX_TX_SDU_AGGR);
+ hci->length = H2B(aggr_len - TYPE_A_HEADER_SIZE - HCI_HEADER_SIZE);
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ print_hex_dump_debug("sdio_send: ", DUMP_PREFIX_NONE, 16, 1,
+ tx->sdu_buf + TYPE_A_HEADER_SIZE,
+ aggr_len - TYPE_A_HEADER_SIZE, false);
+
+ for (pos = TYPE_A_HEADER_SIZE; pos < aggr_len; pos += TX_CHUNK_SIZE) {
+ len = aggr_len - pos;
+ len = len > TX_CHUNK_SIZE ? TX_CHUNK_SIZE : len;
+ buf = tx->sdu_buf + pos - TYPE_A_HEADER_SIZE;
+
+ buf[0] = len & 0xff;
+ buf[1] = (len >> 8) & 0xff;
+ buf[2] = (len >> 16) & 0xff;
+ buf[3] = (pos + len) >= aggr_len ? 0 : 1;
+ send_sdio_pkt(func, buf, len + TYPE_A_HEADER_SIZE);
+ }
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ for (l = tx->sdu_list.next, i = 0; i < aggr_num; i++, l = next) {
+ next = l->next;
+ t = list_entry(l, struct sdio_tx, list);
+ if (t->callback)
+ t->callback(t->cb_data);
+
+ list_del(l);
+ put_tx_struct(t->tx_cxt, t);
+ }
+
+ do_gettimeofday(&tx->sdu_stamp);
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+static void send_hci(struct sdio_func *func, struct tx_cxt *tx,
+ struct sdio_tx *t)
+{
+ unsigned long flags;
+
+ print_hex_dump_debug("sdio_send: ", DUMP_PREFIX_NONE, 16, 1,
+ t->buf + TYPE_A_HEADER_SIZE,
+ t->len - TYPE_A_HEADER_SIZE, false);
+ send_sdio_pkt(func, t->buf, t->len);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ if (t->callback)
+ t->callback(t->cb_data);
+ free_tx_struct(t);
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+static void do_tx(struct work_struct *work)
+{
+ struct sdiowm_dev *sdev = container_of(work, struct sdiowm_dev, ws);
+ struct sdio_func *func = sdev->func;
+ struct tx_cxt *tx = &sdev->tx;
+ struct sdio_tx *t = NULL;
+ struct timeval now, *before;
+ int is_sdu = 0;
+ long diff;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ if (!tx->can_send) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ }
+
+ if (!list_empty(&tx->hci_list)) {
+ t = list_entry(tx->hci_list.next, struct sdio_tx, list);
+ list_del(&t->list);
+ is_sdu = 0;
+ } else if (!tx->stop_sdu_tx && !list_empty(&tx->sdu_list)) {
+ do_gettimeofday(&now);
+ before = &tx->sdu_stamp;
+
+ diff = (now.tv_sec - before->tv_sec) * 1000000 +
+ (now.tv_usec - before->tv_usec);
+ if (diff >= 0 && diff < TX_INTERVAL) {
+ schedule_work(&sdev->ws);
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ }
+ is_sdu = 1;
+ }
+
+ if (!is_sdu && t == NULL) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return;
+ }
+
+ tx->can_send = 0;
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (is_sdu)
+ send_sdu(func, tx);
+ else
+ send_hci(func, tx, t);
+}
+
+static int gdm_sdio_send(void *priv_dev, void *data, int len,
+ void (*cb)(void *data), void *cb_data)
+{
+ struct sdiowm_dev *sdev = priv_dev;
+ struct tx_cxt *tx = &sdev->tx;
+ struct sdio_tx *t;
+ u8 *pkt = data;
+ int no_spc = 0;
+ u16 cmd_evt;
+ unsigned long flags;
+
+ BUG_ON(len > TX_BUF_SIZE - TYPE_A_HEADER_SIZE);
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ cmd_evt = (pkt[0] << 8) | pkt[1];
+ if (cmd_evt == WIMAX_TX_SDU) {
+ t = get_tx_struct(tx, &no_spc);
+ if (t == NULL) {
+ /* This case must not happen. */
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return -ENOSPC;
+ }
+ list_add_tail(&t->list, &tx->sdu_list);
+
+ memcpy(t->buf, data, len);
+
+ t->len = len;
+ t->callback = cb;
+ t->cb_data = cb_data;
+ } else {
+ t = alloc_tx_struct(tx);
+ if (t == NULL) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return -ENOMEM;
+ }
+ list_add_tail(&t->list, &tx->hci_list);
+
+ t->buf[0] = len & 0xff;
+ t->buf[1] = (len >> 8) & 0xff;
+ t->buf[2] = (len >> 16) & 0xff;
+ t->buf[3] = 2;
+ memcpy(t->buf + TYPE_A_HEADER_SIZE, data, len);
+
+ t->len = len + TYPE_A_HEADER_SIZE;
+ t->callback = cb;
+ t->cb_data = cb_data;
+ }
+
+ if (tx->can_send)
+ schedule_work(&sdev->ws);
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (no_spc)
+ return -ENOSPC;
+
+ return 0;
+}
+
+/*
+ * Handle the HCI, WIMAX_SDU_TX_FLOW.
+ */
+static int control_sdu_tx_flow(struct sdiowm_dev *sdev, u8 *hci_data, int len)
+{
+ struct tx_cxt *tx = &sdev->tx;
+ u16 cmd_evt;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ cmd_evt = (hci_data[0] << 8) | (hci_data[1]);
+ if (cmd_evt != WIMAX_SDU_TX_FLOW)
+ goto out;
+
+ if (hci_data[4] == 0) {
+ dev_dbg(&sdev->func->dev, "WIMAX ==> STOP SDU TX\n");
+ tx->stop_sdu_tx = 1;
+ } else if (hci_data[4] == 1) {
+ dev_dbg(&sdev->func->dev, "WIMAX ==> START SDU TX\n");
+ tx->stop_sdu_tx = 0;
+ if (tx->can_send)
+ schedule_work(&sdev->ws);
+ /*
+ * If free buffer for sdu tx doesn't exist, then tx queue
+ * should not be woken. For this reason, don't pass the command,
+ * START_SDU_TX.
+ */
+ if (list_empty(&tx->free_list))
+ len = 0;
+ }
+
+out:
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return len;
+}
+
+static void gdm_sdio_irq(struct sdio_func *func)
+{
+ struct phy_dev *phy_dev = sdio_get_drvdata(func);
+ struct sdiowm_dev *sdev = phy_dev->priv_dev;
+ struct tx_cxt *tx = &sdev->tx;
+ struct rx_cxt *rx = &sdev->rx;
+ struct sdio_rx *r;
+ unsigned long flags;
+ u8 val, hdr[TYPE_A_LOOKAHEAD_SIZE], *buf;
+ u32 len, blocks, n;
+ int ret, remain;
+
+ /* Check interrupt */
+ val = sdio_readb(func, 0x13, &ret);
+ if (val & 0x01)
+ sdio_writeb(func, 0x01, 0x13, &ret); /* clear interrupt */
+ else
+ return;
+
+ ret = sdio_memcpy_fromio(func, hdr, 0x0, TYPE_A_LOOKAHEAD_SIZE);
+ if (ret) {
+ dev_err(&func->dev,
+ "Cannot read from function %d\n", func->num);
+ goto done;
+ }
+
+ len = (hdr[2] << 16) | (hdr[1] << 8) | hdr[0];
+ if (len > (RX_BUF_SIZE - TYPE_A_HEADER_SIZE)) {
+ dev_err(&func->dev, "Too big Type-A size: %d\n", len);
+ goto done;
+ }
+
+ if (hdr[3] == 1) { /* Ack */
+ u32 *ack_seq = (u32 *)&hdr[4];
+
+ spin_lock_irqsave(&tx->lock, flags);
+ tx->can_send = 1;
+
+ if (!list_empty(&tx->sdu_list) || !list_empty(&tx->hci_list))
+ schedule_work(&sdev->ws);
+ spin_unlock_irqrestore(&tx->lock, flags);
+ dev_dbg(&func->dev, "Ack... %0x\n", ntohl(*ack_seq));
+ goto done;
+ }
+
+ memcpy(rx->rx_buf, hdr + TYPE_A_HEADER_SIZE,
+ TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE);
+
+ buf = rx->rx_buf + TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE;
+ remain = len - TYPE_A_LOOKAHEAD_SIZE + TYPE_A_HEADER_SIZE;
+ if (remain <= 0)
+ goto end_io;
+
+ blocks = remain / func->cur_blksize;
+
+ if (blocks) {
+ n = blocks * func->cur_blksize;
+ ret = sdio_memcpy_fromio(func, buf, 0x0, n);
+ if (ret) {
+ dev_err(&func->dev,
+ "Cannot read from function %d\n", func->num);
+ goto done;
+ }
+ buf += n;
+ remain -= n;
+ }
+
+ if (remain) {
+ ret = sdio_memcpy_fromio(func, buf, 0x0, remain);
+ if (ret) {
+ dev_err(&func->dev,
+ "Cannot read from function %d\n", func->num);
+ goto done;
+ }
+ }
+
+end_io:
+ print_hex_dump_debug("sdio_receive: ", DUMP_PREFIX_NONE, 16, 1,
+ rx->rx_buf, len, false);
+ len = control_sdu_tx_flow(sdev, rx->rx_buf, len);
+
+ spin_lock_irqsave(&rx->lock, flags);
+
+ if (!list_empty(&rx->req_list)) {
+ r = list_entry(rx->req_list.next, struct sdio_rx, list);
+ spin_unlock_irqrestore(&rx->lock, flags);
+ if (r->callback)
+ r->callback(r->cb_data, rx->rx_buf, len);
+ spin_lock_irqsave(&rx->lock, flags);
+ list_del(&r->list);
+ put_rx_struct(rx, r);
+ }
+
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+done:
+ sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */
+ if (!phy_dev->netdev)
+ register_wimax_device(phy_dev, &func->dev);
+}
+
+static int gdm_sdio_receive(void *priv_dev,
+ void (*cb)(void *cb_data, void *data, int len),
+ void *cb_data)
+{
+ struct sdiowm_dev *sdev = priv_dev;
+ struct rx_cxt *rx = &sdev->rx;
+ struct sdio_rx *r;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rx->lock, flags);
+ r = get_rx_struct(rx);
+ if (r == NULL) {
+ spin_unlock_irqrestore(&rx->lock, flags);
+ return -ENOMEM;
+ }
+
+ r->callback = cb;
+ r->cb_data = cb_data;
+
+ list_add_tail(&r->list, &rx->req_list);
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ return 0;
+}
+
+static int sdio_wimax_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ int ret;
+ struct phy_dev *phy_dev = NULL;
+ struct sdiowm_dev *sdev = NULL;
+
+ dev_info(&func->dev, "Found GDM SDIO VID = 0x%04x PID = 0x%04x...\n",
+ func->vendor, func->device);
+ dev_info(&func->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION);
+
+ sdio_claim_host(func);
+ sdio_enable_func(func);
+ sdio_claim_irq(func, gdm_sdio_irq);
+
+ ret = sdio_boot(func);
+ if (ret)
+ return ret;
+
+ phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL);
+ if (phy_dev == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+ if (sdev == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ phy_dev->priv_dev = (void *)sdev;
+ phy_dev->send_func = gdm_sdio_send;
+ phy_dev->rcv_func = gdm_sdio_receive;
+
+ ret = init_sdio(sdev);
+ if (ret < 0)
+ goto out;
+
+ sdev->func = func;
+
+ sdio_writeb(func, 1, 0x14, &ret); /* Enable interrupt */
+ sdio_release_host(func);
+
+ INIT_WORK(&sdev->ws, do_tx);
+
+ sdio_set_drvdata(func, phy_dev);
+out:
+ if (ret) {
+ kfree(phy_dev);
+ kfree(sdev);
+ }
+
+ return ret;
+}
+
+static void sdio_wimax_remove(struct sdio_func *func)
+{
+ struct phy_dev *phy_dev = sdio_get_drvdata(func);
+ struct sdiowm_dev *sdev = phy_dev->priv_dev;
+
+ cancel_work_sync(&sdev->ws);
+ if (phy_dev->netdev)
+ unregister_wimax_device(phy_dev);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ release_sdio(sdev);
+
+ kfree(sdev);
+ kfree(phy_dev);
+}
+
+static const struct sdio_device_id sdio_wimax_ids[] = {
+ { SDIO_DEVICE(0x0296, 0x5347) },
+ {0}
+};
+
+MODULE_DEVICE_TABLE(sdio, sdio_wimax_ids);
+
+static struct sdio_driver sdio_wimax_driver = {
+ .probe = sdio_wimax_probe,
+ .remove = sdio_wimax_remove,
+ .name = "sdio_wimax",
+ .id_table = sdio_wimax_ids,
+};
+
+static int __init sdio_gdm_wimax_init(void)
+{
+ return sdio_register_driver(&sdio_wimax_driver);
+}
+
+static void __exit sdio_gdm_wimax_exit(void)
+{
+ sdio_unregister_driver(&sdio_wimax_driver);
+}
+
+module_init(sdio_gdm_wimax_init);
+module_exit(sdio_gdm_wimax_exit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_DESCRIPTION("GCT WiMax SDIO Device Driver");
+MODULE_AUTHOR("Ethan Park");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/gdm72xx/gdm_sdio.h b/drivers/staging/gdm72xx/gdm_sdio.h
new file mode 100644
index 00000000000..0c0e2cbb727
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_sdio.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __GDM_SDIO_H__
+#define __GDM_SDIO_H__
+
+#include <linux/types.h>
+#include <linux/time.h>
+
+#define MAX_NR_SDU_BUF 64
+
+struct sdio_tx {
+ struct list_head list;
+ struct tx_cxt *tx_cxt;
+ u8 *buf;
+ int len;
+ void (*callback)(void *cb_data);
+ void *cb_data;
+};
+
+struct tx_cxt {
+ struct list_head free_list;
+ struct list_head sdu_list;
+ struct list_head hci_list;
+ struct timeval sdu_stamp;
+ u8 *sdu_buf;
+ spinlock_t lock;
+ int can_send;
+ int stop_sdu_tx;
+};
+
+struct sdio_rx {
+ struct list_head list;
+ struct rx_cxt *rx_cxt;
+ void (*callback)(void *cb_data, void *data, int len);
+ void *cb_data;
+};
+
+struct rx_cxt {
+ struct list_head free_list;
+ struct list_head req_list;
+ u8 *rx_buf;
+ spinlock_t lock;
+};
+
+struct sdiowm_dev {
+ struct sdio_func *func;
+ struct tx_cxt tx;
+ struct rx_cxt rx;
+ struct work_struct ws;
+};
+
+#endif /* __GDM_SDIO_H__ */
diff --git a/drivers/staging/gdm72xx/gdm_usb.c b/drivers/staging/gdm72xx/gdm_usb.c
new file mode 100644
index 00000000000..78d6667fa0d
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_usb.c
@@ -0,0 +1,798 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <asm/byteorder.h>
+#include <linux/kthread.h>
+
+#include "gdm_usb.h"
+#include "gdm_wimax.h"
+#include "usb_boot.h"
+#include "hci.h"
+
+#include "usb_ids.h"
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+#define TX_BUF_SIZE 2048
+
+#if defined(CONFIG_WIMAX_GDM72XX_WIMAX2)
+#define RX_BUF_SIZE (128*1024) /* For packet aggregation */
+#else
+#define RX_BUF_SIZE 2048
+#endif
+
+#define GDM7205_PADDING 256
+
+#define H2B(x) __cpu_to_be16(x)
+#define B2H(x) __be16_to_cpu(x)
+#define DB2H(x) __be32_to_cpu(x)
+
+#define DOWNLOAD_CONF_VALUE 0x21
+
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+
+static DECLARE_WAIT_QUEUE_HEAD(k_wait);
+static LIST_HEAD(k_list);
+static DEFINE_SPINLOCK(k_lock);
+static int k_mode_stop;
+
+#define K_WAIT_TIME (2 * HZ / 100)
+
+#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
+
+static int init_usb(struct usbwm_dev *udev);
+static void release_usb(struct usbwm_dev *udev);
+
+static struct usb_tx *alloc_tx_struct(struct tx_cxt *tx)
+{
+ struct usb_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC);
+
+ if (!t)
+ return NULL;
+
+ t->urb = usb_alloc_urb(0, GFP_ATOMIC);
+ t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC);
+ if (!t->urb || !t->buf) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ return NULL;
+ }
+
+ t->tx_cxt = tx;
+
+ return t;
+}
+
+static void free_tx_struct(struct usb_tx *t)
+{
+ if (t) {
+ usb_free_urb(t->urb);
+ kfree(t->buf);
+ kfree(t);
+ }
+}
+
+static struct usb_rx *alloc_rx_struct(struct rx_cxt *rx)
+{
+ struct usb_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC);
+
+ if (!r)
+ return NULL;
+
+ r->urb = usb_alloc_urb(0, GFP_ATOMIC);
+ r->buf = kmalloc(RX_BUF_SIZE, GFP_ATOMIC);
+ if (!r->urb || !r->buf) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ return NULL;
+ }
+
+ r->rx_cxt = rx;
+ return r;
+}
+
+static void free_rx_struct(struct usb_rx *r)
+{
+ if (r) {
+ usb_free_urb(r->urb);
+ kfree(r->buf);
+ kfree(r);
+ }
+}
+
+/* Before this function is called, spin lock should be locked. */
+static struct usb_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc)
+{
+ struct usb_tx *t;
+
+ if (list_empty(&tx->free_list)) {
+ *no_spc = 1;
+ return NULL;
+ }
+
+ t = list_entry(tx->free_list.next, struct usb_tx, list);
+ list_del(&t->list);
+
+ *no_spc = list_empty(&tx->free_list) ? 1 : 0;
+
+ return t;
+}
+
+/* Before this function is called, spin lock should be locked. */
+static void put_tx_struct(struct tx_cxt *tx, struct usb_tx *t)
+{
+ list_add_tail(&t->list, &tx->free_list);
+}
+
+/* Before this function is called, spin lock should be locked. */
+static struct usb_rx *get_rx_struct(struct rx_cxt *rx)
+{
+ struct usb_rx *r;
+
+ if (list_empty(&rx->free_list)) {
+ r = alloc_rx_struct(rx);
+ if (r == NULL)
+ return NULL;
+
+ list_add(&r->list, &rx->free_list);
+ }
+
+ r = list_entry(rx->free_list.next, struct usb_rx, list);
+ list_move_tail(&r->list, &rx->used_list);
+
+ return r;
+}
+
+/* Before this function is called, spin lock should be locked. */
+static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r)
+{
+ list_move(&r->list, &rx->free_list);
+}
+
+static int init_usb(struct usbwm_dev *udev)
+{
+ int ret = 0, i;
+ struct tx_cxt *tx = &udev->tx;
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_tx *t;
+ struct usb_rx *r;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&tx->free_list);
+ INIT_LIST_HEAD(&tx->sdu_list);
+ INIT_LIST_HEAD(&tx->hci_list);
+#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
+ INIT_LIST_HEAD(&tx->pending_list);
+#endif
+
+ INIT_LIST_HEAD(&rx->free_list);
+ INIT_LIST_HEAD(&rx->used_list);
+
+ spin_lock_init(&tx->lock);
+ spin_lock_init(&rx->lock);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ for (i = 0; i < MAX_NR_SDU_BUF; i++) {
+ t = alloc_tx_struct(tx);
+ if (t == NULL) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ ret = -ENOMEM;
+ goto fail;
+ }
+ list_add(&t->list, &tx->free_list);
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ r = alloc_rx_struct(rx);
+ if (r == NULL) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ spin_lock_irqsave(&rx->lock, flags);
+ list_add(&r->list, &rx->free_list);
+ spin_unlock_irqrestore(&rx->lock, flags);
+ return ret;
+
+fail:
+ release_usb(udev);
+ return ret;
+}
+
+static void release_usb(struct usbwm_dev *udev)
+{
+ struct tx_cxt *tx = &udev->tx;
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_tx *t, *t_next;
+ struct usb_rx *r, *r_next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ list_for_each_entry_safe(t, t_next, &tx->hci_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ list_for_each_entry_safe(t, t_next, &tx->free_list, list) {
+ list_del(&t->list);
+ free_tx_struct(t);
+ }
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ spin_lock_irqsave(&rx->lock, flags);
+
+ list_for_each_entry_safe(r, r_next, &rx->free_list, list) {
+ list_del(&r->list);
+ free_rx_struct(r);
+ }
+
+ list_for_each_entry_safe(r, r_next, &rx->used_list, list) {
+ list_del(&r->list);
+ free_rx_struct(r);
+ }
+
+ spin_unlock_irqrestore(&rx->lock, flags);
+}
+
+static void __gdm_usb_send_complete(struct urb *urb)
+{
+ struct usb_tx *t = urb->context;
+ struct tx_cxt *tx = t->tx_cxt;
+ u8 *pkt = t->buf;
+ u16 cmd_evt;
+
+ /* Completion by usb_unlink_urb */
+ if (urb->status == -ECONNRESET)
+ return;
+
+ if (t->callback)
+ t->callback(t->cb_data);
+
+ /* Delete from sdu list or hci list. */
+ list_del(&t->list);
+
+ cmd_evt = (pkt[0] << 8) | pkt[1];
+ if (cmd_evt == WIMAX_TX_SDU)
+ put_tx_struct(tx, t);
+ else
+ free_tx_struct(t);
+}
+
+static void gdm_usb_send_complete(struct urb *urb)
+{
+ struct usb_tx *t = urb->context;
+ struct tx_cxt *tx = t->tx_cxt;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tx->lock, flags);
+ __gdm_usb_send_complete(urb);
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+
+static int gdm_usb_send(void *priv_dev, void *data, int len,
+ void (*cb)(void *data), void *cb_data)
+{
+ struct usbwm_dev *udev = priv_dev;
+ struct usb_device *usbdev = udev->usbdev;
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx *t;
+ int padding = udev->padding;
+ int no_spc = 0, ret;
+ u8 *pkt = data;
+ u16 cmd_evt;
+ unsigned long flags;
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+ unsigned long flags2;
+#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
+
+ if (!udev->usbdev) {
+ dev_err(&usbdev->dev, "%s: No such device\n", __func__);
+ return -ENODEV;
+ }
+
+ BUG_ON(len > TX_BUF_SIZE - padding - 1);
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ cmd_evt = (pkt[0] << 8) | pkt[1];
+ if (cmd_evt == WIMAX_TX_SDU) {
+ t = get_tx_struct(tx, &no_spc);
+ if (t == NULL) {
+ /* This case must not happen. */
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return -ENOSPC;
+ }
+ list_add_tail(&t->list, &tx->sdu_list);
+ } else {
+ t = alloc_tx_struct(tx);
+ if (t == NULL) {
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return -ENOMEM;
+ }
+ list_add_tail(&t->list, &tx->hci_list);
+ }
+
+ memcpy(t->buf + padding, data, len);
+ t->callback = cb;
+ t->cb_data = cb_data;
+
+ /*
+ * In some cases, USB Module of WiMax is blocked when data size is
+ * the multiple of 512. So, increment length by one in that case.
+ */
+ if ((len % 512) == 0)
+ len++;
+
+ usb_fill_bulk_urb(t->urb, usbdev, usb_sndbulkpipe(usbdev, 1), t->buf,
+ len + padding, gdm_usb_send_complete, t);
+
+ print_hex_dump_debug("usb_send: ", DUMP_PREFIX_NONE, 16, 1, t->buf,
+ len + padding, false);
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ if (usbdev->state & USB_STATE_SUSPENDED) {
+ list_add_tail(&t->p_list, &tx->pending_list);
+ schedule_work(&udev->pm_ws);
+ goto out;
+ }
+#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
+
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+ if (udev->bw_switch) {
+ list_add_tail(&t->p_list, &tx->pending_list);
+ goto out;
+ } else if (cmd_evt == WIMAX_SCAN) {
+ struct rx_cxt *rx;
+ struct usb_rx *r;
+
+ rx = &udev->rx;
+
+ spin_lock_irqsave(&rx->lock, flags2);
+ list_for_each_entry(r, &rx->used_list, list)
+ usb_unlink_urb(r->urb);
+ spin_unlock_irqrestore(&rx->lock, flags2);
+
+ udev->bw_switch = 1;
+
+ spin_lock_irqsave(&k_lock, flags2);
+ list_add_tail(&udev->list, &k_list);
+ spin_unlock_irqrestore(&k_lock, flags2);
+
+ wake_up(&k_wait);
+ }
+#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
+
+ ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+ if (ret)
+ goto send_fail;
+
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ usb_mark_last_busy(usbdev);
+#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
+
+#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
+out:
+#endif
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ if (no_spc)
+ return -ENOSPC;
+
+ return 0;
+
+send_fail:
+ t->callback = NULL;
+ __gdm_usb_send_complete(t->urb);
+ spin_unlock_irqrestore(&tx->lock, flags);
+ return ret;
+}
+
+static void gdm_usb_rcv_complete(struct urb *urb)
+{
+ struct usb_rx *r = urb->context;
+ struct rx_cxt *rx = r->rx_cxt;
+ struct usbwm_dev *udev = container_of(r->rx_cxt, struct usbwm_dev, rx);
+ struct tx_cxt *tx = &udev->tx;
+ struct usb_tx *t;
+ u16 cmd_evt;
+ unsigned long flags, flags2;
+ struct usb_device *dev = urb->dev;
+
+ /* Completion by usb_unlink_urb */
+ if (urb->status == -ECONNRESET)
+ return;
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ if (!urb->status) {
+ cmd_evt = (r->buf[0] << 8) | (r->buf[1]);
+ print_hex_dump_debug("usb_receive: ", DUMP_PREFIX_NONE, 16, 1,
+ r->buf, urb->actual_length, false);
+ if (cmd_evt == WIMAX_SDU_TX_FLOW) {
+ if (r->buf[4] == 0) {
+ dev_dbg(&dev->dev, "WIMAX ==> STOP SDU TX\n");
+ list_for_each_entry(t, &tx->sdu_list, list)
+ usb_unlink_urb(t->urb);
+ } else if (r->buf[4] == 1) {
+ dev_dbg(&dev->dev, "WIMAX ==> START SDU TX\n");
+ list_for_each_entry(t, &tx->sdu_list, list) {
+ usb_submit_urb(t->urb, GFP_ATOMIC);
+ }
+ /*
+ * If free buffer for sdu tx doesn't
+ * exist, then tx queue should not be
+ * woken. For this reason, don't pass
+ * the command, START_SDU_TX.
+ */
+ if (list_empty(&tx->free_list))
+ urb->actual_length = 0;
+ }
+ }
+ }
+
+ if (!urb->status && r->callback)
+ r->callback(r->cb_data, r->buf, urb->actual_length);
+
+ spin_lock_irqsave(&rx->lock, flags2);
+ put_rx_struct(rx, r);
+ spin_unlock_irqrestore(&rx->lock, flags2);
+
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ usb_mark_last_busy(dev);
+#endif
+}
+
+static int gdm_usb_receive(void *priv_dev,
+ void (*cb)(void *cb_data, void *data, int len),
+ void *cb_data)
+{
+ struct usbwm_dev *udev = priv_dev;
+ struct usb_device *usbdev = udev->usbdev;
+ struct rx_cxt *rx = &udev->rx;
+ struct usb_rx *r;
+ unsigned long flags;
+
+ if (!udev->usbdev) {
+ dev_err(&usbdev->dev, "%s: No such device\n", __func__);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&rx->lock, flags);
+ r = get_rx_struct(rx);
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ if (r == NULL)
+ return -ENOMEM;
+
+ r->callback = cb;
+ r->cb_data = cb_data;
+
+ usb_fill_bulk_urb(r->urb, usbdev, usb_rcvbulkpipe(usbdev, 0x82), r->buf,
+ RX_BUF_SIZE, gdm_usb_rcv_complete, r);
+
+ return usb_submit_urb(r->urb, GFP_ATOMIC);
+}
+
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+static void do_pm_control(struct work_struct *work)
+{
+ struct usbwm_dev *udev = container_of(work, struct usbwm_dev, pm_ws);
+ struct tx_cxt *tx = &udev->tx;
+ int ret;
+ unsigned long flags;
+
+ ret = usb_autopm_get_interface(udev->intf);
+ if (!ret)
+ usb_autopm_put_interface(udev->intf);
+
+ spin_lock_irqsave(&tx->lock, flags);
+ if (!(udev->usbdev->state & USB_STATE_SUSPENDED) &&
+ (!list_empty(&tx->hci_list) || !list_empty(&tx->sdu_list))) {
+ struct usb_tx *t, *temp;
+
+ list_for_each_entry_safe(t, temp, &tx->pending_list, p_list) {
+ list_del(&t->p_list);
+ ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+
+ if (ret) {
+ t->callback = NULL;
+ __gdm_usb_send_complete(t->urb);
+ }
+ }
+ }
+ spin_unlock_irqrestore(&tx->lock, flags);
+}
+#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
+
+static int gdm_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret = 0;
+ u8 bConfigurationValue;
+ struct phy_dev *phy_dev = NULL;
+ struct usbwm_dev *udev = NULL;
+ u16 idVendor, idProduct, bcdDevice;
+
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+
+ usb_get_dev(usbdev);
+ bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue;
+
+ /*USB description is set up with Little-Endian*/
+ idVendor = L2H(usbdev->descriptor.idVendor);
+ idProduct = L2H(usbdev->descriptor.idProduct);
+ bcdDevice = L2H(usbdev->descriptor.bcdDevice);
+
+ dev_info(&intf->dev, "Found GDM USB VID = 0x%04x PID = 0x%04x...\n",
+ idVendor, idProduct);
+ dev_info(&intf->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION);
+
+
+ if (idProduct == EMERGENCY_PID) {
+ ret = usb_emergency(usbdev);
+ goto out;
+ }
+
+ /* Support for EEPROM bootloader */
+ if (bConfigurationValue == DOWNLOAD_CONF_VALUE ||
+ idProduct & B_DOWNLOAD) {
+ ret = usb_boot(usbdev, bcdDevice);
+ goto out;
+ }
+
+ phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL);
+ if (phy_dev == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ udev = kzalloc(sizeof(*udev), GFP_KERNEL);
+ if (udev == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (idProduct == 0x7205 || idProduct == 0x7206)
+ udev->padding = GDM7205_PADDING;
+ else
+ udev->padding = 0;
+
+ phy_dev->priv_dev = (void *)udev;
+ phy_dev->send_func = gdm_usb_send;
+ phy_dev->rcv_func = gdm_usb_receive;
+
+ ret = init_usb(udev);
+ if (ret < 0)
+ goto out;
+
+ udev->usbdev = usbdev;
+
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ udev->intf = intf;
+
+ intf->needs_remote_wakeup = 1;
+ device_init_wakeup(&intf->dev, 1);
+
+ pm_runtime_set_autosuspend_delay(&usbdev->dev, 10 * 1000); /* msec */
+
+ INIT_WORK(&udev->pm_ws, do_pm_control);
+#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
+
+ ret = register_wimax_device(phy_dev, &intf->dev);
+ if (ret)
+ release_usb(udev);
+
+out:
+ if (ret) {
+ kfree(phy_dev);
+ kfree(udev);
+ usb_put_dev(usbdev);
+ } else {
+ usb_set_intfdata(intf, phy_dev);
+ }
+ return ret;
+}
+
+static void gdm_usb_disconnect(struct usb_interface *intf)
+{
+ u8 bConfigurationValue;
+ struct phy_dev *phy_dev;
+ struct usbwm_dev *udev;
+ u16 idProduct;
+ struct usb_device *usbdev = interface_to_usbdev(intf);
+
+ bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue;
+ phy_dev = usb_get_intfdata(intf);
+
+ /*USB description is set up with Little-Endian*/
+ idProduct = L2H(usbdev->descriptor.idProduct);
+
+ if (idProduct != EMERGENCY_PID &&
+ bConfigurationValue != DOWNLOAD_CONF_VALUE &&
+ (idProduct & B_DOWNLOAD) == 0) {
+
+ udev = phy_dev->priv_dev;
+ udev->usbdev = NULL;
+
+ unregister_wimax_device(phy_dev);
+ release_usb(udev);
+ kfree(udev);
+ kfree(phy_dev);
+ }
+
+ usb_put_dev(usbdev);
+}
+
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+static int gdm_suspend(struct usb_interface *intf, pm_message_t pm_msg)
+{
+ struct phy_dev *phy_dev;
+ struct usbwm_dev *udev;
+ struct rx_cxt *rx;
+ struct usb_rx *r;
+ unsigned long flags;
+
+ phy_dev = usb_get_intfdata(intf);
+ if (!phy_dev)
+ return 0;
+
+ udev = phy_dev->priv_dev;
+ rx = &udev->rx;
+
+ spin_lock_irqsave(&rx->lock, flags);
+
+ list_for_each_entry(r, &rx->used_list, list)
+ usb_unlink_urb(r->urb);
+
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ return 0;
+}
+
+static int gdm_resume(struct usb_interface *intf)
+{
+ struct phy_dev *phy_dev;
+ struct usbwm_dev *udev;
+ struct rx_cxt *rx;
+ struct usb_rx *r;
+ unsigned long flags;
+
+ phy_dev = usb_get_intfdata(intf);
+ if (!phy_dev)
+ return 0;
+
+ udev = phy_dev->priv_dev;
+ rx = &udev->rx;
+
+ spin_lock_irqsave(&rx->lock, flags);
+
+ list_for_each_entry(r, &rx->used_list, list)
+ usb_submit_urb(r->urb, GFP_ATOMIC);
+
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ return 0;
+}
+
+#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */
+
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+static int k_mode_thread(void *arg)
+{
+ struct usbwm_dev *udev;
+ struct tx_cxt *tx;
+ struct rx_cxt *rx;
+ struct usb_tx *t, *temp;
+ struct usb_rx *r;
+ unsigned long flags, flags2, expire;
+ int ret;
+
+ while (!k_mode_stop) {
+
+ spin_lock_irqsave(&k_lock, flags2);
+ while (!list_empty(&k_list)) {
+
+ udev = list_entry(k_list.next, struct usbwm_dev, list);
+ tx = &udev->tx;
+ rx = &udev->rx;
+
+ list_del(&udev->list);
+ spin_unlock_irqrestore(&k_lock, flags2);
+
+ expire = jiffies + K_WAIT_TIME;
+ while (time_before(jiffies, expire))
+ schedule_timeout(K_WAIT_TIME);
+
+ spin_lock_irqsave(&rx->lock, flags);
+
+ list_for_each_entry(r, &rx->used_list, list)
+ usb_submit_urb(r->urb, GFP_ATOMIC);
+
+ spin_unlock_irqrestore(&rx->lock, flags);
+
+ spin_lock_irqsave(&tx->lock, flags);
+
+ list_for_each_entry_safe(t, temp, &tx->pending_list,
+ p_list) {
+ list_del(&t->p_list);
+ ret = usb_submit_urb(t->urb, GFP_ATOMIC);
+
+ if (ret) {
+ t->callback = NULL;
+ __gdm_usb_send_complete(t->urb);
+ }
+ }
+
+ udev->bw_switch = 0;
+ spin_unlock_irqrestore(&tx->lock, flags);
+
+ spin_lock_irqsave(&k_lock, flags2);
+ }
+ wait_event_interruptible_lock_irq(k_wait,
+ !list_empty(&k_list) ||
+ k_mode_stop, k_lock);
+ spin_unlock_irqrestore(&k_lock, flags2);
+ }
+ return 0;
+}
+#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
+
+static struct usb_driver gdm_usb_driver = {
+ .name = "gdm_wimax",
+ .probe = gdm_usb_probe,
+ .disconnect = gdm_usb_disconnect,
+ .id_table = id_table,
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ .supports_autosuspend = 1,
+ .suspend = gdm_suspend,
+ .resume = gdm_resume,
+ .reset_resume = gdm_resume,
+#endif
+};
+
+static int __init usb_gdm_wimax_init(void)
+{
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+ kthread_run(k_mode_thread, NULL, "k_mode_wimax");
+#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */
+ return usb_register(&gdm_usb_driver);
+}
+
+static void __exit usb_gdm_wimax_exit(void)
+{
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+ k_mode_stop = 1;
+ wake_up(&k_wait);
+#endif
+ usb_deregister(&gdm_usb_driver);
+}
+
+module_init(usb_gdm_wimax_init);
+module_exit(usb_gdm_wimax_exit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_DESCRIPTION("GCT WiMax Device Driver");
+MODULE_AUTHOR("Ethan Park");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/gdm72xx/gdm_usb.h b/drivers/staging/gdm72xx/gdm_usb.h
new file mode 100644
index 00000000000..30506529a8c
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_usb.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __GDM_USB_H__
+#define __GDM_USB_H__
+
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/list.h>
+
+#define B_DIFF_DL_DRV (1 << 4)
+#define B_DOWNLOAD (1 << 5)
+#define MAX_NR_SDU_BUF 64
+
+struct usb_tx {
+ struct list_head list;
+#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
+ struct list_head p_list;
+#endif
+ struct tx_cxt *tx_cxt;
+ struct urb *urb;
+ u8 *buf;
+ void (*callback)(void *cb_data);
+ void *cb_data;
+};
+
+struct tx_cxt {
+ struct list_head free_list;
+ struct list_head sdu_list;
+ struct list_head hci_list;
+#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE)
+ struct list_head pending_list;
+#endif
+ spinlock_t lock;
+};
+
+struct usb_rx {
+ struct list_head list;
+ struct rx_cxt *rx_cxt;
+ struct urb *urb;
+ u8 *buf;
+ void (*callback)(void *cb_data, void *data, int len);
+ void *cb_data;
+};
+
+struct rx_cxt {
+ struct list_head free_list;
+ struct list_head used_list;
+ spinlock_t lock;
+};
+
+struct usbwm_dev {
+ struct usb_device *usbdev;
+#ifdef CONFIG_WIMAX_GDM72XX_USB_PM
+ struct work_struct pm_ws;
+
+ struct usb_interface *intf;
+#endif
+#ifdef CONFIG_WIMAX_GDM72XX_K_MODE
+ int bw_switch;
+ struct list_head list;
+#endif
+ struct tx_cxt tx;
+ struct rx_cxt rx;
+ int padding;
+};
+
+#endif /* __GDM_USB_H__ */
diff --git a/drivers/staging/gdm72xx/gdm_wimax.c b/drivers/staging/gdm72xx/gdm_wimax.c
new file mode 100644
index 00000000000..e5e51158512
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_wimax.c
@@ -0,0 +1,962 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/etherdevice.h>
+#include <asm/byteorder.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#include <linux/in.h>
+
+#include "gdm_wimax.h"
+#include "hci.h"
+#include "wm_ioctl.h"
+#include "netlink_k.h"
+
+#define gdm_wimax_send(n, d, l) \
+ (n->phy_dev->send_func)(n->phy_dev->priv_dev, d, l, NULL, NULL)
+#define gdm_wimax_send_with_cb(n, d, l, c, b) \
+ (n->phy_dev->send_func)(n->phy_dev->priv_dev, d, l, c, b)
+#define gdm_wimax_rcv_with_cb(n, c, b) \
+ (n->phy_dev->rcv_func)(n->phy_dev->priv_dev, c, b)
+
+#define EVT_MAX_SIZE 2048
+
+struct evt_entry {
+ struct list_head list;
+ struct net_device *dev;
+ char evt_data[EVT_MAX_SIZE];
+ int size;
+};
+
+static void __gdm_wimax_event_send(struct work_struct *work);
+static inline struct evt_entry *alloc_event_entry(void);
+static inline void free_event_entry(struct evt_entry *e);
+static struct evt_entry *get_event_entry(void);
+static void put_event_entry(struct evt_entry *e);
+
+static struct {
+ int ref_cnt;
+ struct sock *sock;
+ struct list_head evtq;
+ spinlock_t evt_lock;
+ struct list_head freeq;
+ struct work_struct ws;
+} wm_event;
+
+static u8 gdm_wimax_macaddr[6] = {0x00, 0x0a, 0x3b, 0xf0, 0x01, 0x30};
+
+static void gdm_wimax_ind_fsm_update(struct net_device *dev, struct fsm_s *fsm);
+static void gdm_wimax_ind_if_updown(struct net_device *dev, int if_up);
+
+static const char *get_protocol_name(u16 protocol)
+{
+ static char buf[32];
+ const char *name = "-";
+
+ switch (protocol) {
+ case ETH_P_ARP:
+ name = "ARP";
+ break;
+ case ETH_P_IP:
+ name = "IP";
+ break;
+ case ETH_P_IPV6:
+ name = "IPv6";
+ break;
+ }
+
+ sprintf(buf, "0x%04x(%s)", protocol, name);
+ return buf;
+}
+
+static const char *get_ip_protocol_name(u8 ip_protocol)
+{
+ static char buf[32];
+ const char *name = "-";
+
+ switch (ip_protocol) {
+ case IPPROTO_TCP:
+ name = "TCP";
+ break;
+ case IPPROTO_UDP:
+ name = "UDP";
+ break;
+ case IPPROTO_ICMP:
+ name = "ICMP";
+ break;
+ }
+
+ sprintf(buf, "%u(%s)", ip_protocol, name);
+ return buf;
+}
+
+static const char *get_port_name(u16 port)
+{
+ static char buf[32];
+ const char *name = "-";
+
+ switch (port) {
+ case 67:
+ name = "DHCP-Server";
+ break;
+ case 68:
+ name = "DHCP-Client";
+ break;
+ case 69:
+ name = "TFTP";
+ break;
+ }
+
+ sprintf(buf, "%u(%s)", port, name);
+ return buf;
+}
+
+static void dump_eth_packet(struct net_device *dev, const char *title,
+ u8 *data, int len)
+{
+ struct iphdr *ih = NULL;
+ struct udphdr *uh = NULL;
+ u16 protocol = 0;
+ u8 ip_protocol = 0;
+ u16 port = 0;
+
+ protocol = (data[12]<<8) | data[13];
+ ih = (struct iphdr *)(data+ETH_HLEN);
+
+ if (protocol == ETH_P_IP) {
+ uh = (struct udphdr *)((char *)ih + sizeof(struct iphdr));
+ ip_protocol = ih->protocol;
+ port = ntohs(uh->dest);
+ } else if (protocol == ETH_P_IPV6) {
+ struct ipv6hdr *i6h = (struct ipv6hdr *)data;
+
+ uh = (struct udphdr *)((char *)i6h + sizeof(struct ipv6hdr));
+ ip_protocol = i6h->nexthdr;
+ port = ntohs(uh->dest);
+ }
+
+ netdev_dbg(dev, "[%s] len=%d, %s, %s, %s\n", title, len,
+ get_protocol_name(protocol),
+ get_ip_protocol_name(ip_protocol),
+ get_port_name(port));
+
+ if (!(data[0] == 0xff && data[1] == 0xff)) {
+ if (protocol == ETH_P_IP)
+ netdev_dbg(dev, " src=%pI4\n", &ih->saddr);
+ else if (protocol == ETH_P_IPV6)
+ netdev_dbg(dev, " src=%pI6\n", &ih->saddr);
+ }
+
+ print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, data, len, false);
+}
+
+static inline int gdm_wimax_header(struct sk_buff **pskb)
+{
+ u16 buf[HCI_HEADER_SIZE / sizeof(u16)];
+ struct sk_buff *skb = *pskb;
+
+ if (unlikely(skb_headroom(skb) < HCI_HEADER_SIZE)) {
+ struct sk_buff *skb2;
+
+ skb2 = skb_realloc_headroom(skb, HCI_HEADER_SIZE);
+ if (skb2 == NULL)
+ return -ENOMEM;
+ if (skb->sk)
+ skb_set_owner_w(skb2, skb->sk);
+ kfree_skb(skb);
+ skb = skb2;
+ }
+
+ skb_push(skb, HCI_HEADER_SIZE);
+ buf[0] = H2B(WIMAX_TX_SDU);
+ buf[1] = H2B(skb->len - HCI_HEADER_SIZE);
+ memcpy(skb->data, buf, HCI_HEADER_SIZE);
+
+ *pskb = skb;
+ return 0;
+}
+
+static void gdm_wimax_event_rcv(struct net_device *dev, u16 type, void *msg,
+ int len)
+{
+ struct nic *nic = netdev_priv(dev);
+
+ u8 *buf = (u8 *)msg;
+ u16 hci_cmd = (buf[0]<<8) | buf[1];
+ u16 hci_len = (buf[2]<<8) | buf[3];
+
+ netdev_dbg(dev, "H=>D: 0x%04x(%d)\n", hci_cmd, hci_len);
+
+ gdm_wimax_send(nic, msg, len);
+}
+
+static int gdm_wimax_event_init(void)
+{
+ if (!wm_event.ref_cnt) {
+ wm_event.sock = netlink_init(NETLINK_WIMAX,
+ gdm_wimax_event_rcv);
+ if (wm_event.sock) {
+ INIT_LIST_HEAD(&wm_event.evtq);
+ INIT_LIST_HEAD(&wm_event.freeq);
+ INIT_WORK(&wm_event.ws, __gdm_wimax_event_send);
+ spin_lock_init(&wm_event.evt_lock);
+ }
+ }
+
+ if (wm_event.sock) {
+ wm_event.ref_cnt++;
+ return 0;
+ }
+
+ pr_err("Creating WiMax Event netlink is failed\n");
+ return -1;
+}
+
+static void gdm_wimax_event_exit(void)
+{
+ if (wm_event.sock && --wm_event.ref_cnt == 0) {
+ struct evt_entry *e, *temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wm_event.evt_lock, flags);
+
+ list_for_each_entry_safe(e, temp, &wm_event.evtq, list) {
+ list_del(&e->list);
+ free_event_entry(e);
+ }
+ list_for_each_entry_safe(e, temp, &wm_event.freeq, list) {
+ list_del(&e->list);
+ free_event_entry(e);
+ }
+
+ spin_unlock_irqrestore(&wm_event.evt_lock, flags);
+ netlink_exit(wm_event.sock);
+ wm_event.sock = NULL;
+ }
+}
+
+static inline struct evt_entry *alloc_event_entry(void)
+{
+ return kmalloc(sizeof(struct evt_entry), GFP_ATOMIC);
+}
+
+static inline void free_event_entry(struct evt_entry *e)
+{
+ kfree(e);
+}
+
+static struct evt_entry *get_event_entry(void)
+{
+ struct evt_entry *e;
+
+ if (list_empty(&wm_event.freeq)) {
+ e = alloc_event_entry();
+ } else {
+ e = list_entry(wm_event.freeq.next, struct evt_entry, list);
+ list_del(&e->list);
+ }
+
+ return e;
+}
+
+static void put_event_entry(struct evt_entry *e)
+{
+ BUG_ON(!e);
+
+ list_add_tail(&e->list, &wm_event.freeq);
+}
+
+static void __gdm_wimax_event_send(struct work_struct *work)
+{
+ int idx;
+ unsigned long flags;
+ struct evt_entry *e;
+
+ spin_lock_irqsave(&wm_event.evt_lock, flags);
+
+ while (!list_empty(&wm_event.evtq)) {
+ e = list_entry(wm_event.evtq.next, struct evt_entry, list);
+ spin_unlock_irqrestore(&wm_event.evt_lock, flags);
+
+ sscanf(e->dev->name, "wm%d", &idx);
+ netlink_send(wm_event.sock, idx, 0, e->evt_data, e->size);
+
+ spin_lock_irqsave(&wm_event.evt_lock, flags);
+ list_del(&e->list);
+ put_event_entry(e);
+ }
+
+ spin_unlock_irqrestore(&wm_event.evt_lock, flags);
+}
+
+static int gdm_wimax_event_send(struct net_device *dev, char *buf, int size)
+{
+ struct evt_entry *e;
+ unsigned long flags;
+
+ u16 hci_cmd = ((u8)buf[0]<<8) | (u8)buf[1];
+ u16 hci_len = ((u8)buf[2]<<8) | (u8)buf[3];
+
+ netdev_dbg(dev, "D=>H: 0x%04x(%d)\n", hci_cmd, hci_len);
+
+ spin_lock_irqsave(&wm_event.evt_lock, flags);
+
+ e = get_event_entry();
+ if (!e) {
+ netdev_err(dev, "%s: No memory for event\n", __func__);
+ spin_unlock_irqrestore(&wm_event.evt_lock, flags);
+ return -ENOMEM;
+ }
+
+ e->dev = dev;
+ e->size = size;
+ memcpy(e->evt_data, buf, size);
+
+ list_add_tail(&e->list, &wm_event.evtq);
+ spin_unlock_irqrestore(&wm_event.evt_lock, flags);
+
+ schedule_work(&wm_event.ws);
+
+ return 0;
+}
+
+static void tx_complete(void *arg)
+{
+ struct nic *nic = arg;
+
+ if (netif_queue_stopped(nic->netdev))
+ netif_wake_queue(nic->netdev);
+}
+
+int gdm_wimax_send_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ int ret = 0;
+ struct nic *nic = netdev_priv(dev);
+
+ ret = gdm_wimax_send_with_cb(nic, skb->data, skb->len, tx_complete,
+ nic);
+ if (ret == -ENOSPC) {
+ netif_stop_queue(dev);
+ ret = 0;
+ }
+
+ if (ret) {
+ skb_pull(skb, HCI_HEADER_SIZE);
+ return ret;
+ }
+
+ nic->stats.tx_packets++;
+ nic->stats.tx_bytes += skb->len - HCI_HEADER_SIZE;
+ kfree_skb(skb);
+ return ret;
+}
+
+static int gdm_wimax_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ int ret = 0;
+ struct nic *nic = netdev_priv(dev);
+ struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf;
+
+ dump_eth_packet(dev, "TX", skb->data, skb->len);
+
+ ret = gdm_wimax_header(&skb);
+ if (ret < 0) {
+ skb_pull(skb, HCI_HEADER_SIZE);
+ return ret;
+ }
+
+ #if !defined(LOOPBACK_TEST)
+ if (!fsm) {
+ netdev_err(dev, "ASSERTION ERROR: fsm is NULL!!\n");
+ } else if (fsm->m_status != M_CONNECTED) {
+ netdev_emerg(dev, "ASSERTION ERROR: Device is NOT ready. status=%d\n",
+ fsm->m_status);
+ kfree_skb(skb);
+ return 0;
+ }
+ #endif
+
+#if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ ret = gdm_qos_send_hci_pkt(skb, dev);
+#else
+ ret = gdm_wimax_send_tx(skb, dev);
+#endif
+ return ret;
+}
+
+static int gdm_wimax_set_config(struct net_device *dev, struct ifmap *map)
+{
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ return 0;
+}
+
+static void __gdm_wimax_set_mac_addr(struct net_device *dev, char *mac_addr)
+{
+ u16 hci_pkt_buf[32 / sizeof(u16)];
+ u8 *pkt = (u8 *)&hci_pkt_buf[0];
+ struct nic *nic = netdev_priv(dev);
+
+ /* Since dev is registered as a ethernet device,
+ * ether_setup has made dev->addr_len to be ETH_ALEN
+ */
+ memcpy(dev->dev_addr, mac_addr, dev->addr_len);
+
+ /* Let lower layer know of this change by sending
+ * SetInformation(MAC Address)
+ */
+ hci_pkt_buf[0] = H2B(WIMAX_SET_INFO); /* cmd_evt */
+ hci_pkt_buf[1] = H2B(8); /* size */
+ pkt[4] = 0; /* T */
+ pkt[5] = 6; /* L */
+ memcpy(pkt + 6, mac_addr, dev->addr_len); /* V */
+
+ gdm_wimax_send(nic, pkt, HCI_HEADER_SIZE + 8);
+}
+
+/* A driver function */
+static int gdm_wimax_set_mac_addr(struct net_device *dev, void *p)
+{
+ struct sockaddr *addr = p;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ __gdm_wimax_set_mac_addr(dev, addr->sa_data);
+
+ return 0;
+}
+
+static struct net_device_stats *gdm_wimax_stats(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+
+ return &nic->stats;
+}
+
+static int gdm_wimax_open(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf;
+
+ netif_start_queue(dev);
+
+ if (fsm && fsm->m_status != M_INIT)
+ gdm_wimax_ind_if_updown(dev, 1);
+ return 0;
+}
+
+static int gdm_wimax_close(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf;
+
+ netif_stop_queue(dev);
+
+ if (fsm && fsm->m_status != M_INIT)
+ gdm_wimax_ind_if_updown(dev, 0);
+ return 0;
+}
+
+static void kdelete(void **buf)
+{
+ if (buf && *buf) {
+ kfree(*buf);
+ *buf = NULL;
+ }
+}
+
+static int gdm_wimax_ioctl_get_data(struct data_s *dst, struct data_s *src)
+{
+ int size;
+
+ size = dst->size < src->size ? dst->size : src->size;
+
+ dst->size = size;
+ if (src->size) {
+ if (!dst->buf)
+ return -EINVAL;
+ if (copy_to_user((void __user *)dst->buf, src->buf, size))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int gdm_wimax_ioctl_set_data(struct data_s *dst, struct data_s *src)
+{
+ if (!src->size) {
+ dst->size = 0;
+ return 0;
+ }
+
+ if (!src->buf)
+ return -EINVAL;
+
+ if (!(dst->buf && dst->size == src->size)) {
+ kdelete(&dst->buf);
+ dst->buf = kmalloc(src->size, GFP_KERNEL);
+ if (dst->buf == NULL)
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(dst->buf, (void __user *)src->buf, src->size)) {
+ kdelete(&dst->buf);
+ return -EFAULT;
+ }
+ dst->size = src->size;
+ return 0;
+}
+
+static void gdm_wimax_cleanup_ioctl(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ int i;
+
+ for (i = 0; i < SIOC_DATA_MAX; i++)
+ kdelete(&nic->sdk_data[i].buf);
+}
+
+static void gdm_update_fsm(struct net_device *dev, struct fsm_s *new_fsm)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct fsm_s *cur_fsm = (struct fsm_s *)
+ nic->sdk_data[SIOC_DATA_FSM].buf;
+
+ if (!cur_fsm)
+ return;
+
+ if (cur_fsm->m_status != new_fsm->m_status ||
+ cur_fsm->c_status != new_fsm->c_status) {
+ if (new_fsm->m_status == M_CONNECTED) {
+ netif_carrier_on(dev);
+ } else if (cur_fsm->m_status == M_CONNECTED) {
+ netif_carrier_off(dev);
+ #if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ gdm_qos_release_list(nic);
+ #endif
+ }
+ gdm_wimax_ind_fsm_update(dev, new_fsm);
+ }
+}
+
+static int gdm_wimax_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct wm_req_s *req = (struct wm_req_s *)ifr;
+ struct nic *nic = netdev_priv(dev);
+ int ret;
+
+ if (cmd != SIOCWMIOCTL)
+ return -EOPNOTSUPP;
+
+ switch (req->cmd) {
+ case SIOCG_DATA:
+ case SIOCS_DATA:
+ if (req->data_id >= SIOC_DATA_MAX) {
+ netdev_err(dev, "%s error: data-index(%d) is invalid!!\n",
+ __func__, req->data_id);
+ return -EOPNOTSUPP;
+ }
+ if (req->cmd == SIOCG_DATA) {
+ ret = gdm_wimax_ioctl_get_data(
+ &req->data, &nic->sdk_data[req->data_id]);
+ if (ret < 0)
+ return ret;
+ } else if (req->cmd == SIOCS_DATA) {
+ if (req->data_id == SIOC_DATA_FSM) {
+ /*NOTE: gdm_update_fsm should be called
+ before gdm_wimax_ioctl_set_data is called*/
+ gdm_update_fsm(dev,
+ (struct fsm_s *)req->data.buf);
+ }
+ ret = gdm_wimax_ioctl_set_data(
+ &nic->sdk_data[req->data_id], &req->data);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+ default:
+ netdev_err(dev, "%s: %x unknown ioctl\n", __func__, cmd);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static void gdm_wimax_prepare_device(struct net_device *dev)
+{
+ struct nic *nic = netdev_priv(dev);
+ u16 buf[32 / sizeof(u16)];
+ struct hci_s *hci = (struct hci_s *)buf;
+ u16 len = 0;
+ u32 val = 0;
+
+ #define BIT_MULTI_CS 0
+ #define BIT_WIMAX 1
+ #define BIT_QOS 2
+ #define BIT_AGGREGATION 3
+
+ /* GetInformation mac address */
+ len = 0;
+ hci->cmd_evt = H2B(WIMAX_GET_INFO);
+ hci->data[len++] = TLV_T(T_MAC_ADDRESS);
+ hci->length = H2B(len);
+ gdm_wimax_send(nic, hci, HCI_HEADER_SIZE+len);
+
+ val = (1<<BIT_WIMAX) | (1<<BIT_MULTI_CS);
+ #if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ val |= (1<<BIT_QOS);
+ #endif
+ #if defined(CONFIG_WIMAX_GDM72XX_WIMAX2)
+ val |= (1<<BIT_AGGREGATION);
+ #endif
+
+ /* Set capability */
+ len = 0;
+ hci->cmd_evt = H2B(WIMAX_SET_INFO);
+ hci->data[len++] = TLV_T(T_CAPABILITY);
+ hci->data[len++] = TLV_L(T_CAPABILITY);
+ val = DH2B(val);
+ memcpy(&hci->data[len], &val, TLV_L(T_CAPABILITY));
+ len += TLV_L(T_CAPABILITY);
+ hci->length = H2B(len);
+ gdm_wimax_send(nic, hci, HCI_HEADER_SIZE+len);
+
+ netdev_info(dev, "GDM WiMax Set CAPABILITY: 0x%08X\n", DB2H(val));
+}
+
+static int gdm_wimax_hci_get_tlv(u8 *buf, u8 *T, u16 *L, u8 **V)
+{
+ #define __U82U16(b) ((u16)((u8 *)(b))[0] | ((u16)((u8 *)(b))[1] << 8))
+ int next_pos;
+
+ *T = buf[0];
+ if (buf[1] == 0x82) {
+ *L = B2H(__U82U16(&buf[2]));
+ next_pos = 1/*type*/+3/*len*/;
+ } else {
+ *L = buf[1];
+ next_pos = 1/*type*/+1/*len*/;
+ }
+ *V = &buf[next_pos];
+
+ next_pos += *L/*length of val*/;
+ return next_pos;
+}
+
+static int gdm_wimax_get_prepared_info(struct net_device *dev, char *buf,
+ int len)
+{
+ u8 T, *V;
+ u16 L;
+ u16 cmd_evt, cmd_len;
+ int pos = HCI_HEADER_SIZE;
+
+ cmd_evt = B2H(*(u16 *)&buf[0]);
+ cmd_len = B2H(*(u16 *)&buf[2]);
+
+ if (len < cmd_len + HCI_HEADER_SIZE) {
+ netdev_err(dev, "%s: invalid length [%d/%d]\n", __func__,
+ cmd_len + HCI_HEADER_SIZE, len);
+ return -1;
+ }
+
+ if (cmd_evt == WIMAX_GET_INFO_RESULT) {
+ if (cmd_len < 2) {
+ netdev_err(dev, "%s: len is too short [%x/%d]\n",
+ __func__, cmd_evt, len);
+ return -1;
+ }
+
+ pos += gdm_wimax_hci_get_tlv(&buf[pos], &T, &L, &V);
+ if (T == TLV_T(T_MAC_ADDRESS)) {
+ if (L != dev->addr_len) {
+ netdev_err(dev,
+ "%s Invalid inofrmation result T/L [%x/%d]\n",
+ __func__, T, L);
+ return -1;
+ }
+ netdev_info(dev, "MAC change [%pM]->[%pM]\n",
+ dev->dev_addr, V);
+ memcpy(dev->dev_addr, V, dev->addr_len);
+ return 1;
+ }
+ }
+
+ gdm_wimax_event_send(dev, buf, len);
+ return 0;
+}
+
+static void gdm_wimax_netif_rx(struct net_device *dev, char *buf, int len)
+{
+ struct nic *nic = netdev_priv(dev);
+ struct sk_buff *skb;
+ int ret;
+
+ dump_eth_packet(dev, "RX", buf, len);
+
+ skb = dev_alloc_skb(len + 2);
+ if (!skb) {
+ netdev_err(dev, "%s: dev_alloc_skb failed!\n", __func__);
+ return;
+ }
+ skb_reserve(skb, 2);
+
+ nic->stats.rx_packets++;
+ nic->stats.rx_bytes += len;
+
+ memcpy(skb_put(skb, len), buf, len);
+
+ skb->dev = dev;
+ skb->protocol = eth_type_trans(skb, dev); /* what will happen? */
+
+ ret = in_interrupt() ? netif_rx(skb) : netif_rx_ni(skb);
+ if (ret == NET_RX_DROP)
+ netdev_err(dev, "%s skb dropped\n", __func__);
+}
+
+static void gdm_wimax_transmit_aggr_pkt(struct net_device *dev, char *buf,
+ int len)
+{
+ #define HCI_PADDING_BYTE 4
+ #define HCI_RESERVED_BYTE 4
+ struct hci_s *hci;
+ int length;
+
+ while (len > 0) {
+ hci = (struct hci_s *)buf;
+
+ if (B2H(hci->cmd_evt) != WIMAX_RX_SDU) {
+ netdev_err(dev, "Wrong cmd_evt(0x%04X)\n",
+ B2H(hci->cmd_evt));
+ break;
+ }
+
+ length = B2H(hci->length);
+ gdm_wimax_netif_rx(dev, hci->data, length);
+
+ if (length & 0x3) {
+ /* Add padding size */
+ length += HCI_PADDING_BYTE - (length & 0x3);
+ }
+
+ length += HCI_HEADER_SIZE + HCI_RESERVED_BYTE;
+ len -= length;
+ buf += length;
+ }
+}
+
+static void gdm_wimax_transmit_pkt(struct net_device *dev, char *buf, int len)
+{
+ #if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ struct nic *nic = netdev_priv(dev);
+ #endif
+ u16 cmd_evt, cmd_len;
+
+ /* This code is added for certain rx packet to be ignored. */
+ if (len == 0)
+ return;
+
+ cmd_evt = B2H(*(u16 *)&buf[0]);
+ cmd_len = B2H(*(u16 *)&buf[2]);
+
+ if (len < cmd_len + HCI_HEADER_SIZE) {
+ if (len)
+ netdev_err(dev, "%s: invalid length [%d/%d]\n",
+ __func__, cmd_len + HCI_HEADER_SIZE, len);
+ return;
+ }
+
+ switch (cmd_evt) {
+ case WIMAX_RX_SDU_AGGR:
+ gdm_wimax_transmit_aggr_pkt(dev, &buf[HCI_HEADER_SIZE],
+ cmd_len);
+ break;
+ case WIMAX_RX_SDU:
+ gdm_wimax_netif_rx(dev, &buf[HCI_HEADER_SIZE], cmd_len);
+ break;
+ #if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ case WIMAX_EVT_MODEM_REPORT:
+ gdm_recv_qos_hci_packet(nic, buf, len);
+ break;
+ #endif
+ case WIMAX_SDU_TX_FLOW:
+ if (buf[4] == 0) {
+ if (!netif_queue_stopped(dev))
+ netif_stop_queue(dev);
+ } else if (buf[4] == 1) {
+ if (netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+ }
+ break;
+ default:
+ gdm_wimax_event_send(dev, buf, len);
+ break;
+ }
+}
+
+static void gdm_wimax_ind_fsm_update(struct net_device *dev, struct fsm_s *fsm)
+{
+ u16 buf[32 / sizeof(u16)];
+ u8 *hci_pkt_buf = (u8 *)&buf[0];
+
+ /* Indicate updating fsm */
+ buf[0] = H2B(WIMAX_FSM_UPDATE);
+ buf[1] = H2B(sizeof(struct fsm_s));
+ memcpy(&hci_pkt_buf[HCI_HEADER_SIZE], fsm, sizeof(struct fsm_s));
+
+ gdm_wimax_event_send(dev, hci_pkt_buf,
+ HCI_HEADER_SIZE + sizeof(struct fsm_s));
+}
+
+static void gdm_wimax_ind_if_updown(struct net_device *dev, int if_up)
+{
+ u16 buf[32 / sizeof(u16)];
+ struct hci_s *hci = (struct hci_s *)buf;
+ unsigned char up_down;
+
+ up_down = if_up ? WIMAX_IF_UP : WIMAX_IF_DOWN;
+
+ /* Indicate updating fsm */
+ hci->cmd_evt = H2B(WIMAX_IF_UPDOWN);
+ hci->length = H2B(sizeof(up_down));
+ hci->data[0] = up_down;
+
+ gdm_wimax_event_send(dev, (char *)hci, HCI_HEADER_SIZE+sizeof(up_down));
+}
+
+static void rx_complete(void *arg, void *data, int len)
+{
+ struct nic *nic = arg;
+
+ gdm_wimax_transmit_pkt(nic->netdev, data, len);
+ gdm_wimax_rcv_with_cb(nic, rx_complete, nic);
+}
+
+static void prepare_rx_complete(void *arg, void *data, int len)
+{
+ struct nic *nic = arg;
+ int ret;
+
+ ret = gdm_wimax_get_prepared_info(nic->netdev, data, len);
+ if (ret == 1) {
+ gdm_wimax_rcv_with_cb(nic, rx_complete, nic);
+ } else {
+ if (ret < 0)
+ netdev_err(nic->netdev,
+ "get_prepared_info failed(%d)\n", ret);
+ gdm_wimax_rcv_with_cb(nic, prepare_rx_complete, nic);
+ }
+}
+
+static void start_rx_proc(struct nic *nic)
+{
+ gdm_wimax_rcv_with_cb(nic, prepare_rx_complete, nic);
+}
+
+static struct net_device_ops gdm_netdev_ops = {
+ .ndo_open = gdm_wimax_open,
+ .ndo_stop = gdm_wimax_close,
+ .ndo_set_config = gdm_wimax_set_config,
+ .ndo_start_xmit = gdm_wimax_tx,
+ .ndo_get_stats = gdm_wimax_stats,
+ .ndo_set_mac_address = gdm_wimax_set_mac_addr,
+ .ndo_do_ioctl = gdm_wimax_ioctl,
+};
+
+int register_wimax_device(struct phy_dev *phy_dev, struct device *pdev)
+{
+ struct nic *nic = NULL;
+ struct net_device *dev;
+ int ret;
+
+ dev = alloc_netdev(sizeof(*nic), "wm%d", ether_setup);
+
+ if (dev == NULL) {
+ pr_err("alloc_etherdev failed\n");
+ return -ENOMEM;
+ }
+
+ SET_NETDEV_DEV(dev, pdev);
+ dev->mtu = 1400;
+ dev->netdev_ops = &gdm_netdev_ops;
+ dev->flags &= ~IFF_MULTICAST;
+ memcpy(dev->dev_addr, gdm_wimax_macaddr, sizeof(gdm_wimax_macaddr));
+
+ nic = netdev_priv(dev);
+ memset(nic, 0, sizeof(*nic));
+
+ nic->netdev = dev;
+ nic->phy_dev = phy_dev;
+ phy_dev->netdev = dev;
+
+ /* event socket init */
+ ret = gdm_wimax_event_init();
+ if (ret < 0) {
+ pr_err("Cannot create event.\n");
+ goto cleanup;
+ }
+
+ ret = register_netdev(dev);
+ if (ret)
+ goto cleanup;
+
+ #if defined(LOOPBACK_TEST)
+ netif_start_queue(dev);
+ netif_carrier_on(dev);
+ #else
+ netif_carrier_off(dev);
+ #endif
+
+#ifdef CONFIG_WIMAX_GDM72XX_QOS
+ gdm_qos_init(nic);
+#endif
+
+ start_rx_proc(nic);
+
+ /* Prepare WiMax device */
+ gdm_wimax_prepare_device(dev);
+
+ return 0;
+
+cleanup:
+ pr_err("register_netdev failed\n");
+ free_netdev(dev);
+ return ret;
+}
+
+void unregister_wimax_device(struct phy_dev *phy_dev)
+{
+ struct nic *nic = netdev_priv(phy_dev->netdev);
+ struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf;
+
+ if (fsm)
+ fsm->m_status = M_INIT;
+ unregister_netdev(nic->netdev);
+
+ gdm_wimax_event_exit();
+
+#if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ gdm_qos_release_list(nic);
+#endif
+
+ gdm_wimax_cleanup_ioctl(phy_dev->netdev);
+
+ free_netdev(nic->netdev);
+}
diff --git a/drivers/staging/gdm72xx/gdm_wimax.h b/drivers/staging/gdm72xx/gdm_wimax.h
new file mode 100644
index 00000000000..7e2c88877ed
--- /dev/null
+++ b/drivers/staging/gdm72xx/gdm_wimax.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __GDM_WIMAX_H__
+#define __GDM_WIMAX_H__
+
+#include <linux/netdevice.h>
+#include <linux/types.h>
+#include "wm_ioctl.h"
+#if defined(CONFIG_WIMAX_GDM72XX_QOS)
+#include "gdm_qos.h"
+#endif
+
+#define DRIVER_VERSION "3.2.3"
+
+#define H2L(x) __cpu_to_le16(x)
+#define L2H(x) __le16_to_cpu(x)
+#define DH2L(x) __cpu_to_le32(x)
+#define DL2H(x) __le32_to_cpu(x)
+
+#define H2B(x) __cpu_to_be16(x)
+#define B2H(x) __be16_to_cpu(x)
+#define DH2B(x) __cpu_to_be32(x)
+#define DB2H(x) __be32_to_cpu(x)
+
+struct phy_dev {
+ void *priv_dev;
+ struct net_device *netdev;
+ int (*send_func)(void *priv_dev, void *data, int len,
+ void (*cb)(void *cb_data), void *cb_data);
+ int (*rcv_func)(void *priv_dev,
+ void (*cb)(void *cb_data, void *data, int len),
+ void *cb_data);
+};
+
+struct nic {
+ struct net_device *netdev;
+ struct phy_dev *phy_dev;
+ struct net_device_stats stats;
+ struct data_s sdk_data[SIOC_DATA_MAX];
+#if defined(CONFIG_WIMAX_GDM72XX_QOS)
+ struct qos_cb_s qos;
+#endif
+};
+
+int register_wimax_device(struct phy_dev *phy_dev, struct device *pdev);
+int gdm_wimax_send_tx(struct sk_buff *skb, struct net_device *dev);
+void unregister_wimax_device(struct phy_dev *phy_dev);
+
+#endif
diff --git a/drivers/staging/gdm72xx/hci.h b/drivers/staging/gdm72xx/hci.h
new file mode 100644
index 00000000000..2485a379912
--- /dev/null
+++ b/drivers/staging/gdm72xx/hci.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 HCI_H_20080801
+#define HCI_H_20080801
+
+#define HCI_HEADER_SIZE 4
+#define HCI_VALUE_OFFS (HCI_HEADER_SIZE)
+#define HCI_MAX_PACKET 2048
+#define HCI_MAX_PARAM (HCI_MAX_PACKET-HCI_HEADER_SIZE)
+#define HCI_MAX_TLV 32
+
+/* CMD-EVT */
+
+/* Category 0 */
+#define WIMAX_RESET 0x0000
+#define WIMAX_SET_INFO 0x0001
+#define WIMAX_GET_INFO 0x0002
+#define WIMAX_GET_INFO_RESULT 0x8003
+#define WIMAX_RADIO_OFF 0x0004
+#define WIMAX_RADIO_ON 0x0006
+#define WIMAX_WIMAX_RESET 0x0007 /* Is this still here */
+
+/* Category 1 */
+#define WIMAX_NET_ENTRY 0x0100
+#define WIMAX_NET_DISCONN 0x0102
+#define WIMAX_ENTER_SLEEP 0x0103
+#define WIMAX_EXIT_SLEEP 0x0104
+#define WIMAX_ENTER_IDLE 0x0105
+#define WIMAX_EXIT_IDLE 0x0106
+#define WIMAX_MODE_CHANGE 0x8108
+#define WIMAX_HANDOVER 0x8109 /* obsolete */
+#define WIMAX_SCAN 0x010d
+#define WIMAX_SCAN_COMPLETE 0x810e
+#define WIMAX_SCAN_RESULT 0x810f
+#define WIMAX_CONNECT 0x0110
+#define WIMAX_CONNECT_START 0x8111
+#define WIMAX_CONNECT_COMPLETE 0x8112
+#define WIMAX_ASSOC_START 0x8113
+#define WIMAX_ASSOC_COMPLETE 0x8114
+#define WIMAX_DISCONN_IND 0x8115
+#define WIMAX_ENTRY_IND 0x8116
+#define WIMAX_HO_START 0x8117
+#define WIMAX_HO_COMPLETE 0x8118
+#define WIMAX_RADIO_STATE_IND 0x8119
+#define WIMAX_IP_RENEW_IND 0x811a
+#define WIMAX_DISCOVER_NSP 0x011d
+#define WIMAX_DISCOVER_NSP_RESULT 0x811e
+#define WIMAX_SDU_TX_FLOW 0x8125
+
+/* Category 2 */
+#define WIMAX_TX_EAP 0x0200
+#define WIMAX_RX_EAP 0x8201
+#define WIMAX_TX_SDU 0x0202
+#define WIMAX_RX_SDU 0x8203
+#define WIMAX_RX_SDU_AGGR 0x8204
+#define WIMAX_TX_SDU_AGGR 0x0205
+
+/* Category 3 */
+#define WIMAX_DM_CMD 0x030a
+#define WIMAX_DM_RSP 0x830b
+
+#define WIMAX_CLI_CMD 0x030c
+#define WIMAX_CLI_RSP 0x830d
+
+#define WIMAX_DL_IMAGE 0x0310
+#define WIMAX_DL_IMAGE_STATUS 0x8311
+#define WIMAX_UL_IMAGE 0x0312
+#define WIMAX_UL_IMAGE_RESULT 0x8313
+#define WIMAX_UL_IMAGE_STATUS 0x0314
+#define WIMAX_EVT_MODEM_REPORT 0x8325
+
+/* Category 0xF */
+#define WIMAX_FSM_UPDATE 0x8F01
+#define WIMAX_IF_UPDOWN 0x8F02
+#define WIMAX_IF_UP 1
+#define WIMAX_IF_DOWN 2
+
+/* WIMAX mode */
+#define W_NULL 0
+#define W_STANDBY 1
+#define W_OOZ 2
+#define W_AWAKE 3
+#define W_IDLE 4
+#define W_SLEEP 5
+#define W_WAIT 6
+
+#define W_NET_ENTRY_RNG 0x80
+#define W_NET_ENTRY_SBC 0x81
+#define W_NET_ENTRY_PKM 0x82
+#define W_NET_ENTRY_REG 0x83
+#define W_NET_ENTRY_DSX 0x84
+
+#define W_NET_ENTRY_RNG_FAIL 0x1100100
+#define W_NET_ENTRY_SBC_FAIL 0x1100200
+#define W_NET_ENTRY_PKM_FAIL 0x1102000
+#define W_NET_ENTRY_REG_FAIL 0x1103000
+#define W_NET_ENTRY_DSX_FAIL 0x1104000
+
+/* Scan Type */
+#define W_SCAN_ALL_CHANNEL 0
+#define W_SCAN_ALL_SUBSCRIPTION 1
+#define W_SCAN_SPECIFIED_SUBSCRIPTION 2
+
+/*
+ * TLV
+ *
+ * [31:31] indicates the type is composite.
+ * [30:16] is the length of the type. 0 length means length is variable.
+ * [15:0] is the actual type.
+ *
+ */
+#define TLV_L(x) (((x) >> 16) & 0xff)
+#define TLV_T(x) ((x) & 0xff)
+#define TLV_COMPOSITE(x) ((x) >> 31)
+
+/* GENERAL */
+#define T_MAC_ADDRESS (0x00 | (6 << 16))
+#define T_BSID (0x01 | (6 << 16))
+#define T_MSK (0x02 | (64 << 16))
+#define T_RSSI_THRSHLD (0x03 | (1 << 16))
+#define T_FREQUENCY (0x04 | (4 << 16))
+#define T_CONN_CS_TYPE (0x05 | (1 << 16))
+#define T_HOST_IP_VER (0x06 | (1 << 16))
+#define T_STBY_SCAN_INTERVAL (0x07 | (4 << 16))
+#define T_OOZ_SCAN_INTERVAL (0x08 | (4 << 16))
+#define T_IMEI (0x09 | (8 << 16))
+#define T_PID (0x0a | (12 << 16))
+#define T_CAPABILITY (0x1a | (4 << 16))
+#define T_RELEASE_NUMBER (0x1b | (4 << 16))
+#define T_DRIVER_REVISION (0x1c | (4 << 16))
+#define T_FW_REVISION (0x1d | (4 << 16))
+#define T_MAC_HW_REVISION (0x1e | (4 << 16))
+#define T_PHY_HW_REVISION (0x1f | (4 << 16))
+
+/* HANDOVER */
+#define T_SCAN_INTERVAL (0x20 | (1 << 16))
+#define T_RSC_RETAIN_TIME (0x2f | (2 << 16))
+
+/* SLEEP */
+#define T_TYPE1_ISW (0x40 | (1 << 16))
+#define T_SLP_START_TO (0x4a | (2 << 16))
+
+/* IDLE */
+#define T_IDLE_MODE_TO (0x50 | (2 << 16))
+#define T_IDLE_START_TO (0x54 | (2 << 16))
+
+/* MONITOR */
+#define T_RSSI (0x60 | (1 << 16))
+#define T_CINR (0x61 | (1 << 16))
+#define T_TX_POWER (0x6a | (1 << 16))
+#define T_CUR_FREQ (0x7f | (4 << 16))
+
+
+/* WIMAX */
+#define T_MAX_SUBSCRIPTION (0xa1 | (1 << 16))
+#define T_MAX_SF (0xa2 | (1 << 16))
+#define T_PHY_TYPE (0xa3 | (1 << 16))
+#define T_PKM (0xa4 | (1 << 16))
+#define T_AUTH_POLICY (0xa5 | (1 << 16))
+#define T_CS_TYPE (0xa6 | (2 << 16))
+#define T_VENDOR_NAME (0xa7 | (0 << 16))
+#define T_MOD_NAME (0xa8 | (0 << 16))
+#define T_PACKET_FILTER (0xa9 | (1 << 16))
+#define T_NSP_CHANGE_COUNT (0xaa | (4 << 16))
+#define T_RADIO_STATE (0xab | (1 << 16))
+#define T_URI_CONTACT_TYPE (0xac | (1 << 16))
+#define T_URI_TEXT (0xad | (0 << 16))
+#define T_URI (0xae | (0 << 16))
+#define T_ENABLE_AUTH (0xaf | (1 << 16))
+#define T_TIMEOUT (0xb0 | (2 << 16))
+#define T_RUN_MODE (0xb1 | (1 << 16))
+#define T_OMADMT_VER (0xb2 | (4 << 16))
+/* This is measured in seconds from 00:00:00 GMT January 1, 1970. */
+#define T_RTC_TIME (0xb3 | (4 << 16))
+#define T_CERT_STATUS (0xb4 | (4 << 16))
+#define T_CERT_MASK (0xb5 | (4 << 16))
+#define T_EMSK (0xb6 | (64 << 16))
+
+/* Subscription TLV */
+#define T_SUBSCRIPTION_LIST (0xd1 | (0 << 16) | (1 << 31))
+#define T_H_NSPID (0xd2 | (3 << 16))
+#define T_NSP_NAME (0xd3 | (0 << 16))
+#define T_SUBSCRIPTION_NAME (0xd4 | (0 << 16))
+#define T_SUBSCRIPTION_FLAG (0xd5 | (2 << 16))
+#define T_V_NSPID (0xd6 | (3 << 16))
+#define T_NAP_ID (0xd7 | (3 << 16))
+#define T_PREAMBLES (0xd8 | (15 << 16))
+#define T_BW (0xd9 | (4 << 16))
+#define T_FFTSIZE (0xda | (4 << 16))
+#define T_DUPLEX_MODE (0xdb | (4 << 16))
+
+struct hci_s {
+ unsigned short cmd_evt;
+ unsigned short length;
+ unsigned char data[0];
+} __packed;
+
+#endif
diff --git a/drivers/staging/gdm72xx/netlink_k.c b/drivers/staging/gdm72xx/netlink_k.c
new file mode 100644
index 00000000000..9bf00e683f1
--- /dev/null
+++ b/drivers/staging/gdm72xx/netlink_k.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <net/netlink.h>
+#include <asm/byteorder.h>
+#include <net/sock.h>
+#include "netlink_k.h"
+
+#if !defined(NLMSG_HDRLEN)
+#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
+#endif
+
+#define ND_MAX_GROUP 30
+#define ND_IFINDEX_LEN sizeof(int)
+#define ND_NLMSG_SPACE(len) (nlmsg_total_size(len) + ND_IFINDEX_LEN)
+#define ND_NLMSG_DATA(nlh) \
+ ((void *)((char *)nlmsg_data(nlh) + ND_IFINDEX_LEN))
+#define ND_NLMSG_S_LEN(len) (len+ND_IFINDEX_LEN)
+#define ND_NLMSG_R_LEN(nlh) (nlh->nlmsg_len-ND_IFINDEX_LEN)
+#define ND_NLMSG_IFIDX(nlh) nlmsg_data(nlh)
+#define ND_MAX_MSG_LEN 8096
+
+#if defined(DEFINE_MUTEX)
+static DEFINE_MUTEX(netlink_mutex);
+#else
+static struct semaphore netlink_mutex;
+#define mutex_lock(x) down(x)
+#define mutex_unlock(x) up(x)
+#endif
+
+static void (*rcv_cb)(struct net_device *dev, u16 type, void *msg, int len);
+
+static void netlink_rcv_cb(struct sk_buff *skb)
+{
+ struct nlmsghdr *nlh;
+ struct net_device *dev;
+ u32 mlen;
+ void *msg;
+ int ifindex;
+
+ if (skb->len >= NLMSG_HDRLEN) {
+ nlh = (struct nlmsghdr *)skb->data;
+
+ if (skb->len < nlh->nlmsg_len ||
+ nlh->nlmsg_len > ND_MAX_MSG_LEN) {
+ netdev_err(skb->dev, "Invalid length (%d,%d)\n",
+ skb->len, nlh->nlmsg_len);
+ return;
+ }
+
+ memcpy(&ifindex, ND_NLMSG_IFIDX(nlh), ND_IFINDEX_LEN);
+ msg = ND_NLMSG_DATA(nlh);
+ mlen = ND_NLMSG_R_LEN(nlh);
+
+ if (rcv_cb) {
+ dev = dev_get_by_index(&init_net, ifindex);
+ if (dev) {
+ rcv_cb(dev, nlh->nlmsg_type, msg, mlen);
+ dev_put(dev);
+ } else
+ netdev_err(skb->dev,
+ "dev_get_by_index(%d) is not found.\n",
+ ifindex);
+ } else {
+ netdev_err(skb->dev, "Unregistered Callback\n");
+ }
+ }
+}
+
+static void netlink_rcv(struct sk_buff *skb)
+{
+ mutex_lock(&netlink_mutex);
+ netlink_rcv_cb(skb);
+ mutex_unlock(&netlink_mutex);
+}
+
+struct sock *netlink_init(int unit, void (*cb)(struct net_device *dev, u16 type,
+ void *msg, int len))
+{
+ struct sock *sock;
+ struct netlink_kernel_cfg cfg = {
+ .input = netlink_rcv,
+ };
+
+#if !defined(DEFINE_MUTEX)
+ init_MUTEX(&netlink_mutex);
+#endif
+
+ sock = netlink_kernel_create(&init_net, unit, &cfg);
+
+ if (sock)
+ rcv_cb = cb;
+
+ return sock;
+}
+
+void netlink_exit(struct sock *sock)
+{
+ netlink_kernel_release(sock);
+}
+
+int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len)
+{
+ static u32 seq;
+ struct sk_buff *skb = NULL;
+ struct nlmsghdr *nlh;
+ int ret = 0;
+
+ if (group > ND_MAX_GROUP) {
+ pr_err("Group %d is invalied.\n", group);
+ pr_err("Valid group is 0 ~ %d.\n", ND_MAX_GROUP);
+ return -EINVAL;
+ }
+
+ skb = nlmsg_new(len, GFP_ATOMIC);
+ if (!skb) {
+ pr_err("netlink_broadcast ret=%d\n", ret);
+ return -ENOMEM;
+ }
+
+ seq++;
+ nlh = nlmsg_put(skb, 0, seq, type, len, 0);
+ if (!nlh) {
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+ memcpy(nlmsg_data(nlh), msg, len);
+
+ NETLINK_CB(skb).portid = 0;
+ NETLINK_CB(skb).dst_group = 0;
+
+ ret = netlink_broadcast(sock, skb, 0, group+1, GFP_ATOMIC);
+
+ if (!ret) {
+ return len;
+ } else {
+ if (ret != -ESRCH) {
+ pr_err("netlink_broadcast g=%d, t=%d, l=%d, r=%d\n",
+ group, type, len, ret);
+ }
+ ret = 0;
+ }
+ return ret;
+}
diff --git a/drivers/staging/gdm72xx/netlink_k.h b/drivers/staging/gdm72xx/netlink_k.h
new file mode 100644
index 00000000000..b6caac16b3d
--- /dev/null
+++ b/drivers/staging/gdm72xx/netlink_k.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#if !defined(NETLINK_H_20081202)
+#define NETLINK_H_20081202
+#include <linux/netdevice.h>
+#include <net/sock.h>
+
+struct sock *netlink_init(int unit, void (*cb)(struct net_device *dev, u16 type,
+ void *msg, int len));
+void netlink_exit(struct sock *sock);
+int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len);
+
+#endif
diff --git a/drivers/staging/gdm72xx/sdio_boot.c b/drivers/staging/gdm72xx/sdio_boot.c
new file mode 100644
index 00000000000..2c02842ac5d
--- /dev/null
+++ b/drivers/staging/gdm72xx/sdio_boot.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+
+#include <linux/firmware.h>
+
+#include "gdm_sdio.h"
+#include "sdio_boot.h"
+
+#define TYPE_A_HEADER_SIZE 4
+#define TYPE_A_LOOKAHEAD_SIZE 16
+#define YMEM0_SIZE 0x8000 /* 32kbytes */
+#define DOWNLOAD_SIZE (YMEM0_SIZE - TYPE_A_HEADER_SIZE)
+
+#define FW_DIR "gdm72xx/"
+#define FW_KRN "gdmskrn.bin"
+#define FW_RFS "gdmsrfs.bin"
+
+static u8 *tx_buf;
+
+static int ack_ready(struct sdio_func *func)
+{
+ unsigned long wait = jiffies + HZ;
+ u8 val;
+ int ret;
+
+ while (time_before(jiffies, wait)) {
+ val = sdio_readb(func, 0x13, &ret);
+ if (val & 0x01)
+ return 1;
+ schedule();
+ }
+
+ return 0;
+}
+
+static int download_image(struct sdio_func *func, const char *img_name)
+{
+ int ret = 0, len, pno;
+ u8 *buf = tx_buf;
+ loff_t pos = 0;
+ int img_len;
+ const struct firmware *firm;
+
+ ret = request_firmware(&firm, img_name, &func->dev);
+ if (ret < 0) {
+ dev_err(&func->dev,
+ "requesting firmware %s failed with error %d\n",
+ img_name, ret);
+ return ret;
+ }
+
+ buf = kmalloc(DOWNLOAD_SIZE + TYPE_A_HEADER_SIZE, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ img_len = firm->size;
+
+ if (img_len <= 0) {
+ ret = -1;
+ goto out;
+ }
+
+ pno = 0;
+ while (img_len > 0) {
+ if (img_len > DOWNLOAD_SIZE) {
+ len = DOWNLOAD_SIZE;
+ buf[3] = 0;
+ } else {
+ len = img_len; /* the last packet */
+ buf[3] = 2;
+ }
+
+ buf[0] = len & 0xff;
+ buf[1] = (len >> 8) & 0xff;
+ buf[2] = (len >> 16) & 0xff;
+
+ memcpy(buf+TYPE_A_HEADER_SIZE, firm->data + pos, len);
+ ret = sdio_memcpy_toio(func, 0, buf, len + TYPE_A_HEADER_SIZE);
+ if (ret < 0) {
+ dev_err(&func->dev,
+ "send image error: packet number = %d ret = %d\n",
+ pno, ret);
+ goto out;
+ }
+
+ if (buf[3] == 2) /* The last packet */
+ break;
+ if (!ack_ready(func)) {
+ ret = -EIO;
+ dev_err(&func->dev, "Ack is not ready.\n");
+ goto out;
+ }
+ ret = sdio_memcpy_fromio(func, buf, 0, TYPE_A_LOOKAHEAD_SIZE);
+ if (ret < 0) {
+ dev_err(&func->dev,
+ "receive ack error: packet number = %d ret = %d\n",
+ pno, ret);
+ goto out;
+ }
+ sdio_writeb(func, 0x01, 0x13, &ret);
+ sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */
+
+ img_len -= DOWNLOAD_SIZE;
+ pos += DOWNLOAD_SIZE;
+ pno++;
+ }
+
+out:
+ kfree(buf);
+ return ret;
+}
+
+int sdio_boot(struct sdio_func *func)
+{
+ int ret;
+ const char *krn_name = FW_DIR FW_KRN;
+ const char *rfs_name = FW_DIR FW_RFS;
+
+ tx_buf = kmalloc(YMEM0_SIZE, GFP_KERNEL);
+ if (tx_buf == NULL)
+ return -ENOMEM;
+
+ ret = download_image(func, krn_name);
+ if (ret)
+ goto restore_fs;
+ dev_info(&func->dev, "GCT: Kernel download success.\n");
+
+ ret = download_image(func, rfs_name);
+ if (ret)
+ goto restore_fs;
+ dev_info(&func->dev, "GCT: Filesystem download success.\n");
+
+restore_fs:
+ kfree(tx_buf);
+ return ret;
+}
diff --git a/drivers/staging/gdm72xx/sdio_boot.h b/drivers/staging/gdm72xx/sdio_boot.h
new file mode 100644
index 00000000000..045c1f45053
--- /dev/null
+++ b/drivers/staging/gdm72xx/sdio_boot.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __SDIO_BOOT_H__
+#define __SDIO_BOOT_H__
+
+struct sdio_func;
+
+int sdio_boot(struct sdio_func *func);
+
+#endif /* __SDIO_BOOT_H__ */
diff --git a/drivers/staging/gdm72xx/usb_boot.c b/drivers/staging/gdm72xx/usb_boot.c
new file mode 100644
index 00000000000..d59bac872ff
--- /dev/null
+++ b/drivers/staging/gdm72xx/usb_boot.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+
+#include <asm/byteorder.h>
+#include "gdm_usb.h"
+#include "usb_boot.h"
+
+#define DN_KERNEL_MAGIC_NUMBER 0x10760001
+#define DN_ROOTFS_MAGIC_NUMBER 0x10760002
+
+#define DOWNLOAD_SIZE 1024
+
+#define MAX_IMG_CNT 16
+#define FW_DIR "gdm72xx/"
+#define FW_UIMG "gdmuimg.bin"
+#define FW_KERN "zImage"
+#define FW_FS "ramdisk.jffs2"
+
+struct dn_header {
+ u32 magic_num;
+ u32 file_size;
+};
+
+struct img_header {
+ u32 magic_code;
+ u32 count;
+ u32 len;
+ u32 offset[MAX_IMG_CNT];
+ char hostname[32];
+ char date[32];
+};
+
+struct fw_info {
+ u32 id;
+ u32 len;
+ u32 kernel_len;
+ u32 rootfs_len;
+ u32 kernel_offset;
+ u32 rootfs_offset;
+ u32 fw_ver;
+ u32 mac_ver;
+ char hostname[32];
+ char userid[16];
+ char date[32];
+ char user_desc[128];
+};
+
+static void array_le32_to_cpu(u32 *arr, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++, arr++)
+ *arr = __le32_to_cpu(*arr);
+}
+
+static u8 *tx_buf;
+
+static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len)
+{
+ int ret;
+ int actual;
+
+ ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len,
+ &actual, 1000);
+
+ if (ret < 0) {
+ dev_err(&usbdev->dev, "Error : usb_bulk_msg ( result = %d )\n",
+ ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len)
+{
+ int ret;
+ int actual;
+
+ ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len,
+ &actual, 5000);
+
+ if (ret < 0) {
+ dev_err(&usbdev->dev,
+ "Error : usb_bulk_msg(recv) ( result = %d )\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int download_image(struct usb_device *usbdev,
+ const struct firmware *firm,
+ loff_t pos, u32 img_len, u32 magic_num)
+{
+ struct dn_header h;
+ int ret = 0;
+ u32 size;
+
+ size = ALIGN(img_len, DOWNLOAD_SIZE);
+ h.magic_num = __cpu_to_be32(magic_num);
+ h.file_size = __cpu_to_be32(size);
+
+ ret = gdm_wibro_send(usbdev, &h, sizeof(h));
+ if (ret < 0)
+ return ret;
+
+ while (img_len > 0) {
+ if (img_len > DOWNLOAD_SIZE)
+ size = DOWNLOAD_SIZE;
+ else
+ size = img_len; /* the last chunk of data */
+
+ memcpy(tx_buf, firm->data + pos, size);
+ ret = gdm_wibro_send(usbdev, tx_buf, size);
+
+ if (ret < 0)
+ return ret;
+
+ img_len -= size;
+ pos += size;
+ }
+
+ return ret;
+}
+
+int usb_boot(struct usb_device *usbdev, u16 pid)
+{
+ int i, ret = 0;
+ struct img_header hdr;
+ struct fw_info fw_info;
+ loff_t pos = 0;
+ char *img_name = FW_DIR FW_UIMG;
+ const struct firmware *firm;
+
+ ret = request_firmware(&firm, img_name, &usbdev->dev);
+ if (ret < 0) {
+ dev_err(&usbdev->dev,
+ "requesting firmware %s failed with error %d\n",
+ img_name, ret);
+ return ret;
+ }
+
+ tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL);
+ if (tx_buf == NULL)
+ return -ENOMEM;
+
+ if (firm->size < sizeof(hdr)) {
+ dev_err(&usbdev->dev, "Cannot read the image info.\n");
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(&hdr, firm->data, sizeof(hdr));
+
+ array_le32_to_cpu((u32 *)&hdr, 19);
+
+ if (hdr.count > MAX_IMG_CNT) {
+ dev_err(&usbdev->dev, "Too many images. %d\n", hdr.count);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < hdr.count; i++) {
+ if (hdr.offset[i] > hdr.len) {
+ dev_err(&usbdev->dev,
+ "Invalid offset. Entry = %d Offset = 0x%08x Image length = 0x%08x\n",
+ i, hdr.offset[i], hdr.len);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ pos = hdr.offset[i];
+ if (firm->size < sizeof(fw_info) + pos) {
+ dev_err(&usbdev->dev, "Cannot read the FW info.\n");
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(&fw_info, firm->data + pos, sizeof(fw_info));
+
+ array_le32_to_cpu((u32 *)&fw_info, 8);
+
+ if ((fw_info.id & 0xffff) != pid)
+ continue;
+
+ pos = hdr.offset[i] + fw_info.kernel_offset;
+ if (firm->size < fw_info.kernel_len + pos) {
+ dev_err(&usbdev->dev, "Kernel FW is too small.\n");
+ goto out;
+ }
+
+ ret = download_image(usbdev, firm, pos, fw_info.kernel_len,
+ DN_KERNEL_MAGIC_NUMBER);
+ if (ret < 0)
+ goto out;
+ dev_info(&usbdev->dev, "GCT: Kernel download success.\n");
+
+ pos = hdr.offset[i] + fw_info.rootfs_offset;
+ if (firm->size < fw_info.rootfs_len + pos) {
+ dev_err(&usbdev->dev, "Filesystem FW is too small.\n");
+ goto out;
+ }
+ ret = download_image(usbdev, firm, pos, fw_info.rootfs_len,
+ DN_ROOTFS_MAGIC_NUMBER);
+ if (ret < 0)
+ goto out;
+ dev_info(&usbdev->dev, "GCT: Filesystem download success.\n");
+
+ break;
+ }
+
+ if (i == hdr.count) {
+ dev_err(&usbdev->dev, "Firmware for gsk%x is not installed.\n",
+ pid);
+ ret = -EINVAL;
+ }
+out:
+ release_firmware(firm);
+ kfree(tx_buf);
+ return ret;
+}
+
+/*#define GDM7205_PADDING 256 */
+#define DOWNLOAD_CHUCK 2048
+#define KERNEL_TYPE_STRING "linux"
+#define FS_TYPE_STRING "rootfs"
+
+static int em_wait_ack(struct usb_device *usbdev, int send_zlp)
+{
+ int ack;
+ int ret = -1;
+
+ if (send_zlp) {
+ /*Send ZLP*/
+ ret = gdm_wibro_send(usbdev, NULL, 0);
+ if (ret < 0)
+ goto out;
+ }
+
+ /*Wait for ACK*/
+ ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack));
+ if (ret < 0)
+ goto out;
+out:
+ return ret;
+}
+
+static int em_download_image(struct usb_device *usbdev, const char *img_name,
+ char *type_string)
+{
+ char *buf = NULL;
+ loff_t pos = 0;
+ int ret = 0;
+ int len;
+ int img_len;
+ const struct firmware *firm;
+ #if defined(GDM7205_PADDING)
+ const int pad_size = GDM7205_PADDING;
+ #else
+ const int pad_size = 0;
+ #endif
+
+ ret = request_firmware(&firm, img_name, &usbdev->dev);
+ if (ret < 0) {
+ dev_err(&usbdev->dev,
+ "requesting firmware %s failed with error %d\n",
+ img_name, ret);
+ return ret;
+ }
+
+ buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ strcpy(buf+pad_size, type_string);
+ ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size);
+ if (ret < 0)
+ goto out;
+
+ img_len = firm->size;
+
+ if (img_len <= 0) {
+ ret = -1;
+ goto out;
+ }
+
+ while (img_len > 0) {
+ if (img_len > DOWNLOAD_CHUCK)
+ len = DOWNLOAD_CHUCK;
+ else
+ len = img_len; /* the last chunk of data */
+
+ memcpy(buf+pad_size, firm->data + pos, len);
+ ret = gdm_wibro_send(usbdev, buf, len+pad_size);
+
+ if (ret < 0)
+ goto out;
+
+ img_len -= DOWNLOAD_CHUCK;
+ pos += DOWNLOAD_CHUCK;
+
+ ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0));
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = em_wait_ack(usbdev, 1);
+ if (ret < 0)
+ goto out;
+
+out:
+ release_firmware(firm);
+ kfree(buf);
+
+ return ret;
+}
+
+static int em_fw_reset(struct usb_device *usbdev)
+{
+ /*Send ZLP*/
+ return gdm_wibro_send(usbdev, NULL, 0);
+}
+
+int usb_emergency(struct usb_device *usbdev)
+{
+ int ret;
+ const char *kern_name = FW_DIR FW_KERN;
+ const char *fs_name = FW_DIR FW_FS;
+
+ ret = em_download_image(usbdev, kern_name, KERNEL_TYPE_STRING);
+ if (ret < 0)
+ return ret;
+ dev_err(&usbdev->dev, "GCT Emergency: Kernel download success.\n");
+
+ ret = em_download_image(usbdev, fs_name, FS_TYPE_STRING);
+ if (ret < 0)
+ return ret;
+ dev_info(&usbdev->dev, "GCT Emergency: Filesystem download success.\n");
+
+ ret = em_fw_reset(usbdev);
+
+ return ret;
+}
diff --git a/drivers/staging/gdm72xx/usb_boot.h b/drivers/staging/gdm72xx/usb_boot.h
new file mode 100644
index 00000000000..05308e25305
--- /dev/null
+++ b/drivers/staging/gdm72xx/usb_boot.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __USB_BOOT_H__
+#define __USB_BOOT_H__
+
+struct usb_device;
+
+int usb_boot(struct usb_device *usbdev, u16 pid);
+int usb_emergency(struct usb_device *usbdev);
+
+#endif /* __USB_BOOT_H__ */
diff --git a/drivers/staging/gdm72xx/usb_ids.h b/drivers/staging/gdm72xx/usb_ids.h
new file mode 100644
index 00000000000..1a61b359976
--- /dev/null
+++ b/drivers/staging/gdm72xx/usb_ids.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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 __USB_IDS_H__
+#define __USB_IDS_H__
+
+/*You can replace vendor-ID as yours.*/
+#define GCT_VID 0x1076
+
+/*You can replace product-ID as yours.*/
+#define GCT_PID1 0x7e00
+#define GCT_PID2 0x7f00
+
+#define USB_DEVICE_ID_MATCH_DEVICE_INTERFACE \
+ (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS)
+
+#define USB_DEVICE_INTF(vend, prod, intf) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_INTERFACE, \
+ .idVendor = (vend), .idProduct = (prod), .bInterfaceClass = (intf)
+
+#define EMERGENCY_PID 0x720f
+#define BL_PID_MASK 0xffc0
+
+#define USB_DEVICE_BOOTLOADER(vid, pid) \
+ {USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD)}, \
+ {USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD|B_DIFF_DL_DRV)}
+
+#define USB_DEVICE_CDC_DATA(vid, pid) \
+ {USB_DEVICE_INTF((vid), (pid), USB_CLASS_CDC_DATA)}
+
+static const struct usb_device_id id_table[] = {
+ USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID1),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x1),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x2),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x3),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x4),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x5),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x6),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x7),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x8),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x9),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xa),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xb),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xc),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xd),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xe),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xf),
+
+ USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID2),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x1),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x2),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x3),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x4),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x5),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x6),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x7),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x8),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x9),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xa),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xb),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xc),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xd),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xe),
+ USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xf),
+
+ {USB_DEVICE(GCT_VID, EMERGENCY_PID)},
+ { }
+};
+
+#endif /* __USB_IDS_H__ */
diff --git a/drivers/staging/gdm72xx/wm_ioctl.h b/drivers/staging/gdm72xx/wm_ioctl.h
new file mode 100644
index 00000000000..d022c6ca0e2
--- /dev/null
+++ b/drivers/staging/gdm72xx/wm_ioctl.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+#if !defined(WM_IOCTL_H_20080714)
+#define WM_IOCTL_H_20080714
+#if !defined(__KERNEL__)
+#include <net/if.h>
+#endif
+
+#define NETLINK_WIMAX 31
+
+#define SIOCWMIOCTL SIOCDEVPRIVATE
+
+#define SIOCG_DATA 0x8D10
+#define SIOCS_DATA 0x8D11
+
+enum {
+ SIOC_DATA_FSM,
+ SIOC_DATA_NETLIST,
+ SIOC_DATA_CONNNSP,
+ SIOC_DATA_CONNCOMP,
+ SIOC_DATA_PROFILEID,
+
+ SIOC_DATA_END
+};
+
+#define SIOC_DATA_MAX 16
+
+/* FSM */
+enum {
+ M_INIT = 0,
+ M_OPEN_OFF,
+ M_OPEN_ON,
+ M_SCAN,
+ M_CONNECTING,
+ M_CONNECTED,
+ M_FSM_END,
+
+ C_INIT = 0,
+ C_CONNSTART,
+ C_ASSOCSTART,
+ C_RNG,
+ C_SBC,
+ C_AUTH,
+ C_REG,
+ C_DSX,
+ C_ASSOCCOMPLETE,
+ C_CONNCOMPLETE,
+ C_FSM_END,
+
+ D_INIT = 0,
+ D_READY,
+ D_LISTEN,
+ D_IPACQUISITION,
+
+ END_FSM
+};
+
+struct fsm_s {
+ int m_status; /*main status*/
+ int c_status; /*connection status*/
+ int d_status; /*oma-dm status*/
+};
+
+struct data_s {
+ int size;
+ void *buf;
+};
+
+struct wm_req_s {
+ union {
+ char ifrn_name[IFNAMSIZ];
+ } ifr_ifrn;
+ unsigned short cmd;
+ unsigned short data_id;
+ struct data_s data;
+
+/* NOTE: sizeof(struct wm_req_s) must be less than sizeof(struct ifreq). */
+};
+
+#ifndef ifr_name
+#define ifr_name ifr_ifrn.ifrn_name
+#endif
+
+#endif
diff --git a/drivers/staging/goldfish/Kconfig b/drivers/staging/goldfish/Kconfig
new file mode 100644
index 00000000000..4e094602437
--- /dev/null
+++ b/drivers/staging/goldfish/Kconfig
@@ -0,0 +1,13 @@
+config GOLDFISH_AUDIO
+ tristate "Goldfish AVD Audio Device"
+ depends on GOLDFISH
+ ---help---
+ Emulated audio channel for the Goldfish Android Virtual Device
+
+config MTD_GOLDFISH_NAND
+ tristate "Goldfish NAND device"
+ depends on GOLDFISH
+ depends on MTD
+ help
+ Drives the emulated NAND flash device on the Google Goldfish
+ Android virtual device.
diff --git a/drivers/staging/goldfish/Makefile b/drivers/staging/goldfish/Makefile
new file mode 100644
index 00000000000..dec34ad5816
--- /dev/null
+++ b/drivers/staging/goldfish/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for the Goldfish audio driver
+#
+
+obj-$(CONFIG_GOLDFISH_AUDIO) += goldfish_audio.o
+obj-$(CONFIG_MTD_GOLDFISH_NAND) += goldfish_nand.o
diff --git a/drivers/staging/goldfish/README b/drivers/staging/goldfish/README
new file mode 100644
index 00000000000..183af005323
--- /dev/null
+++ b/drivers/staging/goldfish/README
@@ -0,0 +1,11 @@
+Audio
+-----
+- Move to using the ALSA framework not faking it
+- Fix the wrong user page DMA (moving to ALSA may fix that too)
+
+NAND
+----
+- Remove excess checking of parameters in calls
+- Use dma coherent memory not kmalloc/__pa for the memory (this is just
+ a cleanliness issue not a correctness one)
+
diff --git a/drivers/staging/goldfish/goldfish_audio.c b/drivers/staging/goldfish/goldfish_audio.c
new file mode 100644
index 00000000000..cbd456770af
--- /dev/null
+++ b/drivers/staging/goldfish/goldfish_audio.c
@@ -0,0 +1,384 @@
+/*
+ * drivers/misc/goldfish_audio.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (C) 2012 Intel, 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 <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/dma-mapping.h>
+#include <linux/uaccess.h>
+#include <linux/goldfish.h>
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_DESCRIPTION("Android QEMU Audio Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+struct goldfish_audio {
+ char __iomem *reg_base;
+ int irq;
+ spinlock_t lock;
+ wait_queue_head_t wait;
+
+ char __iomem *buffer_virt; /* combined buffer virtual address */
+ unsigned long buffer_phys; /* combined buffer physical address */
+
+ char __iomem *write_buffer1; /* write buffer 1 virtual address */
+ char __iomem *write_buffer2; /* write buffer 2 virtual address */
+ char __iomem *read_buffer; /* read buffer virtual address */
+ int buffer_status;
+ int read_supported; /* true if we have audio input support */
+};
+
+/*
+ * We will allocate two read buffers and two write buffers.
+ * Having two read buffers facilitate stereo -> mono conversion.
+ * Having two write buffers facilitate interleaved IO.
+ */
+#define READ_BUFFER_SIZE 16384
+#define WRITE_BUFFER_SIZE 16384
+#define COMBINED_BUFFER_SIZE ((2 * READ_BUFFER_SIZE) + \
+ (2 * WRITE_BUFFER_SIZE))
+
+#define AUDIO_READ(data, addr) (readl(data->reg_base + addr))
+#define AUDIO_WRITE(data, addr, x) (writel(x, data->reg_base + addr))
+#define AUDIO_WRITE64(data, addr, addr2, x) \
+ (gf_write64((u64)(x), data->reg_base + addr, data->reg_base+addr2))
+
+/*
+ * temporary variable used between goldfish_audio_probe() and
+ * goldfish_audio_open()
+ */
+static struct goldfish_audio *audio_data;
+
+enum {
+ /* audio status register */
+ AUDIO_INT_STATUS = 0x00,
+ /* set this to enable IRQ */
+ AUDIO_INT_ENABLE = 0x04,
+ /* set these to specify buffer addresses */
+ AUDIO_SET_WRITE_BUFFER_1 = 0x08,
+ AUDIO_SET_WRITE_BUFFER_2 = 0x0C,
+ /* set number of bytes in buffer to write */
+ AUDIO_WRITE_BUFFER_1 = 0x10,
+ AUDIO_WRITE_BUFFER_2 = 0x14,
+ AUDIO_SET_WRITE_BUFFER_1_HIGH = 0x28,
+ AUDIO_SET_WRITE_BUFFER_2_HIGH = 0x30,
+
+ /* true if audio input is supported */
+ AUDIO_READ_SUPPORTED = 0x18,
+ /* buffer to use for audio input */
+ AUDIO_SET_READ_BUFFER = 0x1C,
+ AUDIO_SET_READ_BUFFER_HIGH = 0x34,
+
+ /* driver writes number of bytes to read */
+ AUDIO_START_READ = 0x20,
+
+ /* number of bytes available in read buffer */
+ AUDIO_READ_BUFFER_AVAILABLE = 0x24,
+
+ /* AUDIO_INT_STATUS bits */
+
+ /* this bit set when it is safe to write more bytes to the buffer */
+ AUDIO_INT_WRITE_BUFFER_1_EMPTY = 1U << 0,
+ AUDIO_INT_WRITE_BUFFER_2_EMPTY = 1U << 1,
+ AUDIO_INT_READ_BUFFER_FULL = 1U << 2,
+
+ AUDIO_INT_MASK = AUDIO_INT_WRITE_BUFFER_1_EMPTY |
+ AUDIO_INT_WRITE_BUFFER_2_EMPTY |
+ AUDIO_INT_READ_BUFFER_FULL,
+};
+
+
+static atomic_t open_count = ATOMIC_INIT(0);
+
+
+static ssize_t goldfish_audio_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct goldfish_audio *data = fp->private_data;
+ int length;
+ int result = 0;
+
+ if (!data->read_supported)
+ return -ENODEV;
+
+ while (count > 0) {
+ length = (count > READ_BUFFER_SIZE ? READ_BUFFER_SIZE : count);
+ AUDIO_WRITE(data, AUDIO_START_READ, length);
+
+ wait_event_interruptible(data->wait,
+ (data->buffer_status & AUDIO_INT_READ_BUFFER_FULL));
+
+ length = AUDIO_READ(data,
+ AUDIO_READ_BUFFER_AVAILABLE);
+
+ /* copy data to user space */
+ if (copy_to_user(buf, data->read_buffer, length))
+ return -EFAULT;
+
+ result += length;
+ buf += length;
+ count -= length;
+ }
+ return result;
+}
+
+static ssize_t goldfish_audio_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct goldfish_audio *data = fp->private_data;
+ unsigned long irq_flags;
+ ssize_t result = 0;
+ char __iomem *kbuf;
+
+ while (count > 0) {
+ ssize_t copy = count;
+
+ if (copy > WRITE_BUFFER_SIZE)
+ copy = WRITE_BUFFER_SIZE;
+ wait_event_interruptible(data->wait, (data->buffer_status &
+ (AUDIO_INT_WRITE_BUFFER_1_EMPTY |
+ AUDIO_INT_WRITE_BUFFER_2_EMPTY)));
+
+ if ((data->buffer_status & AUDIO_INT_WRITE_BUFFER_1_EMPTY) != 0)
+ kbuf = data->write_buffer1;
+ else
+ kbuf = data->write_buffer2;
+
+ /* copy from user space to the appropriate buffer */
+ if (copy_from_user(kbuf, buf, copy)) {
+ result = -EFAULT;
+ break;
+ }
+
+ spin_lock_irqsave(&data->lock, irq_flags);
+ /*
+ * clear the buffer empty flag, and signal the emulator
+ * to start writing the buffer
+ */
+ if (kbuf == data->write_buffer1) {
+ data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_1_EMPTY;
+ AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_1, copy);
+ } else {
+ data->buffer_status &= ~AUDIO_INT_WRITE_BUFFER_2_EMPTY;
+ AUDIO_WRITE(data, AUDIO_WRITE_BUFFER_2, copy);
+ }
+ spin_unlock_irqrestore(&data->lock, irq_flags);
+
+ buf += copy;
+ result += copy;
+ count -= copy;
+ }
+ return result;
+}
+
+static int goldfish_audio_open(struct inode *ip, struct file *fp)
+{
+ if (!audio_data)
+ return -ENODEV;
+
+ if (atomic_inc_return(&open_count) == 1) {
+ fp->private_data = audio_data;
+ audio_data->buffer_status = (AUDIO_INT_WRITE_BUFFER_1_EMPTY |
+ AUDIO_INT_WRITE_BUFFER_2_EMPTY);
+ AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, AUDIO_INT_MASK);
+ return 0;
+ } else {
+ atomic_dec(&open_count);
+ return -EBUSY;
+ }
+}
+
+static int goldfish_audio_release(struct inode *ip, struct file *fp)
+{
+ atomic_dec(&open_count);
+ /* FIXME: surely this is wrong for the multi-opened case */
+ AUDIO_WRITE(audio_data, AUDIO_INT_ENABLE, 0);
+ return 0;
+}
+
+static long goldfish_audio_ioctl(struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ /* temporary workaround, until we switch to the ALSA API */
+ if (cmd == 315)
+ return -1;
+ else
+ return 0;
+}
+
+static irqreturn_t goldfish_audio_interrupt(int irq, void *dev_id)
+{
+ unsigned long irq_flags;
+ struct goldfish_audio *data = dev_id;
+ u32 status;
+
+ spin_lock_irqsave(&data->lock, irq_flags);
+
+ /* read buffer status flags */
+ status = AUDIO_READ(data, AUDIO_INT_STATUS);
+ status &= AUDIO_INT_MASK;
+ /*
+ * if buffers are newly empty, wake up blocked
+ * goldfish_audio_write() call
+ */
+ if (status) {
+ data->buffer_status = status;
+ wake_up(&data->wait);
+ }
+
+ spin_unlock_irqrestore(&data->lock, irq_flags);
+ return status ? IRQ_HANDLED : IRQ_NONE;
+}
+
+/* file operations for /dev/eac */
+static const struct file_operations goldfish_audio_fops = {
+ .owner = THIS_MODULE,
+ .read = goldfish_audio_read,
+ .write = goldfish_audio_write,
+ .open = goldfish_audio_open,
+ .release = goldfish_audio_release,
+ .unlocked_ioctl = goldfish_audio_ioctl,
+};
+
+static struct miscdevice goldfish_audio_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "eac",
+ .fops = &goldfish_audio_fops,
+};
+
+static int goldfish_audio_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+ struct goldfish_audio *data;
+ dma_addr_t buf_addr;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ ret = -ENOMEM;
+ goto err_data_alloc_failed;
+ }
+ spin_lock_init(&data->lock);
+ init_waitqueue_head(&data->wait);
+ platform_set_drvdata(pdev, data);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "platform_get_resource failed\n");
+ ret = -ENODEV;
+ goto err_no_io_base;
+ }
+ data->reg_base = ioremap(r->start, PAGE_SIZE);
+ if (data->reg_base == NULL) {
+ ret = -ENOMEM;
+ goto err_no_io_base;
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ ret = -ENODEV;
+ goto err_no_irq;
+ }
+ data->buffer_virt = dma_alloc_coherent(&pdev->dev,
+ COMBINED_BUFFER_SIZE, &buf_addr, GFP_KERNEL);
+ if (data->buffer_virt == 0) {
+ ret = -ENOMEM;
+ dev_err(&pdev->dev, "allocate buffer failed\n");
+ goto err_alloc_write_buffer_failed;
+ }
+ data->buffer_phys = buf_addr;
+ data->write_buffer1 = data->buffer_virt;
+ data->write_buffer2 = data->buffer_virt + WRITE_BUFFER_SIZE;
+ data->read_buffer = data->buffer_virt + 2 * WRITE_BUFFER_SIZE;
+
+ ret = request_irq(data->irq, goldfish_audio_interrupt,
+ IRQF_SHARED, pdev->name, data);
+ if (ret) {
+ dev_err(&pdev->dev, "request_irq failed\n");
+ goto err_request_irq_failed;
+ }
+
+ ret = misc_register(&goldfish_audio_device);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "misc_register returned %d in goldfish_audio_init\n",
+ ret);
+ goto err_misc_register_failed;
+ }
+
+ AUDIO_WRITE64(data, AUDIO_SET_WRITE_BUFFER_1,
+ AUDIO_SET_WRITE_BUFFER_1_HIGH, buf_addr);
+ buf_addr += WRITE_BUFFER_SIZE;
+
+ AUDIO_WRITE64(data, AUDIO_SET_WRITE_BUFFER_2,
+ AUDIO_SET_WRITE_BUFFER_2_HIGH, buf_addr);
+
+ buf_addr += WRITE_BUFFER_SIZE;
+
+ data->read_supported = AUDIO_READ(data, AUDIO_READ_SUPPORTED);
+ if (data->read_supported)
+ AUDIO_WRITE64(data, AUDIO_SET_READ_BUFFER,
+ AUDIO_SET_READ_BUFFER_HIGH, buf_addr);
+
+ audio_data = data;
+ return 0;
+
+err_misc_register_failed:
+ free_irq(data->irq, data);
+err_request_irq_failed:
+ dma_free_coherent(&pdev->dev, COMBINED_BUFFER_SIZE,
+ data->buffer_virt, data->buffer_phys);
+err_alloc_write_buffer_failed:
+err_no_irq:
+ iounmap(data->reg_base);
+err_no_io_base:
+ kfree(data);
+err_data_alloc_failed:
+ return ret;
+}
+
+static int goldfish_audio_remove(struct platform_device *pdev)
+{
+ struct goldfish_audio *data = platform_get_drvdata(pdev);
+
+ misc_deregister(&goldfish_audio_device);
+ free_irq(data->irq, data);
+ dma_free_coherent(&pdev->dev, COMBINED_BUFFER_SIZE,
+ data->buffer_virt, data->buffer_phys);
+ iounmap(data->reg_base);
+ kfree(data);
+ audio_data = NULL;
+ return 0;
+}
+
+static struct platform_driver goldfish_audio_driver = {
+ .probe = goldfish_audio_probe,
+ .remove = goldfish_audio_remove,
+ .driver = {
+ .name = "goldfish_audio"
+ }
+};
+
+module_platform_driver(goldfish_audio_driver);
diff --git a/drivers/staging/goldfish/goldfish_nand.c b/drivers/staging/goldfish/goldfish_nand.c
new file mode 100644
index 00000000000..092604c698b
--- /dev/null
+++ b/drivers/staging/goldfish/goldfish_nand.c
@@ -0,0 +1,440 @@
+/*
+ * drivers/mtd/devices/goldfish_nand.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (C) 2012 Intel, Inc.
+ * Copyright (C) 2013 Intel, 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 <linux/io.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/goldfish.h>
+#include <asm/div64.h>
+
+#include "goldfish_nand_reg.h"
+
+struct goldfish_nand {
+ struct mutex lock;
+ unsigned char __iomem *base;
+ struct cmd_params *cmd_params;
+ size_t mtd_count;
+ struct mtd_info mtd[0];
+};
+
+static u32 goldfish_nand_cmd_with_params(struct mtd_info *mtd,
+ enum nand_cmd cmd, u64 addr, u32 len,
+ void *ptr, u32 *rv)
+{
+ u32 cmdp;
+ struct goldfish_nand *nand = mtd->priv;
+ struct cmd_params *cps = nand->cmd_params;
+ unsigned char __iomem *base = nand->base;
+
+ if (cps == NULL)
+ return -1;
+
+ switch (cmd) {
+ case NAND_CMD_ERASE:
+ cmdp = NAND_CMD_ERASE_WITH_PARAMS;
+ break;
+ case NAND_CMD_READ:
+ cmdp = NAND_CMD_READ_WITH_PARAMS;
+ break;
+ case NAND_CMD_WRITE:
+ cmdp = NAND_CMD_WRITE_WITH_PARAMS;
+ break;
+ default:
+ return -1;
+ }
+ cps->dev = mtd - nand->mtd;
+ cps->addr_high = (u32)(addr >> 32);
+ cps->addr_low = (u32)addr;
+ cps->transfer_size = len;
+ cps->data = (unsigned long)ptr;
+ writel(cmdp, base + NAND_COMMAND);
+ *rv = cps->result;
+ return 0;
+}
+
+static u32 goldfish_nand_cmd(struct mtd_info *mtd, enum nand_cmd cmd,
+ u64 addr, u32 len, void *ptr)
+{
+ struct goldfish_nand *nand = mtd->priv;
+ u32 rv;
+ unsigned char __iomem *base = nand->base;
+
+ mutex_lock(&nand->lock);
+ if (goldfish_nand_cmd_with_params(mtd, cmd, addr, len, ptr, &rv)) {
+ writel(mtd - nand->mtd, base + NAND_DEV);
+ writel((u32)(addr >> 32), base + NAND_ADDR_HIGH);
+ writel((u32)addr, base + NAND_ADDR_LOW);
+ writel(len, base + NAND_TRANSFER_SIZE);
+ gf_write64((u64)ptr, base + NAND_DATA, base + NAND_DATA_HIGH);
+ writel(cmd, base + NAND_COMMAND);
+ rv = readl(base + NAND_RESULT);
+ }
+ mutex_unlock(&nand->lock);
+ return rv;
+}
+
+static int goldfish_nand_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ loff_t ofs = instr->addr;
+ u32 len = instr->len;
+ u32 rem;
+
+ if (ofs + len > mtd->size)
+ goto invalid_arg;
+ rem = do_div(ofs, mtd->writesize);
+ if (rem)
+ goto invalid_arg;
+ ofs *= (mtd->writesize + mtd->oobsize);
+
+ if (len % mtd->writesize)
+ goto invalid_arg;
+ len = len / mtd->writesize * (mtd->writesize + mtd->oobsize);
+
+ if (goldfish_nand_cmd(mtd, NAND_CMD_ERASE, ofs, len, NULL) != len) {
+ pr_err("goldfish_nand_erase: erase failed, start %llx, len %x, dev_size %llx, erase_size %x\n",
+ ofs, len, mtd->size, mtd->erasesize);
+ return -EIO;
+ }
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_erase: invalid erase, start %llx, len %x, dev_size %llx, erase_size %x\n",
+ ofs, len, mtd->size, mtd->erasesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_read_oob(struct mtd_info *mtd, loff_t ofs,
+ struct mtd_oob_ops *ops)
+{
+ u32 rem;
+
+ if (ofs + ops->len > mtd->size)
+ goto invalid_arg;
+ if (ops->datbuf && ops->len && ops->len != mtd->writesize)
+ goto invalid_arg;
+ if (ops->ooblen + ops->ooboffs > mtd->oobsize)
+ goto invalid_arg;
+
+ rem = do_div(ofs, mtd->writesize);
+ if (rem)
+ goto invalid_arg;
+ ofs *= (mtd->writesize + mtd->oobsize);
+
+ if (ops->datbuf)
+ ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs,
+ ops->len, ops->datbuf);
+ ofs += mtd->writesize + ops->ooboffs;
+ if (ops->oobbuf)
+ ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, ofs,
+ ops->ooblen, ops->oobbuf);
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_read_oob: invalid read, start %llx, len %zx, ooblen %zx, dev_size %llx, write_size %x\n",
+ ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_write_oob(struct mtd_info *mtd, loff_t ofs,
+ struct mtd_oob_ops *ops)
+{
+ u32 rem;
+
+ if (ofs + ops->len > mtd->size)
+ goto invalid_arg;
+ if (ops->len && ops->len != mtd->writesize)
+ goto invalid_arg;
+ if (ops->ooblen + ops->ooboffs > mtd->oobsize)
+ goto invalid_arg;
+
+ rem = do_div(ofs, mtd->writesize);
+ if (rem)
+ goto invalid_arg;
+ ofs *= (mtd->writesize + mtd->oobsize);
+
+ if (ops->datbuf)
+ ops->retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs,
+ ops->len, ops->datbuf);
+ ofs += mtd->writesize + ops->ooboffs;
+ if (ops->oobbuf)
+ ops->oobretlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, ofs,
+ ops->ooblen, ops->oobbuf);
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_write_oob: invalid write, start %llx, len %zx, ooblen %zx, dev_size %llx, write_size %x\n",
+ ofs, ops->len, ops->ooblen, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ u32 rem;
+
+ if (from + len > mtd->size)
+ goto invalid_arg;
+
+ rem = do_div(from, mtd->writesize);
+ if (rem)
+ goto invalid_arg;
+ from *= (mtd->writesize + mtd->oobsize);
+
+ *retlen = goldfish_nand_cmd(mtd, NAND_CMD_READ, from, len, buf);
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_read: invalid read, start %llx, len %zx, dev_size %llx, write_size %x\n",
+ from, len, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ u32 rem;
+
+ if (to + len > mtd->size)
+ goto invalid_arg;
+
+ rem = do_div(to, mtd->writesize);
+ if (rem)
+ goto invalid_arg;
+ to *= (mtd->writesize + mtd->oobsize);
+
+ *retlen = goldfish_nand_cmd(mtd, NAND_CMD_WRITE, to, len, (void *)buf);
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_write: invalid write, start %llx, len %zx, dev_size %llx, write_size %x\n",
+ to, len, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ u32 rem;
+
+ if (ofs >= mtd->size)
+ goto invalid_arg;
+
+ rem = do_div(ofs, mtd->erasesize);
+ if (rem)
+ goto invalid_arg;
+ ofs *= mtd->erasesize / mtd->writesize;
+ ofs *= (mtd->writesize + mtd->oobsize);
+
+ return goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_GET, ofs, 0, NULL);
+
+invalid_arg:
+ pr_err("goldfish_nand_block_isbad: invalid arg, ofs %llx, dev_size %llx, write_size %x\n",
+ ofs, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int goldfish_nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ u32 rem;
+
+ if (ofs >= mtd->size)
+ goto invalid_arg;
+
+ rem = do_div(ofs, mtd->erasesize);
+ if (rem)
+ goto invalid_arg;
+ ofs *= mtd->erasesize / mtd->writesize;
+ ofs *= (mtd->writesize + mtd->oobsize);
+
+ if (goldfish_nand_cmd(mtd, NAND_CMD_BLOCK_BAD_SET, ofs, 0, NULL) != 1)
+ return -EIO;
+ return 0;
+
+invalid_arg:
+ pr_err("goldfish_nand_block_markbad: invalid arg, ofs %llx, dev_size %llx, write_size %x\n",
+ ofs, mtd->size, mtd->writesize);
+ return -EINVAL;
+}
+
+static int nand_setup_cmd_params(struct platform_device *pdev,
+ struct goldfish_nand *nand)
+{
+ u64 paddr;
+ unsigned char __iomem *base = nand->base;
+
+ nand->cmd_params = devm_kzalloc(&pdev->dev,
+ sizeof(struct cmd_params), GFP_KERNEL);
+ if (!nand->cmd_params)
+ return -1;
+
+ paddr = __pa(nand->cmd_params);
+ writel((u32)(paddr >> 32), base + NAND_CMD_PARAMS_ADDR_HIGH);
+ writel((u32)paddr, base + NAND_CMD_PARAMS_ADDR_LOW);
+ return 0;
+}
+
+static int goldfish_nand_init_device(struct platform_device *pdev,
+ struct goldfish_nand *nand, int id)
+{
+ u32 name_len;
+ u32 result;
+ u32 flags;
+ unsigned char __iomem *base = nand->base;
+ struct mtd_info *mtd = &nand->mtd[id];
+ char *name;
+
+ mutex_lock(&nand->lock);
+ writel(id, base + NAND_DEV);
+ flags = readl(base + NAND_DEV_FLAGS);
+ name_len = readl(base + NAND_DEV_NAME_LEN);
+ mtd->writesize = readl(base + NAND_DEV_PAGE_SIZE);
+ mtd->size = readl(base + NAND_DEV_SIZE_LOW);
+ mtd->size |= (u64)readl(base + NAND_DEV_SIZE_HIGH) << 32;
+ mtd->oobsize = readl(base + NAND_DEV_EXTRA_SIZE);
+ mtd->oobavail = mtd->oobsize;
+ mtd->erasesize = readl(base + NAND_DEV_ERASE_SIZE) /
+ (mtd->writesize + mtd->oobsize) * mtd->writesize;
+ do_div(mtd->size, mtd->writesize + mtd->oobsize);
+ mtd->size *= mtd->writesize;
+ dev_dbg(&pdev->dev,
+ "goldfish nand dev%d: size %llx, page %d, extra %d, erase %d\n",
+ id, mtd->size, mtd->writesize,
+ mtd->oobsize, mtd->erasesize);
+ mutex_unlock(&nand->lock);
+
+ mtd->priv = nand;
+
+ mtd->name = name = devm_kzalloc(&pdev->dev, name_len + 1, GFP_KERNEL);
+ if (name == NULL)
+ return -ENOMEM;
+
+ result = goldfish_nand_cmd(mtd, NAND_CMD_GET_DEV_NAME, 0, name_len,
+ name);
+ if (result != name_len) {
+ dev_err(&pdev->dev,
+ "goldfish_nand_init_device failed to get dev name %d != %d\n",
+ result, name_len);
+ return -ENODEV;
+ }
+ ((char *) mtd->name)[name_len] = '\0';
+
+ /* Setup the MTD structure */
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ if (flags & NAND_DEV_FLAG_READ_ONLY)
+ mtd->flags &= ~MTD_WRITEABLE;
+ if (flags & NAND_DEV_FLAG_CMD_PARAMS_CAP)
+ nand_setup_cmd_params(pdev, nand);
+
+ mtd->owner = THIS_MODULE;
+ mtd->_erase = goldfish_nand_erase;
+ mtd->_read = goldfish_nand_read;
+ mtd->_write = goldfish_nand_write;
+ mtd->_read_oob = goldfish_nand_read_oob;
+ mtd->_write_oob = goldfish_nand_write_oob;
+ mtd->_block_isbad = goldfish_nand_block_isbad;
+ mtd->_block_markbad = goldfish_nand_block_markbad;
+
+ if (mtd_device_register(mtd, NULL, 0))
+ return -EIO;
+
+ return 0;
+}
+
+static int goldfish_nand_probe(struct platform_device *pdev)
+{
+ u32 num_dev;
+ int i;
+ int err;
+ u32 num_dev_working;
+ u32 version;
+ struct resource *r;
+ struct goldfish_nand *nand;
+ unsigned char __iomem *base;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL)
+ return -ENODEV;
+
+ base = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE);
+ if (base == NULL)
+ return -ENOMEM;
+
+ version = readl(base + NAND_VERSION);
+ if (version != NAND_VERSION_CURRENT) {
+ dev_err(&pdev->dev,
+ "goldfish_nand_init: version mismatch, got %d, expected %d\n",
+ version, NAND_VERSION_CURRENT);
+ return -ENODEV;
+ }
+ num_dev = readl(base + NAND_NUM_DEV);
+ if (num_dev == 0)
+ return -ENODEV;
+
+ nand = devm_kzalloc(&pdev->dev, sizeof(*nand) +
+ sizeof(struct mtd_info) * num_dev, GFP_KERNEL);
+ if (nand == NULL)
+ return -ENOMEM;
+
+ mutex_init(&nand->lock);
+ nand->base = base;
+ nand->mtd_count = num_dev;
+ platform_set_drvdata(pdev, nand);
+
+ num_dev_working = 0;
+ for (i = 0; i < num_dev; i++) {
+ err = goldfish_nand_init_device(pdev, nand, i);
+ if (err == 0)
+ num_dev_working++;
+ }
+ if (num_dev_working == 0)
+ return -ENODEV;
+ return 0;
+}
+
+static int goldfish_nand_remove(struct platform_device *pdev)
+{
+ struct goldfish_nand *nand = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < nand->mtd_count; i++) {
+ if (nand->mtd[i].name)
+ mtd_device_unregister(&nand->mtd[i]);
+ }
+ return 0;
+}
+
+static struct platform_driver goldfish_nand_driver = {
+ .probe = goldfish_nand_probe,
+ .remove = goldfish_nand_remove,
+ .driver = {
+ .name = "goldfish_nand"
+ }
+};
+
+module_platform_driver(goldfish_nand_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/goldfish/goldfish_nand_reg.h b/drivers/staging/goldfish/goldfish_nand_reg.h
new file mode 100644
index 00000000000..fe7f47c7a5c
--- /dev/null
+++ b/drivers/staging/goldfish/goldfish_nand_reg.h
@@ -0,0 +1,76 @@
+/*
+ * drivers/mtd/devices/goldfish_nand_reg.h
+ *
+ * Copyright (C) 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 GOLDFISH_NAND_REG_H
+#define GOLDFISH_NAND_REG_H
+
+enum nand_cmd {
+ /* Write device name for NAND_DEV to NAND_DATA (vaddr) */
+ NAND_CMD_GET_DEV_NAME,
+ NAND_CMD_READ,
+ NAND_CMD_WRITE,
+ NAND_CMD_ERASE,
+ /* NAND_RESULT is 1 if block is bad, 0 if it is not */
+ NAND_CMD_BLOCK_BAD_GET,
+ NAND_CMD_BLOCK_BAD_SET,
+ NAND_CMD_READ_WITH_PARAMS,
+ NAND_CMD_WRITE_WITH_PARAMS,
+ NAND_CMD_ERASE_WITH_PARAMS
+};
+
+enum nand_dev_flags {
+ NAND_DEV_FLAG_READ_ONLY = 0x00000001,
+ NAND_DEV_FLAG_CMD_PARAMS_CAP = 0x00000002,
+};
+
+#define NAND_VERSION_CURRENT (1)
+
+enum nand_reg {
+ /* Global */
+ NAND_VERSION = 0x000,
+ NAND_NUM_DEV = 0x004,
+ NAND_DEV = 0x008,
+
+ /* Dev info */
+ NAND_DEV_FLAGS = 0x010,
+ NAND_DEV_NAME_LEN = 0x014,
+ NAND_DEV_PAGE_SIZE = 0x018,
+ NAND_DEV_EXTRA_SIZE = 0x01c,
+ NAND_DEV_ERASE_SIZE = 0x020,
+ NAND_DEV_SIZE_LOW = 0x028,
+ NAND_DEV_SIZE_HIGH = 0x02c,
+
+ /* Command */
+ NAND_RESULT = 0x040,
+ NAND_COMMAND = 0x044,
+ NAND_DATA = 0x048,
+ NAND_DATA_HIGH = 0x100,
+ NAND_TRANSFER_SIZE = 0x04c,
+ NAND_ADDR_LOW = 0x050,
+ NAND_ADDR_HIGH = 0x054,
+ NAND_CMD_PARAMS_ADDR_LOW = 0x058,
+ NAND_CMD_PARAMS_ADDR_HIGH = 0x05c,
+};
+
+struct cmd_params {
+ uint32_t dev;
+ uint32_t addr_low;
+ uint32_t addr_high;
+ uint32_t transfer_size;
+ unsigned long data;
+ uint32_t result;
+};
+#endif
diff --git a/drivers/staging/gs_fpgaboot/Kconfig b/drivers/staging/gs_fpgaboot/Kconfig
new file mode 100644
index 00000000000..550645291fa
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/Kconfig
@@ -0,0 +1,8 @@
+#
+# "xilinx FPGA firmware download, fpgaboot"
+#
+config GS_FPGABOOT
+ tristate "Xilinx FPGA firmware download module"
+ default n
+ help
+ Xilinx FPGA firmware download module
diff --git a/drivers/staging/gs_fpgaboot/Makefile b/drivers/staging/gs_fpgaboot/Makefile
new file mode 100644
index 00000000000..d2f0211ba54
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/Makefile
@@ -0,0 +1,2 @@
+gs_fpga-y += gs_fpgaboot.o io.o
+obj-$(CONFIG_GS_FPGABOOT) += gs_fpga.o
diff --git a/drivers/staging/gs_fpgaboot/README b/drivers/staging/gs_fpgaboot/README
new file mode 100644
index 00000000000..cfa8624304e
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/README
@@ -0,0 +1,71 @@
+==============================================================================
+Linux Driver Source for Xilinx FPGA firmware download
+==============================================================================
+
+
+TABLE OF CONTENTS.
+
+1. SUMMARY
+2. BACKGROUND
+3. DESIGN
+4. HOW TO USE
+5. REFERENCE
+
+1. SUMMARY
+
+ - Download Xilinx FPGA firmware
+ - This module downloads Xilinx FPGA firmware using gpio pins.
+
+2. BACKGROUND
+
+ An FPGA (Field Programmable Gate Array) is a programmable hardware that is
+ used in various applications. Hardware design needs to programmed through
+ a dedicated device or CPU assisted way (serial or parallel).
+ This driver provides a way to download FPGA firmware.
+
+3. DESIGN
+
+ - load Xilinx FPGA bitstream format[1] firmware image file using
+ kernel firmware framework, request_firmware()
+ - program the Xilinx FPGA using SelectMAP (parallel) mode [2]
+ - FPGA prgram is done by gpio based bit-banging, as an example
+ - platform independent file: gs_fpgaboot.c
+ - platform dependent file: io.c
+
+
+4. HOW TO USE
+
+ $ insmod gs_fpga.ko file="xlinx_fpga_top_bitstream.bit"
+ $ rmmod gs_fpga
+
+5. USE CASE (from a mailing list discussion with Greg)
+
+ a. As a FPGA development support tool,
+ During FPGA firmware development, you need to download a new FPGA
+ image frequently.
+ You would do that with a dedicated JTAG, which usually a limited
+ resource in the lab.
+ However, if you use my driver, you don't have to have a dedicated JTAG.
+ This is a real gain :)
+
+ b. For the FPGA that runs without config after the download, which
+ doesn't talk to any of Linux interfaces (such as PCIE).
+
+ We download FPGA firmware from user triggered or some other way, and that's it.
+ Since that FPGA runs on its own, it doesn't require a linux driver
+ after the download.
+
+ c. For the FPGA that requires config after the download, which talk to
+ any of linux interfaces (such as PCIE)
+
+ Then, this type of FPGA config can be put into device tree and have a
+ separate driver (pcie or others), then THAT driver calls my driver to
+ download FPGA firmware during the Linux boot, the take over the device
+ through the interface.
+
+6. REFERENCE
+
+ 1. Xilinx APP NOTE XAPP583:
+ http://www.xilinx.com/support/documentation/application_notes/xapp583-fpga-configuration.pdf
+ 2. bitstream file info:
+ http://home.earthlink.net/~davesullins/software/bitinfo.html
diff --git a/drivers/staging/gs_fpgaboot/TODO b/drivers/staging/gs_fpgaboot/TODO
new file mode 100644
index 00000000000..2d9fb17d606
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/TODO
@@ -0,0 +1,7 @@
+TODO:
+ - get bus width input instead of hardcoded bus width
+ - get it reviewed
+
+Please send any patches for this driver to Insop Song<insop.song@gainspeed.com>
+and Greg Kroah-Hartman <gregkh@linuxfoundation.org>.
+And please CC to "Staging subsystem" mail list <devel@driverdev.osuosl.org> too.
diff --git a/drivers/staging/gs_fpgaboot/gs_fpgaboot.c b/drivers/staging/gs_fpgaboot/gs_fpgaboot.c
new file mode 100644
index 00000000000..6aa9d7c3013
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/gs_fpgaboot.c
@@ -0,0 +1,421 @@
+/*
+ * This program is free software; you can redistribute 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/firmware.h>
+
+#include "gs_fpgaboot.h"
+#include "io.h"
+
+#define DEVICE_NAME "device"
+#define CLASS_NAME "fpgaboot"
+
+static uint8_t bits_magic[] = {
+ 0x0, 0x9, 0xf, 0xf0, 0xf, 0xf0,
+ 0xf, 0xf0, 0xf, 0xf0, 0x0, 0x0, 0x1};
+
+/* fake device for request_firmware */
+static struct platform_device *firmware_pdev;
+
+static char *file = "xlinx_fpga_firmware.bit";
+module_param(file, charp, S_IRUGO);
+MODULE_PARM_DESC(file, "Xilinx FPGA firmware file.");
+
+#ifdef DEBUG_FPGA
+static void datadump(char *msg, void *m, int n)
+{
+ int i;
+ unsigned char *c;
+
+ pr_info("=== %s ===\n", msg);
+
+ c = m;
+
+ for (i = 0; i < n; i++) {
+ if ((i&0xf) == 0)
+ pr_info(KERN_INFO "\n 0x%4x: ", i);
+
+ pr_info("%02X ", c[i]);
+ }
+
+ pr_info("\n");
+}
+#endif /* DEBUG_FPGA */
+
+static void read_bitstream(char *bitdata, char *buf, int *offset, int rdsize)
+{
+ memcpy(buf, bitdata + *offset, rdsize);
+ *offset += rdsize;
+}
+
+static void readinfo_bitstream(char *bitdata, char *buf, int *offset)
+{
+ char tbuf[64];
+ int32_t len;
+
+ /* read section char */
+ read_bitstream(bitdata, tbuf, offset, 1);
+
+ /* read length */
+ read_bitstream(bitdata, tbuf, offset, 2);
+
+ len = tbuf[0] << 8 | tbuf[1];
+
+ read_bitstream(bitdata, buf, offset, len);
+ buf[len] = '\0';
+}
+
+/*
+ * read bitdata length
+ */
+static int readlength_bitstream(char *bitdata, int *lendata, int *offset)
+{
+ char tbuf[64];
+
+ /* read section char */
+ read_bitstream(bitdata, tbuf, offset, 1);
+
+ /* make sure it is section 'e' */
+ if (tbuf[0] != 'e') {
+ pr_err("error: length section is not 'e', but %c\n", tbuf[0]);
+ return -1;
+ }
+
+ /* read 4bytes length */
+ read_bitstream(bitdata, tbuf, offset, 4);
+
+ *lendata = tbuf[0] << 24 | tbuf[1] << 16 |
+ tbuf[2] << 8 | tbuf[3];
+
+ return 0;
+}
+
+
+/*
+ * read first 13 bytes to check bitstream magic number
+ */
+static int readmagic_bitstream(char *bitdata, int *offset)
+{
+ char buf[13];
+ int r;
+
+ read_bitstream(bitdata, buf, offset, 13);
+ r = memcmp(buf, bits_magic, 13);
+ if (r) {
+ pr_err("error: corrupted header");
+ return -1;
+ }
+ pr_info("bitstream file magic number Ok\n");
+
+ *offset = 13; /* magic length */
+
+ return 0;
+}
+
+/*
+ * NOTE: supports only bitstream format
+ */
+static enum fmt_image get_imageformat(struct fpgaimage *fimage)
+{
+ return f_bit;
+}
+
+static void gs_print_header(struct fpgaimage *fimage)
+{
+ pr_info("file: %s\n", fimage->filename);
+ pr_info("part: %s\n", fimage->part);
+ pr_info("date: %s\n", fimage->date);
+ pr_info("time: %s\n", fimage->time);
+ pr_info("lendata: %d\n", fimage->lendata);
+}
+
+static void gs_read_bitstream(struct fpgaimage *fimage)
+{
+ char *bitdata;
+ int size;
+ int offset;
+
+ offset = 0;
+ bitdata = (char *)fimage->fw_entry->data;
+ size = fimage->fw_entry->size;
+
+ readmagic_bitstream(bitdata, &offset);
+ readinfo_bitstream(bitdata, fimage->filename, &offset);
+ readinfo_bitstream(bitdata, fimage->part, &offset);
+ readinfo_bitstream(bitdata, fimage->date, &offset);
+ readinfo_bitstream(bitdata, fimage->time, &offset);
+ readlength_bitstream(bitdata, &fimage->lendata, &offset);
+
+ fimage->fpgadata = bitdata + offset;
+}
+
+static int gs_read_image(struct fpgaimage *fimage)
+{
+ int img_fmt;
+
+ img_fmt = get_imageformat(fimage);
+
+ switch (img_fmt) {
+ case f_bit:
+ pr_info("image is bitstream format\n");
+ gs_read_bitstream(fimage);
+ break;
+ default:
+ pr_err("unsupported fpga image format\n");
+ return -1;
+ }
+
+ gs_print_header(fimage);
+
+ return 0;
+}
+
+static int gs_load_image(struct fpgaimage *fimage, char *file)
+{
+ int err;
+
+ pr_info("load fpgaimage %s\n", file);
+
+ err = request_firmware(&fimage->fw_entry, file, &firmware_pdev->dev);
+ if (err != 0) {
+ pr_err("firmware %s is missing, cannot continue.\n", file);
+ return err;
+ }
+
+ return 0;
+}
+
+static int gs_download_image(struct fpgaimage *fimage, enum wbus bus_bytes)
+{
+ char *bitdata;
+ int size, i, cnt;
+
+ cnt = 0;
+ bitdata = (char *)fimage->fpgadata;
+ size = fimage->lendata;
+
+#ifdef DEBUG_FPGA
+ datadump("bitfile sample", bitdata, 0x100);
+#endif /* DEBUG_FPGA */
+
+ if (!xl_supported_prog_bus_width(bus_bytes)) {
+ pr_err("unsupported program bus width %d\n",
+ bus_bytes);
+ return -1;
+ }
+
+ /* Bring csi_b, rdwr_b Low and program_b High */
+ xl_program_b(1);
+ xl_rdwr_b(0);
+ xl_csi_b(0);
+
+ /* Configuration reset */
+ xl_program_b(0);
+ msleep(20);
+ xl_program_b(1);
+
+ /* Wait for Device Initialization */
+ while (xl_get_init_b() == 0)
+ ;
+
+ pr_info("device init done\n");
+
+ for (i = 0; i < size; i += bus_bytes)
+ xl_shift_bytes_out(bus_bytes, bitdata+i);
+
+ pr_info("program done\n");
+
+ /* Check INIT_B */
+ if (xl_get_init_b() == 0) {
+ pr_err("init_b 0\n");
+ return -1;
+ }
+
+ while (xl_get_done_b() == 0) {
+ if (cnt++ > MAX_WAIT_DONE) {
+ pr_err("init_B %d\n", xl_get_init_b());
+ break;
+ }
+ }
+
+ if (cnt > MAX_WAIT_DONE) {
+ pr_err("fpga download fail\n");
+ return -1;
+ }
+
+ pr_info("download fpgaimage\n");
+
+ /* Compensate for Special Startup Conditions */
+ xl_shift_cclk(8);
+
+ return 0;
+}
+
+static int gs_release_image(struct fpgaimage *fimage)
+{
+ release_firmware(fimage->fw_entry);
+ pr_info("release fpgaimage\n");
+
+ return 0;
+}
+
+/*
+ * NOTE: supports systemmap parallel programming
+ */
+static int gs_set_download_method(struct fpgaimage *fimage)
+{
+ pr_info("set program method\n");
+
+ fimage->dmethod = m_systemmap;
+
+ pr_info("systemmap program method\n");
+
+ return 0;
+}
+
+static int init_driver(void)
+{
+ firmware_pdev = platform_device_register_simple("fpgaboot", -1,
+ NULL, 0);
+ return PTR_ERR_OR_ZERO(firmware_pdev);
+}
+
+static void finish_driver(void)
+{
+ platform_device_unregister(firmware_pdev);
+}
+
+static int gs_fpgaboot(void)
+{
+ int err;
+ struct fpgaimage *fimage;
+
+ fimage = kmalloc(sizeof(struct fpgaimage), GFP_KERNEL);
+ if (fimage == NULL) {
+ pr_err("No memory is available\n");
+ goto err_out;
+ }
+
+ err = gs_load_image(fimage, file);
+ if (err) {
+ pr_err("gs_load_image error\n");
+ goto err_out1;
+ }
+
+ err = gs_read_image(fimage);
+ if (err) {
+ pr_err("gs_read_image error\n");
+ goto err_out2;
+ }
+
+ err = gs_set_download_method(fimage);
+ if (err) {
+ pr_err("gs_set_download_method error\n");
+ goto err_out2;
+ }
+
+ err = gs_download_image(fimage, bus_2byte);
+ if (err) {
+ pr_err("gs_download_image error\n");
+ goto err_out2;
+ }
+
+ err = gs_release_image(fimage);
+ if (err) {
+ pr_err("gs_release_image error\n");
+ goto err_out1;
+ }
+
+ kfree(fimage);
+ return 0;
+
+err_out2:
+ err = gs_release_image(fimage);
+ if (err)
+ pr_err("gs_release_image error\n");
+err_out1:
+ kfree(fimage);
+
+err_out:
+ return -1;
+
+}
+
+static int __init gs_fpgaboot_init(void)
+{
+ int err, r;
+
+ r = -1;
+
+ pr_info("FPGA DOWNLOAD --->\n");
+
+ pr_info("FPGA image file name: %s\n", file);
+
+ err = init_driver();
+ if (err != 0) {
+ pr_err("FPGA DRIVER INIT FAIL!!\n");
+ return r;
+ }
+
+ err = xl_init_io();
+ if (err) {
+ pr_err("GPIO INIT FAIL!!\n");
+ r = -1;
+ goto errout;
+ }
+
+ err = gs_fpgaboot();
+ if (err) {
+ pr_err("FPGA DOWNLOAD FAIL!!\n");
+ r = -1;
+ goto errout;
+ }
+
+ pr_info("FPGA DOWNLOAD DONE <---\n");
+
+ r = 0;
+ return r;
+
+errout:
+ finish_driver();
+
+ return r;
+}
+
+static void __exit gs_fpgaboot_exit(void)
+{
+ finish_driver();
+ pr_info("FPGA image download module removed\n");
+}
+
+module_init(gs_fpgaboot_init);
+module_exit(gs_fpgaboot_exit);
+
+MODULE_AUTHOR("Insop Song");
+MODULE_DESCRIPTION("Xlinix FPGA firmware download");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/gs_fpgaboot/gs_fpgaboot.h b/drivers/staging/gs_fpgaboot/gs_fpgaboot.h
new file mode 100644
index 00000000000..f41f4cc798c
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/gs_fpgaboot.h
@@ -0,0 +1,56 @@
+/*
+ * This program is free software; you can redistribute 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 <linux/firmware.h>
+
+#define MAX_STR 256
+
+enum fmt_image {
+ f_bit, /* only bitstream is supported */
+ f_rbt,
+ f_bin,
+ f_mcs,
+ f_hex,
+};
+
+enum mdownload {
+ m_systemmap, /* only system map is supported */
+ m_serial,
+ m_jtag,
+};
+
+/*
+ * xilinx fpgaimage information
+ * NOTE: use MAX_STR instead of dynamic alloc for simplicity
+ */
+struct fpgaimage {
+ enum fmt_image fmt_img;
+ enum mdownload dmethod;
+
+ const struct firmware *fw_entry;
+
+ /*
+ * the followings can be read from bitstream,
+ * but other image format should have as well
+ */
+ char filename[MAX_STR];
+ char part[MAX_STR];
+ char date[MAX_STR];
+ char time[MAX_STR];
+ int32_t lendata;
+ char *fpgadata;
+};
diff --git a/drivers/staging/gs_fpgaboot/io.c b/drivers/staging/gs_fpgaboot/io.c
new file mode 100644
index 00000000000..23c12f47c8f
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/io.c
@@ -0,0 +1,300 @@
+/*
+ * This program is free software; you can redistribute 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+
+#include "io.h"
+
+#ifdef CONFIG_B4860G100
+static struct gpiobus gbus;
+#endif /* CONFIG_B4860G100 */
+
+static inline void byte0_out(unsigned char data);
+static inline void byte1_out(unsigned char data);
+static inline void xl_cclk_b(int32_t i);
+
+
+/* Assert and Deassert CCLK */
+void xl_shift_cclk(int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ xl_cclk_b(1);
+ xl_cclk_b(0);
+ }
+}
+
+int xl_supported_prog_bus_width(enum wbus bus_bytes)
+{
+ switch (bus_bytes) {
+ case bus_1byte:
+ break;
+ case bus_2byte:
+ break;
+ default:
+ pr_err("unsupported program bus width %d\n",
+ bus_bytes);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Serialize byte and clock each bit on target's DIN and CCLK pins */
+void xl_shift_bytes_out(enum wbus bus_byte, unsigned char *pdata)
+{
+ /*
+ * supports 1 and 2 bytes programming mode
+ */
+ if (likely(bus_byte == bus_2byte))
+ byte0_out(pdata[0]);
+
+ byte1_out(pdata[1]);
+ xl_shift_cclk(1);
+}
+
+/*
+ * generic bit swap for xilinx SYSTEMMAP FPGA programming
+ */
+static inline unsigned char bitswap(unsigned char s)
+{
+ unsigned char d;
+
+ d = (((s&0x80)>>7) | ((s&0x40)>>5) | ((s&0x20)>>3) | ((s&0x10)>>1) |
+ ((s&0x08)<<1) | ((s&0x04)<<3) | ((s&0x02)<<5) | ((s&0x01)<<7));
+ return d;
+}
+
+#ifdef CONFIG_B4860G100
+/*
+ * ======================================================================
+ * board specific configuration
+ */
+
+static inline void mpc85xx_gpio_set_dir(
+ int32_t port,
+ uint32_t mask,
+ uint32_t dir)
+{
+ dir |= (in_be32(gbus.r[port]+GPDIR) & ~mask);
+ out_be32(gbus.r[port]+GPDIR, dir);
+}
+
+static inline void mpc85xx_gpio_set(int32_t port, uint32_t mask, uint32_t val)
+{
+ /* First mask off the unwanted parts of "dir" and "val" */
+ val &= mask;
+
+ /* Now read in the values we're supposed to preserve */
+ val |= (in_be32(gbus.r[port]+GPDAT) & ~mask);
+
+ out_be32(gbus.r[port]+GPDAT, val);
+}
+
+static inline uint32_t mpc85xx_gpio_get(int32_t port, uint32_t mask)
+{
+ /* Read the requested values */
+ return in_be32(gbus.r[port]+GPDAT) & mask;
+}
+
+static inline void mpc85xx_gpio_set_low(int32_t port, uint32_t gpios)
+{
+ mpc85xx_gpio_set(port, gpios, 0x00000000);
+}
+
+static inline void mpc85xx_gpio_set_high(int32_t port, uint32_t gpios)
+{
+ mpc85xx_gpio_set(port, gpios, 0xFFFFFFFF);
+}
+
+static inline void gpio_set_value(int32_t port, uint32_t gpio, uint32_t value)
+{
+ int32_t g;
+
+ g = 31 - gpio;
+ if (value)
+ mpc85xx_gpio_set_high(port, 1U << g);
+ else
+ mpc85xx_gpio_set_low(port, 1U << g);
+}
+
+static inline int gpio_get_value(int32_t port, uint32_t gpio)
+{
+ int32_t g;
+
+ g = 31 - gpio;
+ return !!mpc85xx_gpio_get(port, 1U << g);
+}
+
+static inline void xl_cclk_b(int32_t i)
+{
+ gpio_set_value(XL_CCLK_PORT, XL_CCLK_PIN, i);
+}
+
+void xl_program_b(int32_t i)
+{
+ gpio_set_value(XL_PROGN_PORT, XL_PROGN_PIN, i);
+}
+
+void xl_rdwr_b(int32_t i)
+{
+ gpio_set_value(XL_RDWRN_PORT, XL_RDWRN_PIN, i);
+}
+
+void xl_csi_b(int32_t i)
+{
+ gpio_set_value(XL_CSIN_PORT, XL_CSIN_PIN, i);
+}
+
+int xl_get_init_b(void)
+{
+ return gpio_get_value(XL_INITN_PORT, XL_INITN_PIN);
+}
+
+int xl_get_done_b(void)
+{
+ return gpio_get_value(XL_DONE_PORT, XL_DONE_PIN);
+}
+
+
+/* G100 specific bit swap and remmap (to gpio pins) for byte 0 */
+static inline uint32_t bit_remap_byte0(uint32_t s)
+{
+ uint32_t d;
+
+ d = (((s&0x80)>>7) | ((s&0x40)>>5) | ((s&0x20)>>3) | ((s&0x10)>>1) |
+ ((s&0x08)<<1) | ((s&0x04)<<3) | ((s&0x02)<<6) | ((s&0x01)<<9));
+ return d;
+}
+
+/*
+ * G100 specific MSB, in this order [byte0 | byte1], out
+ */
+static inline void byte0_out(unsigned char data)
+{
+ uint32_t swap32;
+
+ swap32 = bit_remap_byte0((uint32_t) data) << 8;
+
+ mpc85xx_gpio_set(0, 0x0002BF00, (uint32_t) swap32);
+}
+
+/*
+ * G100 specific LSB, in this order [byte0 | byte1], out
+ */
+static inline void byte1_out(unsigned char data)
+{
+ mpc85xx_gpio_set(0, 0x000000FF, (uint32_t) bitswap(data));
+}
+
+/*
+ * configurable per device type for different I/O config
+ */
+int xl_init_io(void)
+{
+ struct device_node *np;
+ const u32 *p_reg;
+ int reg, cnt;
+
+ cnt = 0;
+ memset(&gbus, 0, sizeof(struct gpiobus));
+ for_each_compatible_node(np, NULL, "fsl,qoriq-gpio") {
+ p_reg = of_get_property(np, "reg", NULL);
+ if (p_reg == NULL)
+ break;
+ reg = (int) *p_reg;
+ gbus.r[cnt] = of_iomap(np, 0);
+
+ if (!gbus.r[cnt]) {
+ pr_err("not findding gpio cell-index %d\n", cnt);
+ return -ENODEV;
+ }
+ cnt++;
+ }
+ mpc85xx_gpio_set_dir(0, 0x0002BFFF, 0x0002BFFF);
+ mpc85xx_gpio_set_dir(1, 0x00240060, 0x00240060);
+
+ gbus.ngpio = cnt;
+
+ return 0;
+}
+
+
+#else /* placeholder for boards with different config */
+
+void xl_program_b(int32_t i)
+{
+ return;
+}
+
+void xl_rdwr_b(int32_t i)
+{
+ return;
+}
+
+void xl_csi_b(int32_t i)
+{
+ return;
+}
+
+int xl_get_init_b(void)
+{
+ return -1;
+}
+
+int xl_get_done_b(void)
+{
+ return -1;
+}
+
+static inline void byte0_out(unsigned char data)
+{
+ return;
+}
+
+static inline void byte1_out(unsigned char data)
+{
+ return;
+}
+
+static inline void xl_cclk_b(int32_t i)
+{
+ return;
+}
+
+/*
+ * configurable per device type for different I/O config
+ */
+int xl_init_io(void)
+{
+ return -1;
+}
+
+#endif /* CONFIG_B4860G100 */
diff --git a/drivers/staging/gs_fpgaboot/io.h b/drivers/staging/gs_fpgaboot/io.h
new file mode 100644
index 00000000000..7b46ac24b74
--- /dev/null
+++ b/drivers/staging/gs_fpgaboot/io.h
@@ -0,0 +1,90 @@
+/*
+ * This program is free software; you can redistribute 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 GPDIR 0
+#define GPCFG 4 /* open drain or not */
+#define GPDAT 8
+
+/*
+ * gpio port and pin definitions
+ * NOTE: port number starts from 0
+ */
+#define XL_INITN_PORT 1
+#define XL_INITN_PIN 14
+#define XL_RDWRN_PORT 1
+#define XL_RDWRN_PIN 13
+#define XL_CCLK_PORT 1
+#define XL_CCLK_PIN 10
+#define XL_PROGN_PORT 1
+#define XL_PROGN_PIN 25
+#define XL_CSIN_PORT 1
+#define XL_CSIN_PIN 26
+#define XL_DONE_PORT 1
+#define XL_DONE_PIN 27
+
+/*
+ * gpio mapping
+ *
+ XL_config_D0 – gpio1_31
+ Xl_config_d1 – gpio1_30
+ Xl_config_d2 – gpio1_29
+ Xl_config_d3 – gpio1_28
+ Xl_config_d4 – gpio1_27
+ Xl_config_d5 – gpio1_26
+ Xl_config_d6 – gpio1_25
+ Xl_config_d7 – gpio1_24
+ Xl_config_d8 – gpio1_23
+ Xl_config_d9 – gpio1_22
+ Xl_config_d10 – gpio1_21
+ Xl_config_d11 – gpio1_20
+ Xl_config_d12 – gpio1_19
+ Xl_config_d13 – gpio1_18
+ Xl_config_d14 – gpio1_16
+ Xl_config_d15 – gpio1_14
+*
+*/
+
+/*
+ * program bus width in bytes
+ */
+enum wbus {
+ bus_1byte = 1,
+ bus_2byte = 2,
+};
+
+
+#define MAX_WAIT_DONE 10000
+
+
+struct gpiobus {
+ int ngpio;
+ void __iomem *r[4];
+};
+
+int xl_supported_prog_bus_width(enum wbus bus_bytes);
+
+void xl_program_b(int32_t i);
+void xl_rdwr_b(int32_t i);
+void xl_csi_b(int32_t i);
+
+int xl_get_init_b(void);
+int xl_get_done_b(void);
+
+void xl_shift_cclk(int count);
+void xl_shift_bytes_out(enum wbus bus_byte, unsigned char *pdata);
+
+int xl_init_io(void);
diff --git a/drivers/staging/iio/Documentation/dac/max517 b/drivers/staging/iio/Documentation/dac/max517
new file mode 100644
index 00000000000..e60ec2f91a7
--- /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 <stigge@antcom.de>
+
+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 00000000000..8be32e5a0af
--- /dev/null
+++ b/drivers/staging/iio/Documentation/device.txt
@@ -0,0 +1,79 @@
+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_device_alloc(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->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 calibbias.
+ * 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->buffer:
+ 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
+ from 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_device_free(indio_dev) 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 00000000000..40d0ecac047
--- /dev/null
+++ b/drivers/staging/iio/Documentation/generic_buffer.c
@@ -0,0 +1,340 @@
+/* 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 <device_name> -t <trigger_name>
+ * If trigger name is not specified the program assumes you want a dataready
+ * trigger associated with the device and goes looking for it.
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/dir.h>
+#include <linux/types.h>
+#include <string.h>
+#include <poll.h>
+#include <endian.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include "iio_utils.h"
+
+/**
+ * size_from_channelarray() - calculate the storage size of a scan
+ * @channels: the channel info array
+ * @num_channels: number of channels
+ *
+ * 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((uint16_t)input);
+ else
+ input = le16toh((uint16_t)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 ", ((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
+ * @channels: information about the channels. Note
+ * size_from_channelarray must have been called first to fill the
+ * location offsets.
+ * @num_channels: number of channels
+ **/
+void process_scan(char *data,
+ struct iio_channel_info *channels,
+ int num_channels)
+{
+ int k;
+ for (k = 0; k < num_channels; k++)
+ switch (channels[k].bytes) {
+ /* only a few cases implemented so far */
+ case 2:
+ print2byte(*(uint16_t *)(data + channels[k].location),
+ &channels[k]);
+ break;
+ case 4:
+ if (!channels[k].is_signed) {
+ uint32_t val = *(uint32_t *)
+ (data + channels[k].location);
+ printf("%05f ", ((float)val +
+ channels[k].offset)*
+ channels[k].scale);
+
+ }
+ break;
+ case 8:
+ if (channels[k].is_signed) {
+ int64_t val = *(int64_t *)
+ (data +
+ channels[k].location);
+ if ((val >> channels[k].bits_used) & 1)
+ val = (val & channels[k].mask) |
+ ~channels[k].mask;
+ /* special case for timestamp */
+ if (channels[k].scale == 1.0f &&
+ channels[k].offset == 0.0f)
+ printf("%" PRId64 " ", val);
+ else
+ printf("%05f ", ((float)val +
+ channels[k].offset)*
+ channels[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;
+ 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 *channels;
+
+ 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 its
+ * name is <device_name>_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, &channels, &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 ready 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(channels, 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,
+ channels,
+ num_channels);
+ }
+
+ /* Stop the buffer */
+ ret = write_sysfs_int("enable", buf_dir_name, 0);
+ if (ret < 0)
+ goto error_close_buffer_access;
+
+ /* Disconnect 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 00000000000..3a9b0008740
--- /dev/null
+++ b/drivers/staging/iio/Documentation/iio_event_monitor.c
@@ -0,0 +1,255 @@
+/* Industrialio event test code.
+ *
+ * Copyright (c) 2011-2012 Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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 <device_name>
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include "iio_utils.h"
+#include <linux/iio/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",
+ [IIO_ALTVOLTAGE] = "altvoltage",
+};
+
+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",
+ [IIO_MOD_ROOT_SUM_SQUARED_X_Y] = "sqrt(x^2+y^2)",
+ [IIO_MOD_SUM_SQUARED_X_Y_Z] = "x^2+y^2+z^2",
+ [IIO_MOD_LIGHT_CLEAR] = "clear",
+ [IIO_MOD_LIGHT_RED] = "red",
+ [IIO_MOD_LIGHT_GREEN] = "green",
+ [IIO_MOD_LIGHT_BLUE] = "blue",
+};
+
+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:
+ case IIO_ALTVOLTAGE:
+ 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:
+ case IIO_MOD_ROOT_SUM_SQUARED_X_Y:
+ case IIO_MOD_SUM_SQUARED_X_Y_Z:
+ case IIO_MOD_LIGHT_CLEAR:
+ case IIO_MOD_LIGHT_RED:
+ case IIO_MOD_LIGHT_GREEN:
+ case IIO_MOD_LIGHT_BLUE:
+ 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 <device_name>\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 00000000000..a9cfc06edb0
--- /dev/null
+++ b/drivers/staging/iio/Documentation/iio_utils.h
@@ -0,0 +1,676 @@
+/* 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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <errno.h>
+
+/* Made up value to limit allocation sizes */
+#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
+ **/
+inline 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 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");
+ ret = -errno;
+ goto error_close_sysfsfp;
+ }
+ *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;
+ sysfsfp = 0;
+ }
+error_close_sysfsfp:
+ if (sysfsfp)
+ fclose(sysfsfp);
+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, 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, "%i", &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) {
+ int current_enabled = 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, "%i", &current_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,
+ &current->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", &current->index);
+ fclose(sysfsfp);
+ free(filename);
+ /* Find the scale */
+ ret = iioutils_get_param_float(&current->scale,
+ "scale",
+ device_dir,
+ current->name,
+ current->generic_name);
+ if (ret < 0)
+ goto error_cleanup_array;
+ ret = iioutils_get_param_float(&current->offset,
+ "offset",
+ device_dir,
+ current->name,
+ current->generic_name);
+ if (ret < 0)
+ goto error_cleanup_array;
+ ret = iioutils_get_type(&current->is_signed,
+ &current->bytes,
+ &current->bits_used,
+ &current->shift,
+ &current->mask,
+ &current->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\n");
+ 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) {
+ closedir(dp);
+ return -ENOMEM;
+ }
+ sprintf(filename, "%s%s%d/name",
+ iio_dir,
+ type,
+ number);
+ nameFile = fopen(filename, "r");
+ if (!nameFile) {
+ free(filename);
+ continue;
+ }
+ free(filename);
+ fscanf(nameFile, "%s", thisname);
+ fclose(nameFile);
+ if (strcmp(name, thisname) == 0) {
+ closedir(dp);
+ return number;
+ }
+ }
+ }
+ }
+ closedir(dp);
+ return -ENODEV;
+}
+
+inline int _write_sysfs_int(char *filename, char *basedir, int val, int verify)
+{
+ int ret = 0;
+ 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);
+ fclose(sysfsfp);
+ 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);
+ fclose(sysfsfp);
+ 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;
+}
+
+read_sysfs_string(const char *filename, const char *basedir, char *str)
+{
+ 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, "%s\n", str);
+ 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 00000000000..ab528409bba
--- /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_read_channel_raw() - get a reading
+iio_get_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_channel_get_all.
+
diff --git a/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 b/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583
new file mode 100644
index 00000000000..470f7ad9c07
--- /dev/null
+++ b/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583
@@ -0,0 +1,6 @@
+What: /sys/bus/iio/devices/device[n]/in_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.
diff --git a/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x b/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x
new file mode 100644
index 00000000000..b2798b258bf
--- /dev/null
+++ b/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x
@@ -0,0 +1,13 @@
+What: /sys/bus/iio/devices/device[n]/in_illuminance0_calibrate
+KernelVersion: 3.3-rc1
+Contact: linux-iio@vger.kernel.org
+Description:
+ 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]/in_proximity0_calibrate
+KernelVersion: 3.3-rc1
+Contact: linux-iio@vger.kernel.org
+Description:
+ Causes a recalculation and adjustment to the
+ proximity_thresh_rising_value.
diff --git a/drivers/staging/iio/Documentation/lsiio.c b/drivers/staging/iio/Documentation/lsiio.c
new file mode 100644
index 00000000000..24ae9694eb4
--- /dev/null
+++ b/drivers/staging/iio/Documentation/lsiio.c
@@ -0,0 +1,157 @@
+/*
+ * Industrial I/O utilities - lsiio.c
+ *
+ * Copyright (c) 2010 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
+ *
+ * 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 <string.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/dir.h>
+#include "iio_utils.h"
+
+
+static enum verbosity {
+ VERBLEVEL_DEFAULT, /* 0 gives lspci behaviour */
+ VERBLEVEL_SENSORS, /* 1 lists sensors */
+} verblevel = VERBLEVEL_DEFAULT;
+
+const char *type_device = "iio:device";
+const char *type_trigger = "trigger";
+
+
+static inline int check_prefix(const char *str, const char *prefix)
+{
+ return strlen(str) > strlen(prefix) &&
+ strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+static inline int check_postfix(const char *str, const char *postfix)
+{
+ return strlen(str) > strlen(postfix) &&
+ strcmp(str + strlen(str) - strlen(postfix), postfix) == 0;
+}
+
+static int dump_channels(const char *dev_dir_name)
+{
+ DIR *dp;
+ const struct dirent *ent;
+ dp = opendir(dev_dir_name);
+ if (dp == NULL)
+ return -errno;
+ while (ent = readdir(dp), ent != NULL)
+ if (check_prefix(ent->d_name, "in_") &&
+ check_postfix(ent->d_name, "_raw")) {
+ printf(" %-10s\n", ent->d_name);
+ }
+
+ return 0;
+}
+
+static int dump_one_device(const char *dev_dir_name)
+{
+ char name[IIO_MAX_NAME_LENGTH];
+ int dev_idx;
+
+ sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_device),
+ "%i", &dev_idx);
+ read_sysfs_string("name", dev_dir_name, name);
+ printf("Device %03d: %s\n", dev_idx, name);
+
+ if (verblevel >= VERBLEVEL_SENSORS) {
+ int ret = dump_channels(dev_dir_name);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int dump_one_trigger(const char *dev_dir_name)
+{
+ char name[IIO_MAX_NAME_LENGTH];
+ int dev_idx;
+
+ sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_trigger),
+ "%i", &dev_idx);
+ read_sysfs_string("name", dev_dir_name, name);
+ printf("Trigger %03d: %s\n", dev_idx, name);
+ return 0;
+}
+
+static void dump_devices(void)
+{
+ 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 industrial I/O devices available\n");
+ return;
+ }
+
+ while (ent = readdir(dp), ent != NULL) {
+ if (check_prefix(ent->d_name, type_device)) {
+ char *dev_dir_name;
+ asprintf(&dev_dir_name, "%s%s", iio_dir, ent->d_name);
+ dump_one_device(dev_dir_name);
+ free(dev_dir_name);
+ if (verblevel >= VERBLEVEL_SENSORS)
+ printf("\n");
+ }
+ }
+ rewinddir(dp);
+ while (ent = readdir(dp), ent != NULL) {
+ if (check_prefix(ent->d_name, type_trigger)) {
+ char *dev_dir_name;
+ asprintf(&dev_dir_name, "%s%s", iio_dir, ent->d_name);
+ dump_one_trigger(dev_dir_name);
+ free(dev_dir_name);
+ }
+ }
+ closedir(dp);
+}
+
+int main(int argc, char **argv)
+{
+ int c, err = 0;
+
+ while ((c = getopt(argc, argv, "d:D:v")) != EOF) {
+ switch (c) {
+ case 'v':
+ verblevel++;
+ break;
+
+ case '?':
+ default:
+ err++;
+ break;
+ }
+ }
+ if (err || argc > optind) {
+ fprintf(stderr, "Usage: lsiio [options]...\n"
+ "List industrial I/O devices\n"
+ " -v, --verbose\n"
+ " Increase verbosity (may be given multiple times)\n"
+ );
+ exit(1);
+ }
+
+ dump_devices();
+
+ return 0;
+}
diff --git a/drivers/staging/iio/Documentation/overview.txt b/drivers/staging/iio/Documentation/overview.txt
new file mode 100644
index 00000000000..43f92b06bc3
--- /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 its 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 00000000000..e1da43381d0
--- /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 its 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-ad7192 b/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192
new file mode 100644
index 00000000000..1c35c507cc0
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192
@@ -0,0 +1,20 @@
+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 00000000000..863d3856718
--- /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 00000000000..ee8c509c673
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-dds
@@ -0,0 +1,96 @@
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_frequencyY
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ Stores frequency into tuning word Y.
+ There will be more than one out_altvoltageX_frequencyY file,
+ which allows for pin controlled FSK Frequency Shift Keying
+ (out_altvoltageX_pincontrol_frequency_en is active) or the user
+ can control the desired active tuning word by writing Y to the
+ out_altvoltageX_frequencysymbol file.
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_frequencyY_scale
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ Scale to be applied to out_altvoltageX_frequencyY 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/.../out_altvoltageX_frequencysymbol
+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 out_altvoltageX_frequencyY.
+ To exit this mode the user can write
+ out_altvoltageX_pincontrol_frequency_en or
+ out_altvoltageX_out_enable file.
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_phaseY
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ Stores phase into Y.
+ There will be more than one out_altvoltageX_phaseY file, which
+ allows for pin controlled PSK Phase Shift Keying
+ (out_altvoltageX_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 phase_en file.
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_phaseY_scale
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ Scale to be applied to out_altvoltageX_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/.../out_altvoltageX_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
+ out_altvoltageX_phaseY. To exit this mode the user can write
+ out_altvoltageX_pincontrol_phase_en or disable file.
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_en
+What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_frequency_en
+What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_phase_en
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ out_altvoltageX_pincontrol_en: Both, the active frequency and
+ phase is controlled by the respective phase and frequency
+ control inputs. In case the device in features independent
+ controls, then there are dedicated files
+ (out_altvoltageX_pincontrol_frequency_en,
+ out_altvoltageX_pincontrol_phase_en).
+
+What: /sys/bus/iio/devices/.../out_altvoltageX_out_enable
+What: /sys/bus/iio/devices/.../out_altvoltageX_outY_enable
+KernelVersion: 2.6.37
+Contact: linux-iio@vger.kernel.org
+Description:
+ out_altvoltageX_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/.../out_altvoltageX_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/.../out_altvoltageX_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 00000000000..79c7e88c64c
--- /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 00000000000..17e5c9c515d
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light
@@ -0,0 +1,107 @@
+
+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]/in_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_suppression
+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]/in_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]/in_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]/in_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]/in_illuminance0_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]/in_intensity_clear[_input|_raw]
+What: /sys/bus/iio/devices/device[n]/in_intensity_red[_input|_raw]
+What: /sys/bus/iio/devices/device[n]/in_intensity_green[_input|_raw]
+What: /sys/bus/iio/devices/device[n]/in_intensity_blue[_input|_raw]
+KernelVersion: 3.6.0
+Contact: linux-iio@vger.kernel.org
+Description:
+ This property is supported by sensors that have a RGBC
+ 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 (irradiance, uW/mm^2), then it should
+ include _raw.
+
+What: /sys/bus/iio/devices/device[n]/in_cct0[_input|_raw]
+KernelVersion: 3.6.0
+Contact: linux-iio@vger.kernel.org
+Description:
+ This should return the correlated color temperature 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.
+
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 00000000000..660781df409
--- /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 00000000000..5235e6c749a
--- /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 00000000000..64e2e08fb4d
--- /dev/null
+++ b/drivers/staging/iio/Documentation/trigger.txt
@@ -0,0 +1,35 @@
+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_trigger_alloc("<trigger format string>", ...);
+
+allocates a trigger structure. The key elements to then fill in within
+a driver are:
+
+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 00000000000..fa38be0982f
--- /dev/null
+++ b/drivers/staging/iio/Kconfig
@@ -0,0 +1,47 @@
+#
+# Industrial I/O subsystem configuration
+#
+menu "IIO staging drivers"
+ depends on IIO
+
+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/frequency/Kconfig"
+source "drivers/staging/iio/gyro/Kconfig"
+source "drivers/staging/iio/impedance-analyzer/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"
+ select IIO_BUFFER
+ select IIO_KFIFO_BUF
+ help
+ Add buffered data capture to the simple dummy driver.
+
+endif # IIO_SIMPLE_DUMMY
+
+endmenu
diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile
new file mode 100644
index 00000000000..d87106135b2
--- /dev/null
+++ b/drivers/staging/iio/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for the industrial I/O core.
+#
+
+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-y += accel/
+obj-y += adc/
+obj-y += addac/
+obj-y += cdc/
+obj-y += frequency/
+obj-y += gyro/
+obj-y += impedance-analyzer/
+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 00000000000..c22a0edd152
--- /dev/null
+++ b/drivers/staging/iio/TODO
@@ -0,0 +1,84 @@
+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.
+
+MXS LRADC driver:
+This is a classic MFD device as it combines the following subdevices
+ - touchscreen controller (input subsystem related device)
+ - general purpose ADC channels
+ - battery voltage monitor (power subsystem related device)
+ - die temperature monitor (thermal management)
+
+At least the battery voltage and die temperature feature is required in-kernel
+by a driver of the SoC's battery charging unit to avoid any damage to the
+silicon and the battery.
+
+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 individual docs.
+
+Contact: Jonathan Cameron <jic23@kernel.org>.
+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 00000000000..ad45dfbdf41
--- /dev/null
+++ b/drivers/staging/iio/accel/Kconfig
@@ -0,0 +1,78 @@
+#
+# Accelerometer drivers
+#
+menu "Accelerometers"
+
+config ADIS16201
+ tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer"
+ depends on SPI
+ select IIO_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER 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_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER 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_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER 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_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER 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
+ select IIO_ADIS_LIB
+ 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_ADIS_LIB
+ select IIO_ADIS_LIB_BUFFER if IIO_BUFFER
+ help
+ Say yes here to build support for Analog Devices adis16240 programmable
+ impact Sensor and recorder.
+
+config LIS3L02DQ
+ tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver"
+ depends on SPI
+ select IIO_TRIGGER if IIO_BUFFER
+ depends on !IIO_BUFFER || IIO_KFIFO_BUF
+ depends on GPIOLIB
+ 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.
+
+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 00000000000..1ed137f1a50
--- /dev/null
+++ b/drivers/staging/iio/accel/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for industrial I/O accelerometer drivers
+#
+
+adis16201-y := adis16201_core.o
+obj-$(CONFIG_ADIS16201) += adis16201.o
+
+adis16203-y := adis16203_core.o
+obj-$(CONFIG_ADIS16203) += adis16203.o
+
+adis16204-y := adis16204_core.o
+obj-$(CONFIG_ADIS16204) += adis16204.o
+
+adis16209-y := adis16209_core.o
+obj-$(CONFIG_ADIS16209) += adis16209.o
+
+adis16220-y := adis16220_core.o
+obj-$(CONFIG_ADIS16220) += adis16220.o
+
+adis16240-y := adis16240_core.o
+obj-$(CONFIG_ADIS16240) += adis16240.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 00000000000..8747de5a980
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16201.h
@@ -0,0 +1,66 @@
+#ifndef SPI_ADIS16201_H_
+#define SPI_ADIS16201_H_
+
+#define ADIS16201_STARTUP_DELAY 220 /* ms */
+
+#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 */
+
+/* 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_BIT 3 /* SPI communications failure */
+#define ADIS16201_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */
+#define ADIS16201_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */
+#define ADIS16201_DIAG_STAT_POWER_LOW_BIT 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_ERROR_ACTIVE (1<<14)
+
+enum adis16201_scan {
+ ADIS16201_SCAN_ACC_X,
+ ADIS16201_SCAN_ACC_Y,
+ ADIS16201_SCAN_INCLI_X,
+ ADIS16201_SCAN_INCLI_Y,
+ ADIS16201_SCAN_SUPPLY,
+ ADIS16201_SCAN_AUX_ADC,
+ ADIS16201_SCAN_TEMP,
+};
+
+#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 00000000000..2105576fa77
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16201_core.c
@@ -0,0 +1,247 @@
+/*
+ * ADIS16201 Programmable Digital Vibration Sensor driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include "adis16201.h"
+
+static const u8 adis16201_addresses[] = {
+ [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS,
+ [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS,
+ [ADIS16201_SCAN_INCLI_X] = ADIS16201_XINCL_OFFS,
+ [ADIS16201_SCAN_INCLI_Y] = ADIS16201_YINCL_OFFS,
+};
+
+static int adis16201_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16201_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ *val = 1;
+ *val2 = 220000; /* 1.22 mV */
+ } else {
+ *val = 0;
+ *val2 = 610000; /* 0.610 mV */
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470; /* 0.47 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(462400); /* 0.4624 mg */
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_INCLI:
+ *val = 0;
+ *val2 = 100000; /* 0.1 degree */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = 25000 / -470 - 1278; /* 25 C = 1278 */
+ 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->scan_index];
+ ret = adis_read_reg_16(st, 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)
+{
+ struct adis *st = iio_priv(indio_dev);
+ 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->scan_index];
+ return adis_write_reg_16(st, addr, val16);
+ }
+ return -EINVAL;
+}
+
+static const struct iio_chan_spec adis16201_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT, ADIS16201_SCAN_SUPPLY, 12),
+ ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT, ADIS16201_SCAN_TEMP, 12),
+ ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT, ADIS16201_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT, ADIS16201_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC, ADIS16201_SCAN_AUX_ADC, 12),
+ ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT, ADIS16201_SCAN_INCLI_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ ADIS_INCLI_CHAN(X, ADIS16201_YINCL_OUT, ADIS16201_SCAN_INCLI_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ IIO_CHAN_SOFT_TIMESTAMP(7)
+};
+
+static const struct iio_info adis16201_info = {
+ .read_raw = &adis16201_read_raw,
+ .write_raw = &adis16201_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const adis16201_status_error_msgs[] = {
+ [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16201_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16201_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V",
+};
+
+static const struct adis_data adis16201_data = {
+ .read_delay = 20,
+ .msc_ctrl_reg = ADIS16201_MSC_CTRL,
+ .glob_cmd_reg = ADIS16201_GLOB_CMD,
+ .diag_stat_reg = ADIS16201_DIAG_STAT,
+
+ .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16201_STARTUP_DELAY,
+
+ .status_error_msgs = adis16201_status_error_msgs,
+ .status_error_mask = BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16201_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int adis16201_probe(struct spi_device *spi)
+{
+ int ret;
+ struct adis *st;
+ struct iio_dev *indio_dev;
+
+ /* setup the industrialio driver allocated elements */
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = adis_init(st, indio_dev, spi, &adis16201_data);
+ if (ret)
+ return ret;
+ ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(st);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto error_cleanup_buffer_trigger;
+ return 0;
+
+error_cleanup_buffer_trigger:
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+ return ret;
+}
+
+static int adis16201_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct adis *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver adis16201_driver = {
+ .driver = {
+ .name = "adis16201",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16201_probe,
+ .remove = 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/adis16203.h b/drivers/staging/iio/accel/adis16203.h
new file mode 100644
index 00000000000..acc688d7ea9
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16203.h
@@ -0,0 +1,59 @@
+#ifndef SPI_ADIS16203_H_
+#define SPI_ADIS16203_H_
+
+#define ADIS16203_STARTUP_DELAY 220 /* ms */
+
+#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 */
+
+/* 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_BIT 5 /* Self-test diagnostic error flag */
+#define ADIS16203_DIAG_STAT_SPI_FAIL_BIT 3 /* SPI communications failure */
+#define ADIS16203_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */
+#define ADIS16203_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */
+#define ADIS16203_DIAG_STAT_POWER_LOW_BIT 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_ERROR_ACTIVE (1<<14)
+
+enum adis16203_scan {
+ ADIS16203_SCAN_INCLI_X,
+ ADIS16203_SCAN_INCLI_Y,
+ ADIS16203_SCAN_SUPPLY,
+ ADIS16203_SCAN_AUX_ADC,
+ ADIS16203_SCAN_TEMP,
+};
+
+#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 00000000000..409a28ed904
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16203_core.c
@@ -0,0 +1,213 @@
+/*
+ * ADIS16203 Programmable Digital Vibration Sensor driver
+ *
+ * Copyright 2030 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include "adis16203.h"
+
+#define DRIVER_NAME "adis16203"
+
+static const u8 adis16203_addresses[] = {
+ [ADIS16203_SCAN_INCLI_X] = ADIS16203_INCL_NULL,
+};
+
+static int adis16203_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ /* currently only one writable parameter which keeps this simple */
+ u8 addr = adis16203_addresses[chan->scan_index];
+ return adis_write_reg_16(st, 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)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16203_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ *val = 1;
+ *val2 = 220000; /* 1.22 mV */
+ } else {
+ *val = 0;
+ *val2 = 610000; /* 0.61 mV */
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470; /* -0.47 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_INCLI:
+ *val = 0;
+ *val2 = 25000; /* 0.025 degree */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ *val = 25000 / -470 - 1278; /* 25 C = 1278 */
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ bits = 14;
+ mutex_lock(&indio_dev->mlock);
+ addr = adis16203_addresses[chan->scan_index];
+ ret = adis_read_reg_16(st, 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 const struct iio_chan_spec adis16203_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16203_SUPPLY_OUT, ADIS16203_SCAN_SUPPLY, 12),
+ ADIS_AUX_ADC_CHAN(ADIS16203_AUX_ADC, ADIS16203_SCAN_AUX_ADC, 12),
+ ADIS_INCLI_CHAN(X, ADIS16203_XINCL_OUT, ADIS16203_SCAN_INCLI_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ /* Fixme: Not what it appears to be - see data sheet */
+ ADIS_INCLI_CHAN(Y, ADIS16203_YINCL_OUT, ADIS16203_SCAN_INCLI_Y, 0, 14),
+ ADIS_TEMP_CHAN(ADIS16203_TEMP_OUT, ADIS16203_SCAN_TEMP, 12),
+ IIO_CHAN_SOFT_TIMESTAMP(5),
+};
+
+static const struct iio_info adis16203_info = {
+ .read_raw = &adis16203_read_raw,
+ .write_raw = &adis16203_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const adis16203_status_error_msgs[] = {
+ [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
+ [ADIS16203_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16203_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16203_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16203_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V",
+};
+
+static const struct adis_data adis16203_data = {
+ .read_delay = 20,
+ .msc_ctrl_reg = ADIS16203_MSC_CTRL,
+ .glob_cmd_reg = ADIS16203_GLOB_CMD,
+ .diag_stat_reg = ADIS16203_DIAG_STAT,
+
+ .self_test_mask = ADIS16203_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16203_STARTUP_DELAY,
+
+ .status_error_msgs = adis16203_status_error_msgs,
+ .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) |
+ BIT(ADIS16203_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16203_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16203_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16203_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int adis16203_probe(struct spi_device *spi)
+{
+ int ret;
+ struct iio_dev *indio_dev;
+ struct adis *st;
+
+ /* setup the industrialio driver allocated elements */
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = adis_init(st, indio_dev, spi, &adis16203_data);
+ if (ret)
+ return ret;
+
+ ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(st);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+
+ return 0;
+
+error_cleanup_buffer_trigger:
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+ return ret;
+}
+
+static int adis16203_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct adis *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver adis16203_driver = {
+ .driver = {
+ .name = "adis16203",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16203_probe,
+ .remove = 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/adis16204.h b/drivers/staging/iio/accel/adis16204.h
new file mode 100644
index 00000000000..9ff950c1e8d
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16204.h
@@ -0,0 +1,68 @@
+#ifndef SPI_ADIS16204_H_
+#define SPI_ADIS16204_H_
+
+#define ADIS16204_STARTUP_DELAY 220 /* ms */
+
+#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 */
+
+/* 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_BIT 5 /* Self-test diagnostic error flag: 1 = error condition,
+ 0 = normal operation */
+#define ADIS16204_DIAG_STAT_SPI_FAIL_BIT 3 /* SPI communications failure */
+#define ADIS16204_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */
+#define ADIS16204_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */
+#define ADIS16204_DIAG_STAT_POWER_LOW_BIT 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_ERROR_ACTIVE (1<<14)
+
+enum adis16204_scan {
+ ADIS16204_SCAN_ACC_X,
+ ADIS16204_SCAN_ACC_Y,
+ ADIS16204_SCAN_ACC_XY,
+ ADIS16204_SCAN_SUPPLY,
+ ADIS16204_SCAN_AUX_ADC,
+ ADIS16204_SCAN_TEMP,
+};
+
+#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 00000000000..b8ea76857cd
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16204_core.c
@@ -0,0 +1,251 @@
+/*
+ * ADIS16204 Programmable High-g Digital Impact Sensor and Recorder
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include "adis16204.h"
+
+/* Unique to this driver currently */
+
+static const u8 adis16204_addresses[][2] = {
+ [ADIS16204_SCAN_ACC_X] = { ADIS16204_XACCL_NULL, ADIS16204_X_PEAK_OUT },
+ [ADIS16204_SCAN_ACC_Y] = { ADIS16204_YACCL_NULL, ADIS16204_Y_PEAK_OUT },
+ [ADIS16204_SCAN_ACC_XY] = { 0, ADIS16204_XY_PEAK_OUT },
+};
+
+static int adis16204_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+ int addrind;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16204_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ *val = 1;
+ *val2 = 220000; /* 1.22 mV */
+ } else {
+ *val = 0;
+ *val2 = 610000; /* 0.61 mV */
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470; /* 0.47 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = 0;
+ switch (chan->channel2) {
+ case IIO_MOD_X:
+ case IIO_MOD_ROOT_SUM_SQUARED_X_Y:
+ *val2 = IIO_G_TO_M_S_2(17125); /* 17.125 mg */
+ break;
+ case IIO_MOD_Y:
+ case IIO_MOD_Z:
+ *val2 = IIO_G_TO_M_S_2(8407); /* 8.407 mg */
+ break;
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = 25000 / -470 - 1278; /* 25 C = 1278 */
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ case IIO_CHAN_INFO_PEAK:
+ if (mask == IIO_CHAN_INFO_CALIBBIAS) {
+ bits = 12;
+ addrind = 0;
+ } else { /* PEAK_SEPARATE */
+ bits = 14;
+ addrind = 1;
+ }
+ mutex_lock(&indio_dev->mlock);
+ addr = adis16204_addresses[chan->scan_index][addrind];
+ ret = adis_read_reg_16(st, 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)
+{
+ struct adis *st = iio_priv(indio_dev);
+ 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->scan_index][1];
+ return adis_write_reg_16(st, addr, val16);
+ }
+ return -EINVAL;
+}
+
+static const struct iio_chan_spec adis16204_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16204_SUPPLY_OUT, ADIS16204_SCAN_SUPPLY, 12),
+ ADIS_AUX_ADC_CHAN(ADIS16204_AUX_ADC, ADIS16204_SCAN_AUX_ADC, 12),
+ ADIS_TEMP_CHAN(ADIS16204_TEMP_OUT, ADIS16204_SCAN_TEMP, 12),
+ ADIS_ACCEL_CHAN(X, ADIS16204_XACCL_OUT, ADIS16204_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), 14),
+ ADIS_ACCEL_CHAN(Y, ADIS16204_YACCL_OUT, ADIS16204_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), 14),
+ ADIS_ACCEL_CHAN(ROOT_SUM_SQUARED_X_Y, ADIS16204_XY_RSS_OUT,
+ ADIS16204_SCAN_ACC_XY, BIT(IIO_CHAN_INFO_PEAK), 14),
+ IIO_CHAN_SOFT_TIMESTAMP(5),
+};
+
+static const struct iio_info adis16204_info = {
+ .read_raw = &adis16204_read_raw,
+ .write_raw = &adis16204_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const adis16204_status_error_msgs[] = {
+ [ADIS16204_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
+ [ADIS16204_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16204_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16204_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16204_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V",
+};
+
+static const struct adis_data adis16204_data = {
+ .read_delay = 20,
+ .msc_ctrl_reg = ADIS16204_MSC_CTRL,
+ .glob_cmd_reg = ADIS16204_GLOB_CMD,
+ .diag_stat_reg = ADIS16204_DIAG_STAT,
+
+ .self_test_mask = ADIS16204_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16204_STARTUP_DELAY,
+
+ .status_error_msgs = adis16204_status_error_msgs,
+ .status_error_mask = BIT(ADIS16204_DIAG_STAT_SELFTEST_FAIL_BIT) |
+ BIT(ADIS16204_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16204_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16204_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16204_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int adis16204_probe(struct spi_device *spi)
+{
+ int ret;
+ struct adis *st;
+ struct iio_dev *indio_dev;
+
+ /* setup the industrialio driver allocated elements */
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = adis_init(st, indio_dev, spi, &adis16204_data);
+ if (ret)
+ return ret;
+
+ ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(st);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+
+ return 0;
+
+error_cleanup_buffer_trigger:
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+ return ret;
+}
+
+static int adis16204_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct adis *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver adis16204_driver = {
+ .driver = {
+ .name = "adis16204",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16204_probe,
+ .remove = 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/adis16209.h b/drivers/staging/iio/accel/adis16209.h
new file mode 100644
index 00000000000..ad3945a0629
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16209.h
@@ -0,0 +1,105 @@
+#ifndef SPI_ADIS16209_H_
+#define SPI_ADIS16209_H_
+
+#define ADIS16209_STARTUP_DELAY 220 /* ms */
+
+/* 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
+
+/* 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_BIT 5
+/* SPI communications failure */
+#define ADIS16209_DIAG_STAT_SPI_FAIL_BIT 3
+/* Flash update failure */
+#define ADIS16209_DIAG_STAT_FLASH_UPT_BIT 2
+/* Power supply above 3.625 V */
+#define ADIS16209_DIAG_STAT_POWER_HIGH_BIT 1
+/* Power supply below 3.15 V */
+#define ADIS16209_DIAG_STAT_POWER_LOW_BIT 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_ERROR_ACTIVE (1<<14)
+
+#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
+
+#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 00000000000..4492e51d888
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16209_core.c
@@ -0,0 +1,246 @@
+/*
+ * ADIS16209 Programmable Digital Vibration Sensor driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include "adis16209.h"
+
+static const u8 adis16209_addresses[8][1] = {
+ [ADIS16209_SCAN_SUPPLY] = { },
+ [ADIS16209_SCAN_AUX_ADC] = { },
+ [ADIS16209_SCAN_ACC_X] = { ADIS16209_XACCL_NULL },
+ [ADIS16209_SCAN_ACC_Y] = { ADIS16209_YACCL_NULL },
+ [ADIS16209_SCAN_INCLI_X] = { ADIS16209_XINCL_NULL },
+ [ADIS16209_SCAN_INCLI_Y] = { ADIS16209_YINCL_NULL },
+ [ADIS16209_SCAN_ROT] = { },
+ [ADIS16209_SCAN_TEMP] = { },
+};
+
+static int adis16209_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ 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->scan_index][0];
+ return adis_write_reg_16(st, 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)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16209_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ *val = 0;
+ if (chan->channel == 0)
+ *val2 = 305180; /* 0.30518 mV */
+ else
+ *val2 = 610500; /* 0.6105 mV */
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = -470; /* -0.47 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(244140); /* 0.244140 mg */
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_INCLI:
+ case IIO_ROT:
+ *val = 0;
+ *val2 = 25000; /* 0.025 degree */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = 25000 / -470 - 0x4FE; /* 25 C = 0x4FE */
+ 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->scan_index][0];
+ ret = adis_read_reg_16(st, 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 const struct iio_chan_spec adis16209_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16209_SUPPLY_OUT, ADIS16209_SCAN_SUPPLY, 14),
+ ADIS_TEMP_CHAN(ADIS16209_TEMP_OUT, ADIS16209_SCAN_TEMP, 12),
+ ADIS_ACCEL_CHAN(X, ADIS16209_XACCL_OUT, ADIS16209_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ ADIS_ACCEL_CHAN(Y, ADIS16209_YACCL_OUT, ADIS16209_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS), 14),
+ ADIS_AUX_ADC_CHAN(ADIS16209_AUX_ADC, ADIS16209_SCAN_AUX_ADC, 12),
+ ADIS_INCLI_CHAN(X, ADIS16209_XINCL_OUT, ADIS16209_SCAN_INCLI_X, 0, 14),
+ ADIS_INCLI_CHAN(Y, ADIS16209_YINCL_OUT, ADIS16209_SCAN_INCLI_Y, 0, 14),
+ ADIS_ROT_CHAN(X, ADIS16209_ROT_OUT, ADIS16209_SCAN_ROT, 0, 14),
+ IIO_CHAN_SOFT_TIMESTAMP(8)
+};
+
+static const struct iio_info adis16209_info = {
+ .read_raw = &adis16209_read_raw,
+ .write_raw = &adis16209_write_raw,
+ .update_scan_mode = adis_update_scan_mode,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const adis16209_status_error_msgs[] = {
+ [ADIS16209_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure",
+ [ADIS16209_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16209_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16209_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16209_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V",
+};
+
+static const struct adis_data adis16209_data = {
+ .read_delay = 30,
+ .msc_ctrl_reg = ADIS16209_MSC_CTRL,
+ .glob_cmd_reg = ADIS16209_GLOB_CMD,
+ .diag_stat_reg = ADIS16209_DIAG_STAT,
+
+ .self_test_mask = ADIS16209_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16209_STARTUP_DELAY,
+
+ .status_error_msgs = adis16209_status_error_msgs,
+ .status_error_mask = BIT(ADIS16209_DIAG_STAT_SELFTEST_FAIL_BIT) |
+ BIT(ADIS16209_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16209_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16209_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16209_DIAG_STAT_POWER_LOW_BIT),
+};
+
+
+static int adis16209_probe(struct spi_device *spi)
+{
+ int ret;
+ struct adis *st;
+ struct iio_dev *indio_dev;
+
+ /* setup the industrialio driver allocated elements */
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = adis_init(st, indio_dev, spi, &adis16209_data);
+ if (ret)
+ return ret;
+ ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(st);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+
+ return 0;
+
+error_cleanup_buffer_trigger:
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+ return ret;
+}
+
+static int adis16209_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct adis *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver adis16209_driver = {
+ .driver = {
+ .name = "adis16209",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16209_probe,
+ .remove = 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/adis16220.h b/drivers/staging/iio/accel/adis16220.h
new file mode 100644
index 00000000000..a894ad7fb26
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16220.h
@@ -0,0 +1,140 @@
+#ifndef SPI_ADIS16220_H_
+#define SPI_ADIS16220_H_
+
+#include <linux/iio/imu/adis.h>
+
+#define ADIS16220_STARTUP_DELAY 220 /* ms */
+
+/* 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_BIT 4
+/* SPI communications failure */
+#define ADIS16220_DIAG_STAT_SPI_FAIL_BIT 3
+/* Flash update failure */
+#define ADIS16220_DIAG_STAT_FLASH_UPT_BIT 2
+/* Power supply above 3.625 V */
+#define ADIS16220_DIAG_STAT_POWER_HIGH_BIT 1
+/* Power supply below 3.15 V */
+#define ADIS16220_DIAG_STAT_POWER_LOW_BIT 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
+ * @adis: adis device
+ * @tx: transmit buffer
+ * @rx: receive buffer
+ * @buf_lock: mutex to protect tx and rx
+ **/
+struct adis16220_state {
+ struct adis adis;
+
+ 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 00000000000..6f38ca95f9b
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16220_core.c
@@ -0,0 +1,500 @@
+/*
+ * ADIS16220 Programmable Digital Vibration Sensor driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include "adis16220.h"
+
+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_to_iio_dev(dev);
+ struct adis16220_state *st = iio_priv(indio_dev);
+ ssize_t ret;
+ s16 val = 0;
+
+ /* Take the iio_dev status lock */
+ mutex_lock(&indio_dev->mlock);
+ ret = adis_read_reg_16(&st->adis, 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_to_iio_dev(dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ struct adis16220_state *st = iio_priv(indio_dev);
+ int ret;
+ u16 val;
+
+ ret = kstrtou16(buf, 10, &val);
+ if (ret)
+ goto error_ret;
+ ret = adis_write_reg_16(&st->adis, this_attr->address, val);
+
+error_ret:
+ return ret ? ret : len;
+}
+
+static int adis16220_capture(struct iio_dev *indio_dev)
+{
+ struct adis16220_state *st = iio_priv(indio_dev);
+ int ret;
+
+ /* initiates a manual data capture */
+ ret = adis_write_reg_16(&st->adis, ADIS16220_GLOB_CMD, 0xBF08);
+ if (ret)
+ dev_err(&indio_dev->dev, "problem beginning capture");
+
+ msleep(10); /* delay for capture to finish */
+
+ return ret;
+}
+
+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_to_iio_dev(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 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_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 = adis_write_reg_16(&st->adis,
+ 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] = ADIS_READ_REG(addr);
+ st->tx[i + 1] = 0;
+ }
+ xfers[1].len = count;
+
+ ret = spi_sync_transfer(st->adis.spi, xfers, ARRAY_SIZE(xfers));
+ 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 iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj));
+
+ 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 iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj));
+
+ 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 iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj));
+
+ 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,
+};
+
+#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)
+{
+ struct adis16220_state *st = iio_priv(indio_dev);
+ const struct adis16220_address_spec *addr;
+ int ret = -EINVAL;
+ int addrind = 0;
+ u16 uval;
+ s16 sval;
+ u8 bits;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ addrind = 0;
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type == IIO_TEMP) {
+ *val = 25000 / -470 - 1278; /* 25 C = 1278 */
+ return IIO_VAL_INT;
+ }
+ addrind = 1;
+ break;
+ case IIO_CHAN_INFO_PEAK:
+ addrind = 2;
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = -470; /* -0.47 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val2 = IIO_G_TO_M_S_2(19073); /* 19.073 g */
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ *val = 1;
+ *val2 = 220700; /* 1.2207 mV */
+ } else {
+ /* Should really be dependent on VDD */
+ *val2 = 305180; /* 305.18 uV */
+ }
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ addr = &adis16220_addresses[chan->address][addrind];
+ if (addr->sign) {
+ ret = adis_read_reg_16(&st->adis, addr->addr, &sval);
+ if (ret)
+ return ret;
+ bits = addr->bits;
+ sval &= (1 << bits) - 1;
+ sval = (s16)(sval << (16 - bits)) >> (16 - bits);
+ *val = sval;
+ return IIO_VAL_INT;
+ } else {
+ ret = adis_read_reg_16(&st->adis, addr->addr, &uval);
+ if (ret)
+ return ret;
+ bits = addr->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_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = in_supply,
+ }, {
+ .type = IIO_ACCEL,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_PEAK),
+ .address = accel,
+ }, {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = temp,
+ }, {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = in_1,
+ }, {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 2,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = in_2,
+ }
+};
+
+static struct attribute *adis16220_attributes[] = {
+ &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 const char * const adis16220_status_error_msgs[] = {
+ [ADIS16220_DIAG_STAT_VIOLATION_BIT] = "Capture period violation/interruption",
+ [ADIS16220_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16220_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16220_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16220_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V",
+};
+
+static const struct adis_data adis16220_data = {
+ .read_delay = 35,
+ .write_delay = 35,
+ .msc_ctrl_reg = ADIS16220_MSC_CTRL,
+ .glob_cmd_reg = ADIS16220_GLOB_CMD,
+ .diag_stat_reg = ADIS16220_DIAG_STAT,
+
+ .self_test_mask = ADIS16220_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16220_STARTUP_DELAY,
+
+ .status_error_msgs = adis16220_status_error_msgs,
+ .status_error_mask = BIT(ADIS16220_DIAG_STAT_VIOLATION_BIT) |
+ BIT(ADIS16220_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16220_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16220_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16220_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int 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 = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = devm_iio_device_register(&spi->dev, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &accel_bin);
+ if (ret)
+ return ret;
+
+ 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;
+
+ ret = adis_init(&st->adis, indio_dev, spi, &adis16220_data);
+ if (ret)
+ goto error_rm_adc2_bin;
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(&st->adis);
+ 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);
+ return ret;
+}
+
+static int adis16220_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+
+ 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);
+
+ return 0;
+}
+
+static struct spi_driver adis16220_driver = {
+ .driver = {
+ .name = "adis16220",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16220_probe,
+ .remove = 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 00000000000..d442d49f51f
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16240.h
@@ -0,0 +1,129 @@
+#ifndef SPI_ADIS16240_H_
+#define SPI_ADIS16240_H_
+
+#define ADIS16240_STARTUP_DELAY 220 /* ms */
+
+/* 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
+
+/* 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_BIT 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_BIT 3
+/* Flash update failure */
+#define ADIS16240_DIAG_STAT_FLASH_UPT_BIT 2
+/* Power supply above 3.625 V */
+#define ADIS16240_DIAG_STAT_POWER_HIGH_BIT 1
+ /* Power supply below 3.15 V */
+#define ADIS16240_DIAG_STAT_POWER_LOW_BIT 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)
+
+/* At the moment triggers are only used for ring buffer
+ * filling. This may change!
+ */
+
+#define ADIS16240_SCAN_ACC_X 0
+#define ADIS16240_SCAN_ACC_Y 1
+#define ADIS16240_SCAN_ACC_Z 2
+#define ADIS16240_SCAN_SUPPLY 3
+#define ADIS16240_SCAN_AUX_ADC 4
+#define ADIS16240_SCAN_TEMP 5
+
+#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 00000000000..3a303a03d02
--- /dev/null
+++ b/drivers/staging/iio/accel/adis16240_core.c
@@ -0,0 +1,298 @@
+/*
+ * ADIS16240 Programmable Impact Sensor and Recorder driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/imu/adis.h>
+
+#include "adis16240.h"
+
+static ssize_t adis16240_spi_read_signed(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ unsigned bits)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ s16 val = 0;
+ unsigned shift = 16 - bits;
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+
+ ret = adis_read_reg_16(st,
+ this_attr->address, (u16 *)&val);
+ if (ret)
+ return ret;
+
+ if (val & ADIS16240_ERROR_ACTIVE)
+ adis_check_status(st);
+
+ 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_to_iio_dev(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 IIO_DEVICE_ATTR(in_accel_xyz_squared_peak_raw, S_IRUGO,
+ adis16240_read_12bit_signed, NULL,
+ ADIS16240_XYZPEAK_OUT);
+
+static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("4096");
+
+static const u8 adis16240_addresses[][2] = {
+ [ADIS16240_SCAN_ACC_X] = { ADIS16240_XACCL_OFF, ADIS16240_XPEAK_OUT },
+ [ADIS16240_SCAN_ACC_Y] = { ADIS16240_YACCL_OFF, ADIS16240_YPEAK_OUT },
+ [ADIS16240_SCAN_ACC_Z] = { ADIS16240_ZACCL_OFF, ADIS16240_ZPEAK_OUT },
+};
+
+static int adis16240_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2,
+ long mask)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int ret;
+ int bits;
+ u8 addr;
+ s16 val16;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return adis_single_conversion(indio_dev, chan,
+ ADIS16240_ERROR_ACTIVE, val);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chan->channel == 0) {
+ *val = 4;
+ *val2 = 880000; /* 4.88 mV */
+ return IIO_VAL_INT_PLUS_MICRO;
+ } else {
+ return -EINVAL;
+ }
+ case IIO_TEMP:
+ *val = 244; /* 0.244 C */
+ *val2 = 0;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_ACCEL:
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_PEAK_SCALE:
+ *val = 0;
+ *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = 25000 / 244 - 0x133; /* 25 C = 0x133 */
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ bits = 10;
+ mutex_lock(&indio_dev->mlock);
+ addr = adis16240_addresses[chan->scan_index][0];
+ ret = adis_read_reg_16(st, 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->scan_index][1];
+ ret = adis_read_reg_16(st, 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)
+{
+ struct adis *st = iio_priv(indio_dev);
+ int bits = 10;
+ s16 val16;
+ u8 addr;
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBBIAS:
+ val16 = val & ((1 << bits) - 1);
+ addr = adis16240_addresses[chan->scan_index][0];
+ return adis_write_reg_16(st, addr, val16);
+ }
+ return -EINVAL;
+}
+
+static const struct iio_chan_spec adis16240_channels[] = {
+ ADIS_SUPPLY_CHAN(ADIS16240_SUPPLY_OUT, ADIS16240_SCAN_SUPPLY, 10),
+ ADIS_AUX_ADC_CHAN(ADIS16240_AUX_ADC, ADIS16240_SCAN_AUX_ADC, 10),
+ ADIS_ACCEL_CHAN(X, ADIS16240_XACCL_OUT, ADIS16240_SCAN_ACC_X,
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), 10),
+ ADIS_ACCEL_CHAN(Y, ADIS16240_YACCL_OUT, ADIS16240_SCAN_ACC_Y,
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), 10),
+ ADIS_ACCEL_CHAN(Z, ADIS16240_ZACCL_OUT, ADIS16240_SCAN_ACC_Z,
+ BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), 10),
+ ADIS_TEMP_CHAN(ADIS16240_TEMP_OUT, ADIS16240_SCAN_TEMP, 10),
+ 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,
+ 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,
+ .update_scan_mode = adis_update_scan_mode,
+ .driver_module = THIS_MODULE,
+};
+
+static const char * const adis16240_status_error_msgs[] = {
+ [ADIS16240_DIAG_STAT_PWRON_FAIL_BIT] = "Power on, self-test failed",
+ [ADIS16240_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure",
+ [ADIS16240_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed",
+ [ADIS16240_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V",
+ [ADIS16240_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.225V",
+};
+
+static const struct adis_data adis16240_data = {
+ .write_delay = 35,
+ .read_delay = 35,
+ .msc_ctrl_reg = ADIS16240_MSC_CTRL,
+ .glob_cmd_reg = ADIS16240_GLOB_CMD,
+ .diag_stat_reg = ADIS16240_DIAG_STAT,
+
+ .self_test_mask = ADIS16240_MSC_CTRL_SELF_TEST_EN,
+ .startup_delay = ADIS16240_STARTUP_DELAY,
+
+ .status_error_msgs = adis16240_status_error_msgs,
+ .status_error_mask = BIT(ADIS16240_DIAG_STAT_PWRON_FAIL_BIT) |
+ BIT(ADIS16240_DIAG_STAT_SPI_FAIL_BIT) |
+ BIT(ADIS16240_DIAG_STAT_FLASH_UPT_BIT) |
+ BIT(ADIS16240_DIAG_STAT_POWER_HIGH_BIT) |
+ BIT(ADIS16240_DIAG_STAT_POWER_LOW_BIT),
+};
+
+static int adis16240_probe(struct spi_device *spi)
+{
+ int ret;
+ struct adis *st;
+ struct iio_dev *indio_dev;
+
+ /* setup the industrialio driver allocated elements */
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ 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 = adis_init(st, indio_dev, spi, &adis16240_data);
+ if (ret)
+ return ret;
+ ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL);
+ if (ret)
+ return ret;
+
+ /* Get the device into a sane initial state */
+ ret = adis_initial_startup(st);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_cleanup_buffer_trigger;
+ return 0;
+
+error_cleanup_buffer_trigger:
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+ return ret;
+}
+
+static int adis16240_remove(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct adis *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ adis_cleanup_buffer_and_trigger(st, indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver adis16240_driver = {
+ .driver = {
+ .name = "adis16240",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16240_probe,
+ .remove = 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/lis3l02dq.h b/drivers/staging/iio/accel/lis3l02dq.h
new file mode 100644
index 00000000000..0a8ea827086
--- /dev/null
+++ b/drivers/staging/iio/accel/lis3l02dq.h
@@ -0,0 +1,210 @@
+/*
+ * LISL02DQ.h -- support STMicroelectronics LISD02DQ
+ * 3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org>
+ *
+ * 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 of 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
+ * @buf_lock: mutex to protect tx and rx
+ * @tx: transmit buffer
+ * @rx: receive buffer
+ **/
+struct lis3l02dq_state {
+ struct spi_device *us;
+ struct iio_trigger *trig;
+ struct mutex buf_lock;
+ int gpio;
+ 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);
+
+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 00000000000..898653c0927
--- /dev/null
+++ b/drivers/staging/iio/accel/lis3l02dq_core.c
@@ -0,0 +1,822 @@
+/*
+ * lis3l02dq.c support STMicroelectronics LISD02DQ
+ * 3d 2g Linear Accelerometers via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org>
+ *
+ * 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 <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/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);
+ 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;
+
+ ret = spi_sync_transfer(st->us, &xfer, 1);
+ *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 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;
+
+ ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers));
+ 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);
+ 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;
+
+ ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers));
+ 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ int ret;
+
+ ret = lis3l02dq_read_reg_s16(indio_dev, LIS3L02DQ_REG_THS_L_ADDR, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+}
+
+static int lis3l02dq_write_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ 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 IIO_CHAN_INFO_RAW:
+ /* 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);
+ if (ret < 0)
+ goto error_ret;
+ 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_to_iio_dev(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_to_iio_dev(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;
+}
+
+static const struct iio_event_spec lis3l02dq_event[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
+ }
+};
+
+#define LIS3L02DQ_CHAN(index, mod) \
+ { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | \
+ BIT(IIO_CHAN_INFO_CALIBBIAS), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ }, \
+ .event_spec = lis3l02dq_event, \
+ .num_event_specs = ARRAY_SIZE(lis3l02dq_event), \
+ }
+
+static const struct iio_chan_spec lis3l02dq_channels[] = {
+ LIS3L02DQ_CHAN(0, IIO_MOD_X),
+ LIS3L02DQ_CHAN(1, IIO_MOD_Y),
+ LIS3L02DQ_CHAN(2, IIO_MOD_Z),
+ IIO_CHAN_SOFT_TIMESTAMP(3)
+};
+
+
+static int lis3l02dq_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+
+ u8 val;
+ int ret;
+ u8 mask = (1 << (chan->channel2*2 + (dir == 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ int ret = 0;
+ u8 val, control;
+ u8 currentlyset;
+ bool changed = false;
+ u8 mask = (1 << (chan->channel2*2 + (dir == 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 lis3l02dq_probe(struct spi_device *spi)
+{
+ int ret;
+ struct lis3l02dq_state *st;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ st = iio_priv(indio_dev);
+ /* this is only used for removal purposes */
+ spi_set_drvdata(spi, indio_dev);
+
+ st->us = spi;
+ st->gpio = of_get_gpio(spi->dev.of_node, 0);
+ 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)
+ return ret;
+
+ ret = iio_buffer_register(indio_dev,
+ lis3l02dq_channels,
+ ARRAY_SIZE(lis3l02dq_channels));
+ if (ret) {
+ dev_err(&spi->dev, "failed to initialize the buffer\n");
+ goto error_unreg_buffer_funcs;
+ }
+
+ if (spi->irq) {
+ 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 (spi->irq)
+ lis3l02dq_remove_trigger(indio_dev);
+error_free_interrupt:
+ if (spi->irq)
+ free_irq(st->us->irq, indio_dev);
+error_uninitialize_buffer:
+ iio_buffer_unregister(indio_dev);
+error_unreg_buffer_funcs:
+ lis3l02dq_unconfigure_buffer(indio_dev);
+ 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)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(spi);
+ struct lis3l02dq_state *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+
+ lis3l02dq_disable_all_events(indio_dev);
+ lis3l02dq_stop_device(indio_dev);
+
+ if (spi->irq)
+ free_irq(st->us->irq, indio_dev);
+
+ lis3l02dq_remove_trigger(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ lis3l02dq_unconfigure_buffer(indio_dev);
+
+ return 0;
+}
+
+static struct spi_driver lis3l02dq_driver = {
+ .driver = {
+ .name = "lis3l02dq",
+ .owner = THIS_MODULE,
+ },
+ .probe = lis3l02dq_probe,
+ .remove = lis3l02dq_remove,
+};
+module_spi_driver(lis3l02dq_driver);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
+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 00000000000..79cefe0a516
--- /dev/null
+++ b/drivers/staging/iio/accel/lis3l02dq_ring.c
@@ -0,0 +1,422 @@
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include "lis3l02dq.h"
+
+/**
+ * combine_8_to_16() utility function to munge two 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
+ * @indio_dev: IIO device 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) {
+ kfree(rx_array);
+ 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;
+ int len = 0;
+ char *data;
+
+ data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+ if (data == NULL)
+ goto done;
+
+ if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength))
+ len = lis3l02dq_get_buffer_element(indio_dev, data);
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp);
+
+ kfree(data);
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+/* Caller responsible for locking as necessary. */
+static int
+__lis3l02dq_write_data_ready_config(struct iio_dev *indio_dev, bool state)
+{
+ int ret;
+ u8 valold;
+ bool currentlyset;
+ 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 = iio_trigger_get_drvdata(trig);
+ int ret = 0;
+ u8 t;
+
+ __lis3l02dq_write_data_ready_config(indio_dev, state);
+ if (!state) {
+ /*
+ * 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 reenabling irq for data rdy trigger
+ * @trig: the datardy trigger
+ */
+static int lis3l02dq_trig_try_reen(struct iio_trigger *trig)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ 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(st->gpio))
+ lis3l02dq_read_all(indio_dev, NULL);
+ else
+ break;
+ if (i == 5)
+ pr_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_trigger_alloc("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;
+ iio_trigger_set_drvdata(st->trig, indio_dev);
+ ret = iio_trigger_register(st->trig);
+ if (ret)
+ goto error_free_trig;
+
+ return 0;
+
+error_free_trig:
+ iio_trigger_free(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_trigger_free(st->trig);
+}
+
+void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev)
+{
+ iio_dealloc_pollfunc(indio_dev->pollfunc);
+ iio_kfifo_free(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 = {
+ .postenable = &lis3l02dq_buffer_postenable,
+ .predisable = &lis3l02dq_buffer_predisable,
+};
+
+int lis3l02dq_configure_buffer(struct iio_dev *indio_dev)
+{
+ int ret;
+ struct iio_buffer *buffer;
+
+ buffer = iio_kfifo_allocate(indio_dev);
+ if (!buffer)
+ return -ENOMEM;
+
+ iio_device_attach_buffer(indio_dev, 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:
+ iio_kfifo_free(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 00000000000..b284e5a6cac
--- /dev/null
+++ b/drivers/staging/iio/accel/sca3000.h
@@ -0,0 +1,275 @@
+/*
+ * sca3000.c -- support VTI sca3000 series accelerometers
+ * via SPI
+ *
+ * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org>
+ *
+ * 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
+ * (approx 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.
+ */
+#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
+ * @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 00000000000..ed30e32e60d
--- /dev/null
+++ b/drivers/staging/iio/accel/sca3000_core.c
@@ -0,0 +1,1248 @@
+/*
+ * 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 <jic23@kernel.org>
+ *
+ * See industrialio/accels/sca3000.h for comments.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/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_transfer xfer[2] = {
+ {
+ .len = 1,
+ .tx_buf = st->tx,
+ }, {
+ .len = len,
+ .rx_buf = st->rx,
+ }
+ };
+ st->tx[0] = SCA3000_READ_REG(reg_address_high);
+
+ return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+}
+
+/**
+ * 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_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;
+
+ return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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_to_iio_dev(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_rev() - 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_to_iio_dev(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_to_iio_dev(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_measurement_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_to_iio_dev(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_to_iio_dev(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);
+
+static const struct iio_event_spec sca3000_event = {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE),
+};
+
+#define SCA3000_CHAN(index, mod) \
+ { \
+ .type = IIO_ACCEL, \
+ .modified = 1, \
+ .channel2 = mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+ .address = index, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 11, \
+ .storagebits = 16, \
+ .shift = 5, \
+ }, \
+ .event_spec = &sca3000_event, \
+ .num_event_specs = 1, \
+ }
+
+static const struct iio_chan_spec sca3000_channels[] = {
+ SCA3000_CHAN(0, IIO_MOD_X),
+ SCA3000_CHAN(1, IIO_MOD_Y),
+ SCA3000_CHAN(2, IIO_MOD_Z),
+};
+
+static const struct iio_chan_spec sca3000_channels_with_temp[] = {
+ SCA3000_CHAN(0, IIO_MOD_X),
+ SCA3000_CHAN(1, IIO_MOD_Y),
+ SCA3000_CHAN(2, IIO_MOD_Z),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ },
+};
+
+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 IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ if (chan->type == IIO_ACCEL) {
+ 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);
+ } else {
+ /* get the temperature when available */
+ ret = sca3000_read_data_short(st,
+ SCA3000_REG_ADDR_TEMP_MSB, 2);
+ if (ret < 0) {
+ mutex_unlock(&st->lock);
+ return ret;
+ }
+ *val = ((st->rx[0] & 0x3F) << 3) | ((st->rx[1] & 0xE0) >> 5);
+ }
+ 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;
+ case IIO_CHAN_INFO_OFFSET:
+ *val = -214;
+ *val2 = 600000;
+ 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_to_iio_dev(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_freq() 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_to_iio_dev(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_to_iio_dev(dev);
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret, base_freq = 0;
+ int ctrlval;
+ int val;
+
+ ret = kstrtoint(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_thresh() - query of a threshold
+ **/
+static int sca3000_read_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ int ret, i;
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int num = chan->channel2;
+ 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 IIO_VAL_INT;
+}
+
+/**
+ * sca3000_write_thresh() control of threshold
+ **/
+static int sca3000_write_thresh(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int num = chan->channel2;
+ 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 const struct attribute_group sca3000_attribute_group = {
+ .attrs = sca3000_attributes,
+};
+
+/**
+ * sca3000_event_handler() - handling ring and non ring events
+ *
+ * Ring related interrupt handler. Depending on event, push to
+ * the ring buffer event chrdev or the event one.
+ *
+ * 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret;
+ u8 protect_mask = 0x03;
+ int num = chan->channel2;
+
+ /* 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_to_iio_dev(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_to_iio_dev(dev);
+ struct sca3000_state *st = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+ u8 protect_mask = SCA3000_FREE_FALL_DETECT;
+
+ mutex_lock(&st->lock);
+ ret = kstrtou8(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_write_event_config() 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct sca3000_state *st = iio_priv(indio_dev);
+ int ret, ctrlval;
+ u8 protect_mask = 0x03;
+ int num = chan->channel2;
+
+ 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;
+ /* if 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 int sca3000_probe(struct spi_device *spi)
+{
+ int ret;
+ struct sca3000_state *st;
+ struct iio_dev *indio_dev;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ 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;
+ indio_dev->info = &sca3000_info;
+ if (st->info->temp_output) {
+ indio_dev->channels = sca3000_channels_with_temp;
+ indio_dev->num_channels =
+ ARRAY_SIZE(sca3000_channels_with_temp);
+ } else {
+ 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)
+ return ret;
+
+ 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 | IRQF_ONESHOT,
+ "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);
+ 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);
+
+ /* Must ensure no interrupts can be generated after this! */
+ sca3000_stop_all_interrupts(st);
+ if (spi->irq)
+ free_irq(spi->irq, indio_dev);
+ iio_device_unregister(indio_dev);
+ iio_buffer_unregister(indio_dev);
+ sca3000_unconfigure_ring(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 = sca3000_remove,
+ .id_table = sca3000_id,
+};
+module_spi_driver(sca3000_driver);
+
+MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
+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 00000000000..198710651e0
--- /dev/null
+++ b/drivers/staging/iio/accel/sca3000_ring.c
@@ -0,0 +1,365 @@
+/*
+ * 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 <jic23@kernel.org>
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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_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);
+ ret = spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer));
+ 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_to_iio_dev(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_to_iio_dev(dev);
+ struct sca3000_state *st = iio_priv(indio_dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ u8 val;
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = kstrtou8(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_to_iio_dev(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 void sca3000_ring_release(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,
+ .release = sca3000_ring_release,
+};
+
+int sca3000_configure_ring(struct iio_dev *indio_dev)
+{
+ struct iio_buffer *buffer;
+
+ buffer = sca3000_rb_allocate(indio_dev);
+ if (buffer == NULL)
+ return -ENOMEM;
+ indio_dev->modes |= INDIO_BUFFER_HARDWARE;
+
+ indio_dev->buffer->access = &sca3000_ring_access_funcs;
+
+ iio_device_attach_buffer(indio_dev, buffer);
+
+ return 0;
+}
+
+void sca3000_unconfigure_ring(struct iio_dev *indio_dev)
+{
+ iio_buffer_put(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) {
+ dev_info(&indio_dev->dev, "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 00000000000..b87e382ad76
--- /dev/null
+++ b/drivers/staging/iio/adc/Kconfig
@@ -0,0 +1,117 @@
+#
+# 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 AD7606
+ tristate "Analog Devices AD7606 ADC driver"
+ depends on GPIOLIB
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ 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 AD7780
+ tristate "Analog Devices AD7780 and similar ADCs driver"
+ depends on SPI
+ depends on GPIOLIB
+ select AD_SIGMA_DELTA
+ help
+ Say yes here to build support for Analog Devices AD7170, AD7171,
+ 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 AD7816
+ tristate "Analog Devices AD7816/7/8 temperature sensor and ADC driver"
+ depends on SPI
+ depends on GPIOLIB
+ 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 AD_SIGMA_DELTA
+ 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 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 LPC32XX_ADC
+ tristate "NXP LPC32XX ADC"
+ depends on ARCH_LPC32XX || COMPILE_TEST
+ depends on HAS_IOMEM
+ 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 should either select only one of the two
+ drivers (lpc32xx_adc or lpc32xx_ts) or, in the OpenFirmware case,
+ activate only one via device tree selection. Provides direct access
+ via sysfs.
+
+config MXS_LRADC
+ tristate "Freescale i.MX23/i.MX28 LRADC"
+ depends on ARCH_MXS || COMPILE_TEST
+ depends on INPUT
+ select STMP_DEVICE
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ Say yes here to build support for i.MX23/i.MX28 LRADC convertor
+ built into these chips.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mxs-lradc.
+
+config SPEAR_ADC
+ tristate "ST SPEAr ADC"
+ depends on PLAT_SPEAR || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say yes here to build support for the integrated ADC inside the
+ ST SPEAr SoC. 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 00000000000..afdcd1ff08f
--- /dev/null
+++ b/drivers/staging/iio/adc/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for industrial I/O ADC drivers
+#
+
+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
+
+obj-$(CONFIG_AD7291) += ad7291.o
+obj-$(CONFIG_AD7780) += ad7780.o
+obj-$(CONFIG_AD7816) += ad7816.o
+obj-$(CONFIG_AD7192) += ad7192.o
+obj-$(CONFIG_AD7280) += ad7280a.o
+obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
+obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
+obj-$(CONFIG_SPEAR_ADC) += spear_adc.o
diff --git a/drivers/staging/iio/adc/ad7192.c b/drivers/staging/iio/adc/ad7192.c
new file mode 100644
index 00000000000..83bb44b3815
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7192.c
@@ -0,0 +1,719 @@
+/*
+ * AD7190 AD7192 AD7195 SPI ADC driver
+ *
+ * Copyright 2011-2012 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/adc/ad_sigma_delta.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_SEL_MASK (0x7 << 21) /* Operation Mode Select Mask */
+#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) (((1 << (x)) & 0xFF) << 8) /* Channel select */
+#define AD7192_CONF_CHAN_MASK (0xFF << 8) /* Channel select mask */
+#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 regulator *reg;
+ u16 int_vref_mv;
+ u32 mclk;
+ u32 f_order;
+ u32 mode;
+ u32 conf;
+ u32 scale_avail[8][2];
+ u8 gpocon;
+ u8 devid;
+
+ struct ad_sigma_delta sd;
+};
+
+static struct ad7192_state *ad_sigma_delta_to_ad7192(struct ad_sigma_delta *sd)
+{
+ return container_of(sd, struct ad7192_state, sd);
+}
+
+static int ad7192_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
+{
+ struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd);
+
+ st->conf &= ~AD7192_CONF_CHAN_MASK;
+ st->conf |= AD7192_CONF_CHAN(channel);
+
+ return ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf);
+}
+
+static int ad7192_set_mode(struct ad_sigma_delta *sd,
+ enum ad_sigma_delta_mode mode)
+{
+ struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd);
+
+ st->mode &= ~AD7192_MODE_SEL_MASK;
+ st->mode |= AD7192_MODE_SEL(mode);
+
+ return ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode);
+}
+
+static const struct ad_sigma_delta_info ad7192_sigma_delta_info = {
+ .set_channel = ad7192_set_channel,
+ .set_mode = ad7192_set_mode,
+ .has_registers = true,
+ .addr_shift = 3,
+ .read_mask = BIT(6),
+};
+
+static const struct ad_sd_calib_data ad7192_calib_arr[8] = {
+ {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)
+{
+ return ad_sd_calibrate_all(&st->sd, ad7192_calib_arr,
+ ARRAY_SIZE(ad7192_calib_arr));
+}
+
+static int ad7192_setup(struct ad7192_state *st,
+ const struct ad7192_platform_data *pdata)
+{
+ struct iio_dev *indio_dev = spi_get_drvdata(st->sd.spi);
+ unsigned long long scale_uv;
+ int i, ret, id;
+ u8 ones[6];
+
+ /* reset the serial interface */
+ memset(&ones, 0xFF, 6);
+ ret = spi_write(st->sd.spi, &ones, 6);
+ if (ret < 0)
+ goto out;
+ msleep(1); /* Wait for at least 500us */
+
+ /* write/read test for device presence */
+ ret = ad_sd_read_reg(&st->sd, AD7192_REG_ID, 1, &id);
+ if (ret)
+ goto out;
+
+ id &= AD7192_ID_MASK;
+
+ if (id != st->devid)
+ dev_warn(&st->sd.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 = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode);
+ if (ret)
+ goto out;
+
+ ret = ad_sd_write_reg(&st->sd, 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->sd.spi->dev, "setup failed\n");
+ return ret;
+}
+
+static ssize_t ad7192_read_frequency(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(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_to_iio_dev(dev);
+ struct ad7192_state *st = iio_priv(indio_dev);
+ unsigned long lval;
+ int div, ret;
+
+ ret = kstrtoul(buf, 10, &lval);
+ if (ret)
+ return ret;
+ if (lval == 0)
+ return -EINVAL;
+
+ 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);
+ ad_sd_write_reg(&st->sd, 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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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;
+
+ ad_sd_write_reg(&st->sd, AD7192_REG_GPOCON, 1, st->gpocon);
+ break;
+ case AD7192_REG_MODE:
+ if (val)
+ st->mode |= AD7192_MODE_ACX;
+ else
+ st->mode &= ~AD7192_MODE_ACX;
+
+ ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 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 unsigned int ad7192_get_temp_scale(bool unipolar)
+{
+ return unipolar ? 2815 * 2 : 2815;
+}
+
+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);
+ bool unipolar = !!(st->conf & AD7192_CONF_UNIPOLAR);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ return ad_sigma_delta_single_conversion(indio_dev, chan, val);
+ 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 = 0;
+ *val2 = 1000000000 / ad7192_get_temp_scale(unipolar);
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ if (!unipolar)
+ *val = -(1 << (chan->scan_type.realbits - 1));
+ else
+ *val = 0;
+ /* Kelvin to Celsius */
+ if (chan->type == IIO_TEMP)
+ *val -= 273 * ad7192_get_temp_scale(unipolar);
+ return IIO_VAL_INT;
+ }
+
+ 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]) {
+ ret = 0;
+ tmp = st->conf;
+ st->conf &= ~AD7192_CONF_GAIN(-1);
+ st->conf |= AD7192_CONF_GAIN(i);
+ if (tmp == st->conf)
+ break;
+ ad_sd_write_reg(&st->sd, AD7192_REG_CONF,
+ 3, st->conf);
+ ad7192_calibrate_all(st);
+ break;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&indio_dev->mlock);
+
+ return ret;
+}
+
+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 = ad_sd_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 = ad_sd_validate_trigger,
+ .driver_module = THIS_MODULE,
+};
+
+static const struct iio_chan_spec ad7192_channels[] = {
+ AD_SD_DIFF_CHANNEL(0, 1, 2, AD7192_CH_AIN1P_AIN2M, 24, 32, 0),
+ AD_SD_DIFF_CHANNEL(1, 3, 4, AD7192_CH_AIN3P_AIN4M, 24, 32, 0),
+ AD_SD_TEMP_CHANNEL(2, AD7192_CH_TEMP, 24, 32, 0),
+ AD_SD_SHORTED_CHANNEL(3, 2, AD7192_CH_AIN2P_AIN2M, 24, 32, 0),
+ AD_SD_CHANNEL(4, 1, AD7192_CH_AIN1, 24, 32, 0),
+ AD_SD_CHANNEL(5, 2, AD7192_CH_AIN2, 24, 32, 0),
+ AD_SD_CHANNEL(6, 3, AD7192_CH_AIN3, 24, 32, 0),
+ AD_SD_CHANNEL(7, 4, AD7192_CH_AIN4, 24, 32, 0),
+ IIO_CHAN_SOFT_TIMESTAMP(8),
+};
+
+static int ad7192_probe(struct spi_device *spi)
+{
+ const struct ad7192_platform_data *pdata = spi->dev.platform_data;
+ struct ad7192_state *st;
+ struct iio_dev *indio_dev;
+ int ret , 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 = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ st->reg = devm_regulator_get(&spi->dev, "vcc");
+ if (!IS_ERR(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ return ret;
+
+ voltage_uv = regulator_get_voltage(st->reg);
+ }
+
+ 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->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);
+ if (st->devid == ID_AD7195)
+ indio_dev->info = &ad7195_info;
+ else
+ indio_dev->info = &ad7192_info;
+
+ ad_sd_init(&st->sd, indio_dev, spi, &ad7192_sigma_delta_info);
+
+ ret = ad_sd_setup_buffer_and_trigger(indio_dev);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad7192_setup(st, pdata);
+ if (ret)
+ goto error_remove_trigger;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto error_remove_trigger;
+ return 0;
+
+error_remove_trigger:
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+error_disable_reg:
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ 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);
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+
+ if (!IS_ERR(st->reg))
+ regulator_disable(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 = ad7192_remove,
+ .id_table = ad7192_id,
+};
+module_spi_driver(ad7192_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+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 00000000000..a0a5b61a41f
--- /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 00000000000..d215edf66af
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7280a.c
@@ -0,0 +1,984 @@
+/*
+ * AD7280A Lithium Ion Battery Monitoring System
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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];
+
+ __be32 buf[2] ____cacheline_aligned;
+};
+
+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 ad7280_state *st, unsigned *val)
+{
+ int ret;
+ struct spi_transfer t = {
+ .tx_buf = &st->buf[0],
+ .rx_buf = &st->buf[1],
+ .len = 4,
+ };
+
+ st->buf[0] = cpu_to_be32(AD7280A_READ_TXVAL);
+
+ ret = spi_sync_transfer(st->spi, &t, 1);
+ if (ret)
+ return ret;
+
+ *val = be32_to_cpu(st->buf[1]);
+
+ 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;
+ st->buf[0] = cpu_to_be32(reg);
+
+ return spi_write(st->spi, &st->buf[0], 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, &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, &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, &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, &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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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, "%u\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_to_iio_dev(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_separate =
+ BIT(IIO_CHAN_INFO_RAW);
+ st->channels[cnt].info_mask_shared_by_type =
+ BIT(IIO_CHAN_INFO_SCALE);
+ 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_separate = BIT(IIO_CHAN_INFO_RAW);
+ st->channels[cnt].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+ 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_to_iio_dev(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, "%u\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_to_iio_dev(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 = kstrtol(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);
+ int ret;
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ 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)
+ *val = 4000;
+ else
+ *val = 5000;
+
+ *val2 = AD7280A_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+ 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 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;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, 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)
+ return ret;
+
+ 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)
+ return ret;
+
+ 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);
+
+ return ret;
+}
+
+static int 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);
+
+ 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 = ad7280_remove,
+ .id_table = ad7280_id,
+};
+module_spi_driver(ad7280_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+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 00000000000..20400b0045e
--- /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 00000000000..7194bd13876
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7291.c
@@ -0,0 +1,580 @@
+/*
+ * 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 <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+
+#include "ad7291.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_DATA_HIGH(x) ((x) * 3 + 0x4)
+#define AD7291_DATA_LOW(x) ((x) * 3 + 0x5)
+#define AD7291_HYST(x) ((x) * 3 + 0x6)
+#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 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_swapped(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "I2C read error\n");
+ return ret;
+ }
+
+ *data = ret;
+
+ return 0;
+}
+
+static int ad7291_i2c_write(struct ad7291_chip_info *chip, u8 reg, u16 data)
+{
+ return i2c_smbus_write_word_swapped(chip->client, reg, data);
+}
+
+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 unsigned int ad7291_threshold_reg(const struct iio_chan_spec *chan,
+ enum iio_event_direction dir, enum iio_event_info info)
+{
+ unsigned int offset;
+
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ offset = chan->channel;
+ break;
+ case IIO_TEMP:
+ offset = 8;
+ break;
+ default:
+ return 0;
+ }
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (dir == IIO_EV_DIR_FALLING)
+ return AD7291_DATA_HIGH(offset);
+ else
+ return AD7291_DATA_LOW(offset);
+ case IIO_EV_INFO_HYSTERESIS:
+ return AD7291_HYST(offset);
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int ad7291_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct ad7291_chip_info *chip = iio_priv(indio_dev);
+ int ret;
+ u16 uval;
+
+ ret = ad7291_i2c_read(chip, ad7291_threshold_reg(chan, dir, info),
+ &uval);
+ if (ret < 0)
+ return ret;
+
+ if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE)
+ *val = uval & AD7291_VALUE_MASK;
+
+ else
+ *val = sign_extend32(uval, 11);
+
+ return IIO_VAL_INT;
+}
+
+static int ad7291_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct ad7291_chip_info *chip = iio_priv(indio_dev);
+
+ if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE) {
+ if (val > AD7291_VALUE_MASK || val < 0)
+ return -EINVAL;
+ } else {
+ if (val > 2047 || val < -2048)
+ return -EINVAL;
+ }
+
+ return ad7291_i2c_write(chip, ad7291_threshold_reg(chan, dir, info),
+ val);
+}
+
+static int ad7291_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ 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 (chan->type) {
+ case IIO_VOLTAGE:
+ if (chip->c_mask & (1 << (15 - chan->channel)))
+ 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ int ret = 0;
+ struct ad7291_chip_info *chip = iio_priv(indio_dev);
+ unsigned int mask;
+ 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.
+ */
+
+ mask = BIT(15 - chan->channel);
+
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if ((!state) && (chip->c_mask & mask))
+ chip->c_mask &= ~mask;
+ else if (state && (!(chip->c_mask & mask)))
+ chip->c_mask |= mask;
+ 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);
+ u16 regval;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ 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_swapped(chip->client,
+ AD7291_VOLTAGE);
+ if (ret < 0) {
+ mutex_unlock(&chip->state_lock);
+ return ret;
+ }
+ *val = 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_swapped(chip->client,
+ AD7291_T_SENSE);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 11);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ ret = i2c_smbus_read_word_swapped(chip->client,
+ AD7291_T_AVERAGE);
+ if (ret < 0)
+ return ret;
+ *val = sign_extend32(ret, 11);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ if (chip->reg) {
+ int vref;
+ vref = regulator_get_voltage(chip->reg);
+ if (vref < 0)
+ return vref;
+ *val = vref / 1000;
+ } else {
+ *val = 2500;
+ }
+ *val2 = AD7291_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ 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;
+ }
+}
+
+static const struct iio_event_spec ad7291_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS),
+ },
+};
+
+#define AD7291_VOLTAGE_CHAN(_chan) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .indexed = 1, \
+ .channel = _chan, \
+ .event_spec = ad7291_events, \
+ .num_event_specs = ARRAY_SIZE(ad7291_events), \
+}
+
+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_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .indexed = 1,
+ .channel = 0,
+ .event_spec = ad7291_events,
+ .num_event_specs = ARRAY_SIZE(ad7291_events),
+ }
+};
+
+static const struct iio_info ad7291_info = {
+ .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,
+ .driver_module = THIS_MODULE,
+};
+
+static int ad7291_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ad7291_platform_data *pdata = client->dev.platform_data;
+ struct ad7291_chip_info *chip;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ chip = iio_priv(indio_dev);
+
+ if (pdata && pdata->use_external_ref) {
+ chip->reg = devm_regulator_get(&client->dev, "vref");
+ if (IS_ERR(chip->reg))
+ return PTR_ERR(chip->reg);
+
+ ret = regulator_enable(chip->reg);
+ if (ret)
+ return ret;
+ }
+
+ 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 (pdata && pdata->use_external_ref)
+ chip->command |= AD7291_EXT_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;
+
+ return 0;
+
+error_unreg_irq:
+ if (client->irq)
+ free_irq(client->irq, indio_dev);
+error_disable_reg:
+ if (chip->reg)
+ regulator_disable(chip->reg);
+
+ return ret;
+}
+
+static int 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 (chip->reg)
+ regulator_disable(chip->reg);
+
+ 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 = ad7291_remove,
+ .id_table = ad7291_id,
+};
+module_i2c_driver(ad7291_driver);
+
+MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7291 ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/iio/adc/ad7291.h b/drivers/staging/iio/adc/ad7291.h
new file mode 100644
index 00000000000..bbd89fa5118
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7291.h
@@ -0,0 +1,12 @@
+#ifndef __IIO_AD7291_H__
+#define __IIO_AD7291_H__
+
+/**
+ * struct ad7291_platform_data - AD7291 platform data
+ * @use_external_ref: Whether to use an external or internal reference voltage
+ */
+struct ad7291_platform_data {
+ bool use_external_ref;
+};
+
+#endif
diff --git a/drivers/staging/iio/adc/ad7606.h b/drivers/staging/iio/adc/ad7606.h
new file mode 100644
index 00000000000..ec89d055cf5
--- /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 specific 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 specific information
+ * @name: identification 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;
+ const 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 00000000000..f0f05f195d2
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7606_core.c
@@ -0,0 +1,603 @@
+/*
+ * AD7606 SPI ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ 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:
+ *val = st->range * 2;
+ *val2 = st->chip_info->channels[0].scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ }
+ return -EINVAL;
+}
+
+static ssize_t ad7606_show_range(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(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_to_iio_dev(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+ unsigned long lval;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &lval);
+ if (ret)
+ return ret;
+
+ 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_to_iio_dev(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_to_iio_dev(dev);
+ struct ad7606_state *st = iio_priv(indio_dev);
+ unsigned long lval;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &lval);
+ if (ret)
+ return ret;
+
+ 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_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+ .scan_index = num, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_CPU, \
+ }, \
+ }
+
+static const 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 const 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 const 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)) {
+ 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;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return ERR_PTR(-ENOMEM);
+
+ 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 = devm_regulator_get(dev, "vcc");
+ if (!IS_ERR(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ 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_device_register(indio_dev);
+ if (ret)
+ goto error_unregister_ring;
+
+ return indio_dev;
+error_unregister_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);
+ 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);
+ ad7606_ring_cleanup(indio_dev);
+
+ free_irq(irq, indio_dev);
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ ad7606_free_gpios(st);
+
+ 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 <hennerich@blackfin.uclinux.org>");
+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 00000000000..8a48d18de78
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7606_par.c
@@ -0,0 +1,178 @@
+/*
+ * AD7606 Parallel Interface ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include <linux/iio/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 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 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));
+
+ 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 = 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 <hennerich@blackfin.uclinux.org>");
+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 00000000000..3bf174cb19b
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7606_ring.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2011-2012 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.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);
+ __u8 *buf;
+ int ret;
+
+ buf = kzalloc(indio_dev->scan_bytes, 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;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns());
+done:
+ gpio_set_value(st->pdata->gpio_convst, 0);
+ iio_trigger_notify_done(indio_dev->trig);
+ kfree(buf);
+}
+
+int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev)
+{
+ struct ad7606_state *st = iio_priv(indio_dev);
+
+ INIT_WORK(&st->poll_work, &ad7606_poll_bh_to_ring);
+
+ return iio_triggered_buffer_setup(indio_dev,
+ &ad7606_trigger_handler_th_bh, &ad7606_trigger_handler_th_bh,
+ NULL);
+}
+
+void ad7606_ring_cleanup(struct iio_dev *indio_dev)
+{
+ iio_triggered_buffer_cleanup(indio_dev);
+}
diff --git a/drivers/staging/iio/adc/ad7606_spi.c b/drivers/staging/iio/adc/ad7606_spi.c
new file mode 100644
index 00000000000..6a8ecd73a1a
--- /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 <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/err.h>
+
+#include <linux/iio/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 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 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 = ad7606_spi_remove,
+ .id_table = ad7606_id,
+};
+module_spi_driver(ad7606_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+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 00000000000..273add3ed63
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7780.c
@@ -0,0 +1,276 @@
+/*
+ * AD7170/AD7171 and AD7780/AD7781 SPI ADC driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/adc/ad_sigma_delta.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;
+ unsigned int pattern_mask;
+ unsigned int pattern;
+};
+
+struct ad7780_state {
+ const struct ad7780_chip_info *chip_info;
+ struct regulator *reg;
+ int powerdown_gpio;
+ unsigned int gain;
+ u16 int_vref_mv;
+
+ struct ad_sigma_delta sd;
+};
+
+enum ad7780_supported_device_ids {
+ ID_AD7170,
+ ID_AD7171,
+ ID_AD7780,
+ ID_AD7781,
+};
+
+static struct ad7780_state *ad_sigma_delta_to_ad7780(struct ad_sigma_delta *sd)
+{
+ return container_of(sd, struct ad7780_state, sd);
+}
+
+static int ad7780_set_mode(struct ad_sigma_delta *sigma_delta,
+ enum ad_sigma_delta_mode mode)
+{
+ struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta);
+ unsigned val;
+
+ switch (mode) {
+ case AD_SD_MODE_SINGLE:
+ case AD_SD_MODE_CONTINUOUS:
+ val = 1;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+
+ if (gpio_is_valid(st->powerdown_gpio))
+ gpio_set_value(st->powerdown_gpio, val);
+
+ return 0;
+}
+
+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);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ return ad_sigma_delta_single_conversion(indio_dev, chan, val);
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->int_vref_mv * st->gain;
+ *val2 = chan->scan_type.realbits - 1;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OFFSET:
+ *val -= (1 << (chan->scan_type.realbits - 1));
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int ad7780_postprocess_sample(struct ad_sigma_delta *sigma_delta,
+ unsigned int raw_sample)
+{
+ struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta);
+ const struct ad7780_chip_info *chip_info = st->chip_info;
+
+ if ((raw_sample & AD7780_ERR) ||
+ ((raw_sample & chip_info->pattern_mask) != chip_info->pattern))
+ return -EIO;
+
+ if (raw_sample & AD7780_GAIN)
+ st->gain = 1;
+ else
+ st->gain = 128;
+
+ return 0;
+}
+
+static const struct ad_sigma_delta_info ad7780_sigma_delta_info = {
+ .set_mode = ad7780_set_mode,
+ .postprocess_sample = ad7780_postprocess_sample,
+ .has_registers = false,
+};
+
+#define AD7780_CHANNEL(bits, wordsize) \
+ AD_SD_CHANNEL(1, 0, 0, bits, 32, wordsize - bits)
+
+static const struct ad7780_chip_info ad7780_chip_info_tbl[] = {
+ [ID_AD7170] = {
+ .channel = AD7780_CHANNEL(12, 24),
+ .pattern = 0x5,
+ .pattern_mask = 0x7,
+ },
+ [ID_AD7171] = {
+ .channel = AD7780_CHANNEL(16, 24),
+ .pattern = 0x5,
+ .pattern_mask = 0x7,
+ },
+ [ID_AD7780] = {
+ .channel = AD7780_CHANNEL(24, 32),
+ .pattern = 0x1,
+ .pattern_mask = 0x3,
+ },
+ [ID_AD7781] = {
+ .channel = AD7780_CHANNEL(20, 32),
+ .pattern = 0x1,
+ .pattern_mask = 0x3,
+ },
+};
+
+static const struct iio_info ad7780_info = {
+ .read_raw = &ad7780_read_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static int 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;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->gain = 1;
+
+ ad_sd_init(&st->sd, indio_dev, spi, &ad7780_sigma_delta_info);
+
+ st->reg = devm_regulator_get(&spi->dev, "vcc");
+ if (!IS_ERR(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ return ret;
+
+ voltage_uv = regulator_get_voltage(st->reg);
+ }
+
+ st->chip_info =
+ &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data];
+
+ 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);
+
+ 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;
+
+ if (pdata && gpio_is_valid(pdata->gpio_pdrst)) {
+
+ ret = devm_gpio_request_one(&spi->dev, 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;
+ }
+ st->powerdown_gpio = pdata->gpio_pdrst;
+ } else {
+ st->powerdown_gpio = -1;
+ }
+
+ ret = ad_sd_setup_buffer_and_trigger(indio_dev);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto error_cleanup_buffer_and_trigger;
+
+ return 0;
+
+error_cleanup_buffer_and_trigger:
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+error_disable_reg:
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ 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);
+ ad_sd_cleanup_buffer_and_trigger(indio_dev);
+
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ return 0;
+}
+
+static const struct spi_device_id ad7780_id[] = {
+ {"ad7170", ID_AD7170},
+ {"ad7171", ID_AD7171},
+ {"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 = ad7780_remove,
+ .id_table = ad7780_id,
+};
+module_spi_driver(ad7780_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("Analog Devices AD7780 and similar ADCs");
+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 00000000000..67e511c3d6f
--- /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/ad7816.c b/drivers/staging/iio/adc/ad7816.c
new file mode 100644
index 00000000000..158d770f961
--- /dev/null
+++ b/drivers/staging/iio/adc/ad7816.c
@@ -0,0 +1,447 @@
+/*
+ * AD7816 digital temperature sensor driver supporting AD7816/7/8
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/spi/spi.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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 specific 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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(dev);
+ struct ad7816_chip_info *chip = iio_priv(indio_dev);
+ unsigned long data;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &data);
+ if (ret)
+ return ret;
+
+ 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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(dev);
+ struct ad7816_chip_info *chip = iio_priv(indio_dev);
+ long value;
+ u8 data;
+ int ret;
+
+ ret = kstrtol(buf, 10, &value);
+ if (ret)
+ return ret;
+
+ 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 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 = devm_iio_device_alloc(&spi_dev->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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 = devm_gpio_request(&spi_dev->dev, 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);
+ return ret;
+ }
+ gpio_direction_input(chip->rdwr_pin);
+ ret = devm_gpio_request(&spi_dev->dev, 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);
+ return ret;
+ }
+ gpio_direction_input(chip->convert_pin);
+ ret = devm_gpio_request(&spi_dev->dev, 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);
+ return ret;
+ }
+ 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 = devm_request_threaded_irq(&spi_dev->dev, spi_dev->irq,
+ NULL,
+ &ad7816_event_handler,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ indio_dev->name,
+ indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_iio_device_register(&spi_dev->dev, indio_dev);
+ if (ret)
+ return ret;
+
+ dev_info(&spi_dev->dev, "%s temperature sensor and ADC registered.\n",
+ indio_dev->name);
+
+ 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,
+ .id_table = ad7816_id,
+};
+module_spi_driver(ad7816_driver);
+
+MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7816/7/8 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 00000000000..a876ce75535
--- /dev/null
+++ b/drivers/staging/iio/adc/lpc32xx_adc.c
@@ -0,0 +1,216 @@
+/*
+ * lpc32xx_adc.c - Support for ADC in LPC32XX
+ *
+ * 3-channel, 10-bit ADC
+ *
+ * Copyright (C) 2011, 2012 Roland Stigge <stigge@antcom.de>
+ *
+ * This program is free software; you can redistribute 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 <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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 == IIO_CHAN_INFO_RAW) {
+ 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, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .address = AD_IN * _index, \
+ .scan_index = _index, \
+}
+
+static const 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 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");
+ return -EBUSY;
+ }
+
+ iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
+ if (!iodev)
+ return -ENOMEM;
+
+ info = iio_priv(iodev);
+
+ info->adc_base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!info->adc_base) {
+ dev_err(&pdev->dev, "failed mapping memory\n");
+ return -EBUSY;
+ }
+
+ info->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(info->clk)) {
+ dev_err(&pdev->dev, "failed getting clock\n");
+ return PTR_ERR(info->clk);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(&pdev->dev, "failed getting interrupt resource\n");
+ return -EINVAL;
+ }
+
+ retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0,
+ MOD_NAME, info);
+ if (retval < 0) {
+ dev_err(&pdev->dev, "failed requesting interrupt\n");
+ return retval;
+ }
+
+ 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 = devm_iio_device_register(&pdev->dev, iodev);
+ if (retval)
+ return retval;
+
+ dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id lpc32xx_adc_match[] = {
+ { .compatible = "nxp,lpc3220-adc" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, lpc32xx_adc_match);
+#endif
+
+static struct platform_driver lpc32xx_adc_driver = {
+ .probe = lpc32xx_adc_probe,
+ .driver = {
+ .name = MOD_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(lpc32xx_adc_match),
+ },
+};
+
+module_platform_driver(lpc32xx_adc_driver);
+
+MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
+MODULE_DESCRIPTION("LPC32XX ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/iio/adc/mxs-lradc.c b/drivers/staging/iio/adc/mxs-lradc.c
new file mode 100644
index 00000000000..52d7517b342
--- /dev/null
+++ b/drivers/staging/iio/adc/mxs-lradc.c
@@ -0,0 +1,1685 @@
+/*
+ * Freescale i.MX28 LRADC driver
+ *
+ * Copyright (c) 2012 DENX Software Engineering, GmbH.
+ * Marek Vasut <marex@denx.de>
+ *
+ * This program is free software; you can redistribute 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 <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/stmp_device.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/clk.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define DRIVER_NAME "mxs-lradc"
+
+#define LRADC_MAX_DELAY_CHANS 4
+#define LRADC_MAX_MAPPED_CHANS 8
+#define LRADC_MAX_TOTAL_CHANS 16
+
+#define LRADC_DELAY_TIMER_HZ 2000
+
+/*
+ * Make this runtime configurable if necessary. Currently, if the buffered mode
+ * is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before
+ * triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000)
+ * seconds. The result is that the samples arrive every 500mS.
+ */
+#define LRADC_DELAY_TIMER_PER 200
+#define LRADC_DELAY_TIMER_LOOP 5
+
+/*
+ * Once the pen touches the touchscreen, the touchscreen switches from
+ * IRQ-driven mode to polling mode to prevent interrupt storm. The polling
+ * is realized by worker thread, which is called every 20 or so milliseconds.
+ * This gives the touchscreen enough fluence and does not strain the system
+ * too much.
+ */
+#define LRADC_TS_SAMPLE_DELAY_MS 5
+
+/*
+ * The LRADC reads the following amount of samples from each touchscreen
+ * channel and the driver then computes avarage of these.
+ */
+#define LRADC_TS_SAMPLE_AMOUNT 4
+
+enum mxs_lradc_id {
+ IMX23_LRADC,
+ IMX28_LRADC,
+};
+
+static const char * const mx23_lradc_irq_names[] = {
+ "mxs-lradc-touchscreen",
+ "mxs-lradc-channel0",
+ "mxs-lradc-channel1",
+ "mxs-lradc-channel2",
+ "mxs-lradc-channel3",
+ "mxs-lradc-channel4",
+ "mxs-lradc-channel5",
+ "mxs-lradc-channel6",
+ "mxs-lradc-channel7",
+};
+
+static const char * const mx28_lradc_irq_names[] = {
+ "mxs-lradc-touchscreen",
+ "mxs-lradc-thresh0",
+ "mxs-lradc-thresh1",
+ "mxs-lradc-channel0",
+ "mxs-lradc-channel1",
+ "mxs-lradc-channel2",
+ "mxs-lradc-channel3",
+ "mxs-lradc-channel4",
+ "mxs-lradc-channel5",
+ "mxs-lradc-channel6",
+ "mxs-lradc-channel7",
+ "mxs-lradc-button0",
+ "mxs-lradc-button1",
+};
+
+struct mxs_lradc_of_config {
+ const int irq_count;
+ const char * const *irq_name;
+ const uint32_t *vref_mv;
+};
+
+#define VREF_MV_BASE 1850
+
+static const uint32_t mx23_vref_mv[LRADC_MAX_TOTAL_CHANS] = {
+ VREF_MV_BASE, /* CH0 */
+ VREF_MV_BASE, /* CH1 */
+ VREF_MV_BASE, /* CH2 */
+ VREF_MV_BASE, /* CH3 */
+ VREF_MV_BASE, /* CH4 */
+ VREF_MV_BASE, /* CH5 */
+ VREF_MV_BASE * 2, /* CH6 VDDIO */
+ VREF_MV_BASE * 4, /* CH7 VBATT */
+ VREF_MV_BASE, /* CH8 Temp sense 0 */
+ VREF_MV_BASE, /* CH9 Temp sense 1 */
+ VREF_MV_BASE, /* CH10 */
+ VREF_MV_BASE, /* CH11 */
+ VREF_MV_BASE, /* CH12 USB_DP */
+ VREF_MV_BASE, /* CH13 USB_DN */
+ VREF_MV_BASE, /* CH14 VBG */
+ VREF_MV_BASE * 4, /* CH15 VDD5V */
+};
+
+static const uint32_t mx28_vref_mv[LRADC_MAX_TOTAL_CHANS] = {
+ VREF_MV_BASE, /* CH0 */
+ VREF_MV_BASE, /* CH1 */
+ VREF_MV_BASE, /* CH2 */
+ VREF_MV_BASE, /* CH3 */
+ VREF_MV_BASE, /* CH4 */
+ VREF_MV_BASE, /* CH5 */
+ VREF_MV_BASE, /* CH6 */
+ VREF_MV_BASE * 4, /* CH7 VBATT */
+ VREF_MV_BASE, /* CH8 Temp sense 0 */
+ VREF_MV_BASE, /* CH9 Temp sense 1 */
+ VREF_MV_BASE * 2, /* CH10 VDDIO */
+ VREF_MV_BASE, /* CH11 VTH */
+ VREF_MV_BASE * 2, /* CH12 VDDA */
+ VREF_MV_BASE, /* CH13 VDDD */
+ VREF_MV_BASE, /* CH14 VBG */
+ VREF_MV_BASE * 4, /* CH15 VDD5V */
+};
+
+static const struct mxs_lradc_of_config mxs_lradc_of_config[] = {
+ [IMX23_LRADC] = {
+ .irq_count = ARRAY_SIZE(mx23_lradc_irq_names),
+ .irq_name = mx23_lradc_irq_names,
+ .vref_mv = mx23_vref_mv,
+ },
+ [IMX28_LRADC] = {
+ .irq_count = ARRAY_SIZE(mx28_lradc_irq_names),
+ .irq_name = mx28_lradc_irq_names,
+ .vref_mv = mx28_vref_mv,
+ },
+};
+
+enum mxs_lradc_ts {
+ MXS_LRADC_TOUCHSCREEN_NONE = 0,
+ MXS_LRADC_TOUCHSCREEN_4WIRE,
+ MXS_LRADC_TOUCHSCREEN_5WIRE,
+};
+
+/*
+ * Touchscreen handling
+ */
+enum lradc_ts_plate {
+ LRADC_TOUCH = 0,
+ LRADC_SAMPLE_X,
+ LRADC_SAMPLE_Y,
+ LRADC_SAMPLE_PRESSURE,
+ LRADC_SAMPLE_VALID,
+};
+
+enum mxs_lradc_divbytwo {
+ MXS_LRADC_DIV_DISABLED = 0,
+ MXS_LRADC_DIV_ENABLED,
+};
+
+struct mxs_lradc_scale {
+ unsigned int integer;
+ unsigned int nano;
+};
+
+struct mxs_lradc {
+ struct device *dev;
+ void __iomem *base;
+ int irq[13];
+
+ struct clk *clk;
+
+ uint32_t *buffer;
+ struct iio_trigger *trig;
+
+ struct mutex lock;
+
+ struct completion completion;
+
+ const uint32_t *vref_mv;
+ struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2];
+ unsigned long is_divided;
+
+ /*
+ * Touchscreen LRADC channels receives a private slot in the CTRL4
+ * register, the slot #7. Therefore only 7 slots instead of 8 in the
+ * CTRL4 register can be mapped to LRADC channels when using the
+ * touchscreen.
+ *
+ * Furthermore, certain LRADC channels are shared between touchscreen
+ * and/or touch-buttons and generic LRADC block. Therefore when using
+ * either of these, these channels are not available for the regular
+ * sampling. The shared channels are as follows:
+ *
+ * CH0 -- Touch button #0
+ * CH1 -- Touch button #1
+ * CH2 -- Touch screen XPUL
+ * CH3 -- Touch screen YPLL
+ * CH4 -- Touch screen XNUL
+ * CH5 -- Touch screen YNLR
+ * CH6 -- Touch screen WIPER (5-wire only)
+ *
+ * The bitfields below represents which parts of the LRADC block are
+ * switched into special mode of operation. These channels can not
+ * be sampled as regular LRADC channels. The driver will refuse any
+ * attempt to sample these channels.
+ */
+#define CHAN_MASK_TOUCHBUTTON (0x3 << 0)
+#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 2)
+#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 2)
+ enum mxs_lradc_ts use_touchscreen;
+ bool use_touchbutton;
+
+ struct input_dev *ts_input;
+
+ enum mxs_lradc_id soc;
+ enum lradc_ts_plate cur_plate; /* statemachine */
+ bool ts_valid;
+ unsigned ts_x_pos;
+ unsigned ts_y_pos;
+ unsigned ts_pressure;
+
+ /* handle touchscreen's physical behaviour */
+ /* samples per coordinate */
+ unsigned over_sample_cnt;
+ /* time clocks between samples */
+ unsigned over_sample_delay;
+ /* time in clocks to wait after the plates where switched */
+ unsigned settling_delay;
+};
+
+#define LRADC_CTRL0 0x00
+# define LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE (1 << 23)
+# define LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE (1 << 22)
+# define LRADC_CTRL0_MX28_YNNSW /* YM */ (1 << 21)
+# define LRADC_CTRL0_MX28_YPNSW /* YP */ (1 << 20)
+# define LRADC_CTRL0_MX28_YPPSW /* YP */ (1 << 19)
+# define LRADC_CTRL0_MX28_XNNSW /* XM */ (1 << 18)
+# define LRADC_CTRL0_MX28_XNPSW /* XM */ (1 << 17)
+# define LRADC_CTRL0_MX28_XPPSW /* XP */ (1 << 16)
+
+# define LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE (1 << 20)
+# define LRADC_CTRL0_MX23_YM (1 << 19)
+# define LRADC_CTRL0_MX23_XM (1 << 18)
+# define LRADC_CTRL0_MX23_YP (1 << 17)
+# define LRADC_CTRL0_MX23_XP (1 << 16)
+
+# define LRADC_CTRL0_MX28_PLATE_MASK \
+ (LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE | \
+ LRADC_CTRL0_MX28_YNNSW | LRADC_CTRL0_MX28_YPNSW | \
+ LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW | \
+ LRADC_CTRL0_MX28_XNPSW | LRADC_CTRL0_MX28_XPPSW)
+
+# define LRADC_CTRL0_MX23_PLATE_MASK \
+ (LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE | \
+ LRADC_CTRL0_MX23_YM | LRADC_CTRL0_MX23_XM | \
+ LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XP)
+
+#define LRADC_CTRL1 0x10
+#define LRADC_CTRL1_TOUCH_DETECT_IRQ_EN (1 << 24)
+#define LRADC_CTRL1_LRADC_IRQ_EN(n) (1 << ((n) + 16))
+#define LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK (0x1fff << 16)
+#define LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK (0x01ff << 16)
+#define LRADC_CTRL1_LRADC_IRQ_EN_OFFSET 16
+#define LRADC_CTRL1_TOUCH_DETECT_IRQ (1 << 8)
+#define LRADC_CTRL1_LRADC_IRQ(n) (1 << (n))
+#define LRADC_CTRL1_MX28_LRADC_IRQ_MASK 0x1fff
+#define LRADC_CTRL1_MX23_LRADC_IRQ_MASK 0x01ff
+#define LRADC_CTRL1_LRADC_IRQ_OFFSET 0
+
+#define LRADC_CTRL2 0x20
+#define LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET 24
+#define LRADC_CTRL2_TEMPSENSE_PWD (1 << 15)
+
+#define LRADC_STATUS 0x40
+#define LRADC_STATUS_TOUCH_DETECT_RAW (1 << 0)
+
+#define LRADC_CH(n) (0x50 + (0x10 * (n)))
+#define LRADC_CH_ACCUMULATE (1 << 29)
+#define LRADC_CH_NUM_SAMPLES_MASK (0x1f << 24)
+#define LRADC_CH_NUM_SAMPLES_OFFSET 24
+#define LRADC_CH_NUM_SAMPLES(x) \
+ ((x) << LRADC_CH_NUM_SAMPLES_OFFSET)
+#define LRADC_CH_VALUE_MASK 0x3ffff
+#define LRADC_CH_VALUE_OFFSET 0
+
+#define LRADC_DELAY(n) (0xd0 + (0x10 * (n)))
+#define LRADC_DELAY_TRIGGER_LRADCS_MASK (0xff << 24)
+#define LRADC_DELAY_TRIGGER_LRADCS_OFFSET 24
+#define LRADC_DELAY_TRIGGER(x) \
+ (((x) << LRADC_DELAY_TRIGGER_LRADCS_OFFSET) & \
+ LRADC_DELAY_TRIGGER_LRADCS_MASK)
+#define LRADC_DELAY_KICK (1 << 20)
+#define LRADC_DELAY_TRIGGER_DELAYS_MASK (0xf << 16)
+#define LRADC_DELAY_TRIGGER_DELAYS_OFFSET 16
+#define LRADC_DELAY_TRIGGER_DELAYS(x) \
+ (((x) << LRADC_DELAY_TRIGGER_DELAYS_OFFSET) & \
+ LRADC_DELAY_TRIGGER_DELAYS_MASK)
+#define LRADC_DELAY_LOOP_COUNT_MASK (0x1f << 11)
+#define LRADC_DELAY_LOOP_COUNT_OFFSET 11
+#define LRADC_DELAY_LOOP(x) \
+ (((x) << LRADC_DELAY_LOOP_COUNT_OFFSET) & \
+ LRADC_DELAY_LOOP_COUNT_MASK)
+#define LRADC_DELAY_DELAY_MASK 0x7ff
+#define LRADC_DELAY_DELAY_OFFSET 0
+#define LRADC_DELAY_DELAY(x) \
+ (((x) << LRADC_DELAY_DELAY_OFFSET) & \
+ LRADC_DELAY_DELAY_MASK)
+
+#define LRADC_CTRL4 0x140
+#define LRADC_CTRL4_LRADCSELECT_MASK(n) (0xf << ((n) * 4))
+#define LRADC_CTRL4_LRADCSELECT_OFFSET(n) ((n) * 4)
+
+#define LRADC_RESOLUTION 12
+#define LRADC_SINGLE_SAMPLE_MASK ((1 << LRADC_RESOLUTION) - 1)
+
+static void mxs_lradc_reg_set(struct mxs_lradc *lradc, u32 val, u32 reg)
+{
+ writel(val, lradc->base + reg + STMP_OFFSET_REG_SET);
+}
+
+static void mxs_lradc_reg_clear(struct mxs_lradc *lradc, u32 val, u32 reg)
+{
+ writel(val, lradc->base + reg + STMP_OFFSET_REG_CLR);
+}
+
+static void mxs_lradc_reg_wrt(struct mxs_lradc *lradc, u32 val, u32 reg)
+{
+ writel(val, lradc->base + reg);
+}
+
+static u32 mxs_lradc_plate_mask(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL0_MX23_PLATE_MASK;
+ else
+ return LRADC_CTRL0_MX28_PLATE_MASK;
+}
+
+static u32 mxs_lradc_irq_en_mask(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK;
+ else
+ return LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK;
+}
+
+static u32 mxs_lradc_irq_mask(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL1_MX23_LRADC_IRQ_MASK;
+ else
+ return LRADC_CTRL1_MX28_LRADC_IRQ_MASK;
+}
+
+static u32 mxs_lradc_touch_detect_bit(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE;
+ else
+ return LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE;
+}
+
+static u32 mxs_lradc_drive_x_plate(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL0_MX23_XP | LRADC_CTRL0_MX23_XM;
+ else
+ return LRADC_CTRL0_MX28_XPPSW | LRADC_CTRL0_MX28_XNNSW;
+}
+
+static u32 mxs_lradc_drive_y_plate(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_YM;
+ else
+ return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_YNNSW;
+}
+
+static u32 mxs_lradc_drive_pressure(struct mxs_lradc *lradc)
+{
+ if (lradc->soc == IMX23_LRADC)
+ return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XM;
+ else
+ return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW;
+}
+
+static bool mxs_lradc_check_touch_event(struct mxs_lradc *lradc)
+{
+ return !!(readl(lradc->base + LRADC_STATUS) &
+ LRADC_STATUS_TOUCH_DETECT_RAW);
+}
+
+static void mxs_lradc_setup_ts_channel(struct mxs_lradc *lradc, unsigned ch)
+{
+ /*
+ * prepare for oversampling conversion
+ *
+ * from the datasheet:
+ * "The ACCUMULATE bit in the appropriate channel register
+ * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0;
+ * otherwise, the IRQs will not fire."
+ */
+ mxs_lradc_reg_wrt(lradc, LRADC_CH_ACCUMULATE |
+ LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1),
+ LRADC_CH(ch));
+
+ /* from the datasheet:
+ * "Software must clear this register in preparation for a
+ * multi-cycle accumulation.
+ */
+ mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch));
+
+ /* prepare the delay/loop unit according to the oversampling count */
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch) |
+ LRADC_DELAY_TRIGGER_DELAYS(0) |
+ LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) |
+ LRADC_DELAY_DELAY(lradc->over_sample_delay - 1),
+ LRADC_DELAY(3));
+
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(2) |
+ LRADC_CTRL1_LRADC_IRQ(3) | LRADC_CTRL1_LRADC_IRQ(4) |
+ LRADC_CTRL1_LRADC_IRQ(5), LRADC_CTRL1);
+
+ /* wake us again, when the complete conversion is done */
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(ch), LRADC_CTRL1);
+ /*
+ * after changing the touchscreen plates setting
+ * the signals need some initial time to settle. Start the
+ * SoC's delay unit and start the conversion later
+ * and automatically.
+ */
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */
+ LRADC_DELAY_TRIGGER_DELAYS(1 << 3) | /* trigger DELAY unit#3 */
+ LRADC_DELAY_KICK |
+ LRADC_DELAY_DELAY(lradc->settling_delay),
+ LRADC_DELAY(2));
+}
+
+/*
+ * Pressure detection is special:
+ * We want to do both required measurements for the pressure detection in
+ * one turn. Use the hardware features to chain both conversions and let the
+ * hardware report one interrupt if both conversions are done
+ */
+static void mxs_lradc_setup_ts_pressure(struct mxs_lradc *lradc, unsigned ch1,
+ unsigned ch2)
+{
+ u32 reg;
+
+ /*
+ * prepare for oversampling conversion
+ *
+ * from the datasheet:
+ * "The ACCUMULATE bit in the appropriate channel register
+ * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0;
+ * otherwise, the IRQs will not fire."
+ */
+ reg = LRADC_CH_ACCUMULATE |
+ LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1);
+ mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch1));
+ mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch2));
+
+ /* from the datasheet:
+ * "Software must clear this register in preparation for a
+ * multi-cycle accumulation.
+ */
+ mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch1));
+ mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch2));
+
+ /* prepare the delay/loop unit according to the oversampling count */
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch1) |
+ LRADC_DELAY_TRIGGER(1 << ch2) | /* start both channels */
+ LRADC_DELAY_TRIGGER_DELAYS(0) |
+ LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) |
+ LRADC_DELAY_DELAY(lradc->over_sample_delay - 1),
+ LRADC_DELAY(3));
+
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(2) |
+ LRADC_CTRL1_LRADC_IRQ(3) | LRADC_CTRL1_LRADC_IRQ(4) |
+ LRADC_CTRL1_LRADC_IRQ(5), LRADC_CTRL1);
+
+ /* wake us again, when the conversions are done */
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(ch2), LRADC_CTRL1);
+ /*
+ * after changing the touchscreen plates setting
+ * the signals need some initial time to settle. Start the
+ * SoC's delay unit and start the conversion later
+ * and automatically.
+ */
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */
+ LRADC_DELAY_TRIGGER_DELAYS(1 << 3) | /* trigger DELAY unit#3 */
+ LRADC_DELAY_KICK |
+ LRADC_DELAY_DELAY(lradc->settling_delay), LRADC_DELAY(2));
+}
+
+static unsigned mxs_lradc_read_raw_channel(struct mxs_lradc *lradc,
+ unsigned channel)
+{
+ u32 reg;
+ unsigned num_samples, val;
+
+ reg = readl(lradc->base + LRADC_CH(channel));
+ if (reg & LRADC_CH_ACCUMULATE)
+ num_samples = lradc->over_sample_cnt;
+ else
+ num_samples = 1;
+
+ val = (reg & LRADC_CH_VALUE_MASK) >> LRADC_CH_VALUE_OFFSET;
+ return val / num_samples;
+}
+
+static unsigned mxs_lradc_read_ts_pressure(struct mxs_lradc *lradc,
+ unsigned ch1, unsigned ch2)
+{
+ u32 reg, mask;
+ unsigned pressure, m1, m2;
+
+ mask = LRADC_CTRL1_LRADC_IRQ(ch1) | LRADC_CTRL1_LRADC_IRQ(ch2);
+ reg = readl(lradc->base + LRADC_CTRL1) & mask;
+
+ while (reg != mask) {
+ reg = readl(lradc->base + LRADC_CTRL1) & mask;
+ dev_dbg(lradc->dev, "One channel is still busy: %X\n", reg);
+ }
+
+ m1 = mxs_lradc_read_raw_channel(lradc, ch1);
+ m2 = mxs_lradc_read_raw_channel(lradc, ch2);
+
+ if (m2 == 0) {
+ dev_warn(lradc->dev, "Cannot calculate pressure\n");
+ return 1 << (LRADC_RESOLUTION - 1);
+ }
+
+ /* simply scale the value from 0 ... max ADC resolution */
+ pressure = m1;
+ pressure *= (1 << LRADC_RESOLUTION);
+ pressure /= m2;
+
+ dev_dbg(lradc->dev, "Pressure = %u\n", pressure);
+ return pressure;
+}
+
+#define TS_CH_XP 2
+#define TS_CH_YP 3
+#define TS_CH_XM 4
+#define TS_CH_YM 5
+
+static int mxs_lradc_read_ts_channel(struct mxs_lradc *lradc)
+{
+ u32 reg;
+ int val;
+
+ reg = readl(lradc->base + LRADC_CTRL1);
+
+ /* only channels 3 to 5 are of interest here */
+ if (reg & LRADC_CTRL1_LRADC_IRQ(TS_CH_YP)) {
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(TS_CH_YP) |
+ LRADC_CTRL1_LRADC_IRQ(TS_CH_YP), LRADC_CTRL1);
+ val = mxs_lradc_read_raw_channel(lradc, TS_CH_YP);
+ } else if (reg & LRADC_CTRL1_LRADC_IRQ(TS_CH_XM)) {
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(TS_CH_XM) |
+ LRADC_CTRL1_LRADC_IRQ(TS_CH_XM), LRADC_CTRL1);
+ val = mxs_lradc_read_raw_channel(lradc, TS_CH_XM);
+ } else if (reg & LRADC_CTRL1_LRADC_IRQ(TS_CH_YM)) {
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(TS_CH_YM) |
+ LRADC_CTRL1_LRADC_IRQ(TS_CH_YM), LRADC_CTRL1);
+ val = mxs_lradc_read_raw_channel(lradc, TS_CH_YM);
+ } else {
+ return -EIO;
+ }
+
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2));
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3));
+
+ return val;
+}
+
+/*
+ * YP(open)--+-------------+
+ * | |--+
+ * | | |
+ * YM(-)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(weak+) XM(open)
+ *
+ * "weak+" means 200k Ohm VDDIO
+ * (-) means GND
+ */
+static void mxs_lradc_setup_touch_detection(struct mxs_lradc *lradc)
+{
+ /*
+ * In order to detect a touch event the 'touch detect enable' bit
+ * enables:
+ * - a weak pullup to the X+ connector
+ * - a strong ground at the Y- connector
+ */
+ mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0);
+ mxs_lradc_reg_set(lradc, mxs_lradc_touch_detect_bit(lradc),
+ LRADC_CTRL0);
+}
+
+/*
+ * YP(meas)--+-------------+
+ * | |--+
+ * | | |
+ * YM(open)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(+) XM(-)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_x_pos(struct mxs_lradc *lradc)
+{
+ mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0);
+ mxs_lradc_reg_set(lradc, mxs_lradc_drive_x_plate(lradc), LRADC_CTRL0);
+
+ lradc->cur_plate = LRADC_SAMPLE_X;
+ mxs_lradc_setup_ts_channel(lradc, TS_CH_YP);
+}
+
+/*
+ * YP(+)--+-------------+
+ * | |--+
+ * | | |
+ * YM(-)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(open) XM(meas)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_y_pos(struct mxs_lradc *lradc)
+{
+ mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0);
+ mxs_lradc_reg_set(lradc, mxs_lradc_drive_y_plate(lradc), LRADC_CTRL0);
+
+ lradc->cur_plate = LRADC_SAMPLE_Y;
+ mxs_lradc_setup_ts_channel(lradc, TS_CH_XM);
+}
+
+/*
+ * YP(+)--+-------------+
+ * | |--+
+ * | | |
+ * YM(meas)--+-------------+ |
+ * +--------------+
+ * | |
+ * XP(meas) XM(-)
+ *
+ * (+) means here 1.85 V
+ * (-) means here GND
+ */
+static void mxs_lradc_prepare_pressure(struct mxs_lradc *lradc)
+{
+ mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0);
+ mxs_lradc_reg_set(lradc, mxs_lradc_drive_pressure(lradc), LRADC_CTRL0);
+
+ lradc->cur_plate = LRADC_SAMPLE_PRESSURE;
+ mxs_lradc_setup_ts_pressure(lradc, TS_CH_XP, TS_CH_YM);
+}
+
+static void mxs_lradc_enable_touch_detection(struct mxs_lradc *lradc)
+{
+ mxs_lradc_setup_touch_detection(lradc);
+
+ lradc->cur_plate = LRADC_TOUCH;
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ |
+ LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1);
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1);
+}
+
+static void mxs_lradc_report_ts_event(struct mxs_lradc *lradc)
+{
+ input_report_abs(lradc->ts_input, ABS_X, lradc->ts_x_pos);
+ input_report_abs(lradc->ts_input, ABS_Y, lradc->ts_y_pos);
+ input_report_abs(lradc->ts_input, ABS_PRESSURE, lradc->ts_pressure);
+ input_report_key(lradc->ts_input, BTN_TOUCH, 1);
+ input_sync(lradc->ts_input);
+}
+
+static void mxs_lradc_complete_touch_event(struct mxs_lradc *lradc)
+{
+ mxs_lradc_setup_touch_detection(lradc);
+ lradc->cur_plate = LRADC_SAMPLE_VALID;
+ /*
+ * start a dummy conversion to burn time to settle the signals
+ * note: we are not interested in the conversion's value
+ */
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(5));
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(5), LRADC_CTRL1);
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(5), LRADC_CTRL1);
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << 5) |
+ LRADC_DELAY_KICK | LRADC_DELAY_DELAY(10), /* waste 5 ms */
+ LRADC_DELAY(2));
+}
+
+/*
+ * in order to avoid false measurements, report only samples where
+ * the surface is still touched after the position measurement
+ */
+static void mxs_lradc_finish_touch_event(struct mxs_lradc *lradc, bool valid)
+{
+ /* if it is still touched, report the sample */
+ if (valid && mxs_lradc_check_touch_event(lradc)) {
+ lradc->ts_valid = true;
+ mxs_lradc_report_ts_event(lradc);
+ }
+
+ /* if it is even still touched, continue with the next measurement */
+ if (mxs_lradc_check_touch_event(lradc)) {
+ mxs_lradc_prepare_y_pos(lradc);
+ return;
+ }
+
+ if (lradc->ts_valid) {
+ /* signal the release */
+ lradc->ts_valid = false;
+ input_report_key(lradc->ts_input, BTN_TOUCH, 0);
+ input_sync(lradc->ts_input);
+ }
+
+ /* if it is released, wait for the next touch via IRQ */
+ lradc->cur_plate = LRADC_TOUCH;
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ, LRADC_CTRL1);
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1);
+}
+
+/* touchscreen's state machine */
+static void mxs_lradc_handle_touch(struct mxs_lradc *lradc)
+{
+ int val;
+
+ switch (lradc->cur_plate) {
+ case LRADC_TOUCH:
+ /*
+ * start with the Y-pos, because it uses nearly the same plate
+ * settings like the touch detection
+ */
+ if (mxs_lradc_check_touch_event(lradc)) {
+ mxs_lradc_reg_clear(lradc,
+ LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
+ LRADC_CTRL1);
+ mxs_lradc_prepare_y_pos(lradc);
+ }
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ,
+ LRADC_CTRL1);
+ return;
+
+ case LRADC_SAMPLE_Y:
+ val = mxs_lradc_read_ts_channel(lradc);
+ if (val < 0) {
+ mxs_lradc_enable_touch_detection(lradc); /* re-start */
+ return;
+ }
+ lradc->ts_y_pos = val;
+ mxs_lradc_prepare_x_pos(lradc);
+ return;
+
+ case LRADC_SAMPLE_X:
+ val = mxs_lradc_read_ts_channel(lradc);
+ if (val < 0) {
+ mxs_lradc_enable_touch_detection(lradc); /* re-start */
+ return;
+ }
+ lradc->ts_x_pos = val;
+ mxs_lradc_prepare_pressure(lradc);
+ return;
+
+ case LRADC_SAMPLE_PRESSURE:
+ lradc->ts_pressure =
+ mxs_lradc_read_ts_pressure(lradc, TS_CH_XP, TS_CH_YM);
+ mxs_lradc_complete_touch_event(lradc);
+ return;
+
+ case LRADC_SAMPLE_VALID:
+ val = mxs_lradc_read_ts_channel(lradc); /* ignore the value */
+ mxs_lradc_finish_touch_event(lradc, 1);
+ break;
+ }
+}
+
+/*
+ * Raw I/O operations
+ */
+static int mxs_lradc_read_single(struct iio_dev *iio_dev, int chan, int *val)
+{
+ struct mxs_lradc *lradc = iio_priv(iio_dev);
+ int ret;
+
+ /*
+ * See if there is no buffered operation in progess. If there is, simply
+ * bail out. This can be improved to support both buffered and raw IO at
+ * the same time, yet the code becomes horribly complicated. Therefore I
+ * applied KISS principle here.
+ */
+ ret = mutex_trylock(&lradc->lock);
+ if (!ret)
+ return -EBUSY;
+
+ reinit_completion(&lradc->completion);
+
+ /*
+ * No buffered operation in progress, map the channel and trigger it.
+ * Virtual channel 0 is always used here as the others are always not
+ * used if doing raw sampling.
+ */
+ if (lradc->soc == IMX28_LRADC)
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK,
+ LRADC_CTRL1);
+ mxs_lradc_reg_clear(lradc, 0xff, LRADC_CTRL0);
+
+ /* Enable / disable the divider per requirement */
+ if (test_bit(chan, &lradc->is_divided))
+ mxs_lradc_reg_set(lradc, 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET,
+ LRADC_CTRL2);
+ else
+ mxs_lradc_reg_clear(lradc,
+ 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, LRADC_CTRL2);
+
+ /* Clean the slot's previous content, then set new one. */
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(0),
+ LRADC_CTRL4);
+ mxs_lradc_reg_set(lradc, chan, LRADC_CTRL4);
+
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(0));
+
+ /* Enable the IRQ and start sampling the channel. */
+ mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1);
+ mxs_lradc_reg_set(lradc, 1 << 0, LRADC_CTRL0);
+
+ /* Wait for completion on the channel, 1 second max. */
+ ret = wait_for_completion_killable_timeout(&lradc->completion, HZ);
+ if (!ret)
+ ret = -ETIMEDOUT;
+ if (ret < 0)
+ goto err;
+
+ /* Read the data. */
+ *val = readl(lradc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK;
+ ret = IIO_VAL_INT;
+
+err:
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1);
+
+ mutex_unlock(&lradc->lock);
+
+ return ret;
+}
+
+static int mxs_lradc_read_temp(struct iio_dev *iio_dev, int *val)
+{
+ int ret, min, max;
+
+ ret = mxs_lradc_read_single(iio_dev, 8, &min);
+ if (ret != IIO_VAL_INT)
+ return ret;
+
+ ret = mxs_lradc_read_single(iio_dev, 9, &max);
+ if (ret != IIO_VAL_INT)
+ return ret;
+
+ *val = max - min;
+
+ return IIO_VAL_INT;
+}
+
+static int mxs_lradc_read_raw(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long m)
+{
+ struct mxs_lradc *lradc = iio_priv(iio_dev);
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type == IIO_TEMP)
+ return mxs_lradc_read_temp(iio_dev, val);
+
+ return mxs_lradc_read_single(iio_dev, chan->channel, val);
+
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_TEMP) {
+ /* From the datasheet, we have to multiply by 1.012 and
+ * divide by 4
+ */
+ *val = 0;
+ *val2 = 253000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ *val = lradc->vref_mv[chan->channel];
+ *val2 = chan->scan_type.realbits -
+ test_bit(chan->channel, &lradc->is_divided);
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type == IIO_TEMP) {
+ /* The calculated value from the ADC is in Kelvin, we
+ * want Celsius for hwmon so the offset is
+ * -272.15 * scale
+ */
+ *val = -1075;
+ *val2 = 691699;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int mxs_lradc_write_raw(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long m)
+{
+ struct mxs_lradc *lradc = iio_priv(iio_dev);
+ struct mxs_lradc_scale *scale_avail =
+ lradc->scale_avail[chan->channel];
+ int ret;
+
+ ret = mutex_trylock(&lradc->lock);
+ if (!ret)
+ return -EBUSY;
+
+ switch (m) {
+ case IIO_CHAN_INFO_SCALE:
+ ret = -EINVAL;
+ if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer &&
+ val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) {
+ /* divider by two disabled */
+ clear_bit(chan->channel, &lradc->is_divided);
+ ret = 0;
+ } else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer &&
+ val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) {
+ /* divider by two enabled */
+ set_bit(chan->channel, &lradc->is_divided);
+ ret = 0;
+ }
+
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&lradc->lock);
+
+ return ret;
+}
+
+static int mxs_lradc_write_raw_get_fmt(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ long m)
+{
+ return IIO_VAL_INT_PLUS_NANO;
+}
+
+static ssize_t mxs_lradc_show_scale_available_ch(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ int ch)
+{
+ struct iio_dev *iio = dev_to_iio_dev(dev);
+ struct mxs_lradc *lradc = iio_priv(iio);
+ int i, len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(lradc->scale_avail[ch]); i++)
+ len += sprintf(buf + len, "%d.%09u ",
+ lradc->scale_avail[ch][i].integer,
+ lradc->scale_avail[ch][i].nano);
+
+ len += sprintf(buf + len, "\n");
+
+ return len;
+}
+
+static ssize_t mxs_lradc_show_scale_available(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr);
+
+ return mxs_lradc_show_scale_available_ch(dev, attr, buf,
+ iio_attr->address);
+}
+
+#define SHOW_SCALE_AVAILABLE_ATTR(ch) \
+static IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, S_IRUGO, \
+ mxs_lradc_show_scale_available, NULL, ch)
+
+SHOW_SCALE_AVAILABLE_ATTR(0);
+SHOW_SCALE_AVAILABLE_ATTR(1);
+SHOW_SCALE_AVAILABLE_ATTR(2);
+SHOW_SCALE_AVAILABLE_ATTR(3);
+SHOW_SCALE_AVAILABLE_ATTR(4);
+SHOW_SCALE_AVAILABLE_ATTR(5);
+SHOW_SCALE_AVAILABLE_ATTR(6);
+SHOW_SCALE_AVAILABLE_ATTR(7);
+SHOW_SCALE_AVAILABLE_ATTR(10);
+SHOW_SCALE_AVAILABLE_ATTR(11);
+SHOW_SCALE_AVAILABLE_ATTR(12);
+SHOW_SCALE_AVAILABLE_ATTR(13);
+SHOW_SCALE_AVAILABLE_ATTR(14);
+SHOW_SCALE_AVAILABLE_ATTR(15);
+
+static struct attribute *mxs_lradc_attributes[] = {
+ &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage2_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage3_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage4_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage5_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage6_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage7_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage10_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage11_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage12_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage13_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage14_scale_available.dev_attr.attr,
+ &iio_dev_attr_in_voltage15_scale_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mxs_lradc_attribute_group = {
+ .attrs = mxs_lradc_attributes,
+};
+
+static const struct iio_info mxs_lradc_iio_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = mxs_lradc_read_raw,
+ .write_raw = mxs_lradc_write_raw,
+ .write_raw_get_fmt = mxs_lradc_write_raw_get_fmt,
+ .attrs = &mxs_lradc_attribute_group,
+};
+
+static int mxs_lradc_ts_open(struct input_dev *dev)
+{
+ struct mxs_lradc *lradc = input_get_drvdata(dev);
+
+ /* Enable the touch-detect circuitry. */
+ mxs_lradc_enable_touch_detection(lradc);
+
+ return 0;
+}
+
+static void mxs_lradc_disable_ts(struct mxs_lradc *lradc)
+{
+ /* stop all interrupts from firing */
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN |
+ LRADC_CTRL1_LRADC_IRQ_EN(2) | LRADC_CTRL1_LRADC_IRQ_EN(3) |
+ LRADC_CTRL1_LRADC_IRQ_EN(4) | LRADC_CTRL1_LRADC_IRQ_EN(5),
+ LRADC_CTRL1);
+
+ /* Power-down touchscreen touch-detect circuitry. */
+ mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0);
+}
+
+static void mxs_lradc_ts_close(struct input_dev *dev)
+{
+ struct mxs_lradc *lradc = input_get_drvdata(dev);
+
+ mxs_lradc_disable_ts(lradc);
+}
+
+static int mxs_lradc_ts_register(struct mxs_lradc *lradc)
+{
+ struct input_dev *input;
+ struct device *dev = lradc->dev;
+ int ret;
+
+ if (!lradc->use_touchscreen)
+ return 0;
+
+ input = input_allocate_device();
+ if (!input)
+ return -ENOMEM;
+
+ input->name = DRIVER_NAME;
+ input->id.bustype = BUS_HOST;
+ input->dev.parent = dev;
+ input->open = mxs_lradc_ts_open;
+ input->close = mxs_lradc_ts_close;
+
+ __set_bit(EV_ABS, input->evbit);
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(BTN_TOUCH, input->keybit);
+ input_set_abs_params(input, ABS_X, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0);
+ input_set_abs_params(input, ABS_Y, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0);
+ input_set_abs_params(input, ABS_PRESSURE, 0, LRADC_SINGLE_SAMPLE_MASK,
+ 0, 0);
+
+ lradc->ts_input = input;
+ input_set_drvdata(input, lradc);
+ ret = input_register_device(input);
+ if (ret)
+ input_free_device(lradc->ts_input);
+
+ return ret;
+}
+
+static void mxs_lradc_ts_unregister(struct mxs_lradc *lradc)
+{
+ if (!lradc->use_touchscreen)
+ return;
+
+ mxs_lradc_disable_ts(lradc);
+ input_unregister_device(lradc->ts_input);
+}
+
+/*
+ * IRQ Handling
+ */
+static irqreturn_t mxs_lradc_handle_irq(int irq, void *data)
+{
+ struct iio_dev *iio = data;
+ struct mxs_lradc *lradc = iio_priv(iio);
+ unsigned long reg = readl(lradc->base + LRADC_CTRL1);
+ const uint32_t ts_irq_mask =
+ LRADC_CTRL1_TOUCH_DETECT_IRQ |
+ LRADC_CTRL1_LRADC_IRQ(2) |
+ LRADC_CTRL1_LRADC_IRQ(3) |
+ LRADC_CTRL1_LRADC_IRQ(4) |
+ LRADC_CTRL1_LRADC_IRQ(5);
+
+ if (!(reg & mxs_lradc_irq_mask(lradc)))
+ return IRQ_NONE;
+
+ if (lradc->use_touchscreen && (reg & ts_irq_mask))
+ mxs_lradc_handle_touch(lradc);
+
+ if (iio_buffer_enabled(iio))
+ iio_trigger_poll(iio->trig, iio_get_time_ns());
+ else if (reg & LRADC_CTRL1_LRADC_IRQ(0))
+ complete(&lradc->completion);
+
+ mxs_lradc_reg_clear(lradc, reg & mxs_lradc_irq_mask(lradc),
+ LRADC_CTRL1);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Trigger handling
+ */
+static irqreturn_t mxs_lradc_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *iio = pf->indio_dev;
+ struct mxs_lradc *lradc = iio_priv(iio);
+ const uint32_t chan_value = LRADC_CH_ACCUMULATE |
+ ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET);
+ unsigned int i, j = 0;
+
+ for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) {
+ lradc->buffer[j] = readl(lradc->base + LRADC_CH(j));
+ mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(j));
+ lradc->buffer[j] &= LRADC_CH_VALUE_MASK;
+ lradc->buffer[j] /= LRADC_DELAY_TIMER_LOOP;
+ j++;
+ }
+
+ iio_push_to_buffers_with_timestamp(iio, lradc->buffer, pf->timestamp);
+
+ iio_trigger_notify_done(iio->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int mxs_lradc_configure_trigger(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *iio = iio_trigger_get_drvdata(trig);
+ struct mxs_lradc *lradc = iio_priv(iio);
+ const uint32_t st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR;
+
+ mxs_lradc_reg_wrt(lradc, LRADC_DELAY_KICK, LRADC_DELAY(0) + st);
+
+ return 0;
+}
+
+static const struct iio_trigger_ops mxs_lradc_trigger_ops = {
+ .owner = THIS_MODULE,
+ .set_trigger_state = &mxs_lradc_configure_trigger,
+};
+
+static int mxs_lradc_trigger_init(struct iio_dev *iio)
+{
+ int ret;
+ struct iio_trigger *trig;
+ struct mxs_lradc *lradc = iio_priv(iio);
+
+ trig = iio_trigger_alloc("%s-dev%i", iio->name, iio->id);
+ if (trig == NULL)
+ return -ENOMEM;
+
+ trig->dev.parent = lradc->dev;
+ iio_trigger_set_drvdata(trig, iio);
+ trig->ops = &mxs_lradc_trigger_ops;
+
+ ret = iio_trigger_register(trig);
+ if (ret) {
+ iio_trigger_free(trig);
+ return ret;
+ }
+
+ lradc->trig = trig;
+
+ return 0;
+}
+
+static void mxs_lradc_trigger_remove(struct iio_dev *iio)
+{
+ struct mxs_lradc *lradc = iio_priv(iio);
+
+ iio_trigger_unregister(lradc->trig);
+ iio_trigger_free(lradc->trig);
+}
+
+static int mxs_lradc_buffer_preenable(struct iio_dev *iio)
+{
+ struct mxs_lradc *lradc = iio_priv(iio);
+ int ret = 0, chan, ofs = 0;
+ unsigned long enable = 0;
+ uint32_t ctrl4_set = 0;
+ uint32_t ctrl4_clr = 0;
+ uint32_t ctrl1_irq = 0;
+ const uint32_t chan_value = LRADC_CH_ACCUMULATE |
+ ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET);
+ const int len = bitmap_weight(iio->active_scan_mask,
+ LRADC_MAX_TOTAL_CHANS);
+
+ if (!len)
+ return -EINVAL;
+
+ /*
+ * Lock the driver so raw access can not be done during buffered
+ * operation. This simplifies the code a lot.
+ */
+ ret = mutex_trylock(&lradc->lock);
+ if (!ret)
+ return -EBUSY;
+
+ lradc->buffer = kmalloc(len * sizeof(*lradc->buffer), GFP_KERNEL);
+ if (!lradc->buffer) {
+ ret = -ENOMEM;
+ goto err_mem;
+ }
+
+ if (lradc->soc == IMX28_LRADC)
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK,
+ LRADC_CTRL1);
+ mxs_lradc_reg_clear(lradc, 0xff, LRADC_CTRL0);
+
+ for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) {
+ ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs);
+ ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs);
+ ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs);
+ mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(ofs));
+ bitmap_set(&enable, ofs, 1);
+ ofs++;
+ }
+
+ mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK |
+ LRADC_DELAY_KICK, LRADC_DELAY(0));
+ mxs_lradc_reg_clear(lradc, ctrl4_clr, LRADC_CTRL4);
+ mxs_lradc_reg_set(lradc, ctrl4_set, LRADC_CTRL4);
+ mxs_lradc_reg_set(lradc, ctrl1_irq, LRADC_CTRL1);
+ mxs_lradc_reg_set(lradc, enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET,
+ LRADC_DELAY(0));
+
+ return 0;
+
+err_mem:
+ mutex_unlock(&lradc->lock);
+ return ret;
+}
+
+static int mxs_lradc_buffer_postdisable(struct iio_dev *iio)
+{
+ struct mxs_lradc *lradc = iio_priv(iio);
+
+ mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK |
+ LRADC_DELAY_KICK, LRADC_DELAY(0));
+
+ mxs_lradc_reg_clear(lradc, 0xff, LRADC_CTRL0);
+ if (lradc->soc == IMX28_LRADC)
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK,
+ LRADC_CTRL1);
+
+ kfree(lradc->buffer);
+ mutex_unlock(&lradc->lock);
+
+ return 0;
+}
+
+static bool mxs_lradc_validate_scan_mask(struct iio_dev *iio,
+ const unsigned long *mask)
+{
+ struct mxs_lradc *lradc = iio_priv(iio);
+ const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS);
+ int rsvd_chans = 0;
+ unsigned long rsvd_mask = 0;
+
+ if (lradc->use_touchbutton)
+ rsvd_mask |= CHAN_MASK_TOUCHBUTTON;
+ if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_4WIRE)
+ rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE;
+ if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE)
+ rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE;
+
+ if (lradc->use_touchbutton)
+ rsvd_chans++;
+ if (lradc->use_touchscreen)
+ rsvd_chans++;
+
+ /* Test for attempts to map channels with special mode of operation. */
+ if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS))
+ return false;
+
+ /* Test for attempts to map more channels then available slots. */
+ if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS)
+ return false;
+
+ return true;
+}
+
+static const struct iio_buffer_setup_ops mxs_lradc_buffer_ops = {
+ .preenable = &mxs_lradc_buffer_preenable,
+ .postenable = &iio_triggered_buffer_postenable,
+ .predisable = &iio_triggered_buffer_predisable,
+ .postdisable = &mxs_lradc_buffer_postdisable,
+ .validate_scan_mask = &mxs_lradc_validate_scan_mask,
+};
+
+/*
+ * Driver initialization
+ */
+
+#define MXS_ADC_CHAN(idx, chan_type) { \
+ .type = (chan_type), \
+ .indexed = 1, \
+ .scan_index = (idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = (idx), \
+ .address = (idx), \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = LRADC_RESOLUTION, \
+ .storagebits = 32, \
+ }, \
+}
+
+static const struct iio_chan_spec mxs_lradc_chan_spec[] = {
+ MXS_ADC_CHAN(0, IIO_VOLTAGE),
+ MXS_ADC_CHAN(1, IIO_VOLTAGE),
+ MXS_ADC_CHAN(2, IIO_VOLTAGE),
+ MXS_ADC_CHAN(3, IIO_VOLTAGE),
+ MXS_ADC_CHAN(4, IIO_VOLTAGE),
+ MXS_ADC_CHAN(5, IIO_VOLTAGE),
+ MXS_ADC_CHAN(6, IIO_VOLTAGE),
+ MXS_ADC_CHAN(7, IIO_VOLTAGE), /* VBATT */
+ /* Combined Temperature sensors */
+ {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .scan_index = 8,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .channel = 8,
+ .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,},
+ },
+ MXS_ADC_CHAN(10, IIO_VOLTAGE), /* VDDIO */
+ MXS_ADC_CHAN(11, IIO_VOLTAGE), /* VTH */
+ MXS_ADC_CHAN(12, IIO_VOLTAGE), /* VDDA */
+ MXS_ADC_CHAN(13, IIO_VOLTAGE), /* VDDD */
+ MXS_ADC_CHAN(14, IIO_VOLTAGE), /* VBG */
+ MXS_ADC_CHAN(15, IIO_VOLTAGE), /* VDD5V */
+};
+
+static int mxs_lradc_hw_init(struct mxs_lradc *lradc)
+{
+ /* The ADC always uses DELAY CHANNEL 0. */
+ const uint32_t adc_cfg =
+ (1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) |
+ (LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET);
+
+ int ret = stmp_reset_block(lradc->base);
+ if (ret)
+ return ret;
+
+ /* Configure DELAY CHANNEL 0 for generic ADC sampling. */
+ mxs_lradc_reg_wrt(lradc, adc_cfg, LRADC_DELAY(0));
+
+ /* Disable remaining DELAY CHANNELs */
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(1));
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2));
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3));
+
+ /* Configure the touchscreen type */
+ if (lradc->soc == IMX28_LRADC) {
+ mxs_lradc_reg_clear(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE,
+ LRADC_CTRL0);
+
+ if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE)
+ mxs_lradc_reg_set(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE,
+ LRADC_CTRL0);
+ }
+
+ /* Start internal temperature sensing. */
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_CTRL2);
+
+ return 0;
+}
+
+static void mxs_lradc_hw_stop(struct mxs_lradc *lradc)
+{
+ int i;
+
+ mxs_lradc_reg_clear(lradc, mxs_lradc_irq_en_mask(lradc), LRADC_CTRL1);
+
+ for (i = 0; i < LRADC_MAX_DELAY_CHANS; i++)
+ mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(i));
+}
+
+static const struct of_device_id mxs_lradc_dt_ids[] = {
+ { .compatible = "fsl,imx23-lradc", .data = (void *)IMX23_LRADC, },
+ { .compatible = "fsl,imx28-lradc", .data = (void *)IMX28_LRADC, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mxs_lradc_dt_ids);
+
+static int mxs_lradc_probe_touchscreen(struct mxs_lradc *lradc,
+ struct device_node *lradc_node)
+{
+ int ret;
+ u32 ts_wires = 0, adapt;
+
+ ret = of_property_read_u32(lradc_node, "fsl,lradc-touchscreen-wires",
+ &ts_wires);
+ if (ret)
+ return -ENODEV; /* touchscreen feature disabled */
+
+ switch (ts_wires) {
+ case 4:
+ lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_4WIRE;
+ break;
+ case 5:
+ if (lradc->soc == IMX28_LRADC) {
+ lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_5WIRE;
+ break;
+ }
+ /* fall through an error message for i.MX23 */
+ default:
+ dev_err(lradc->dev,
+ "Unsupported number of touchscreen wires (%d)\n",
+ ts_wires);
+ return -EINVAL;
+ }
+
+ lradc->over_sample_cnt = 4;
+ ret = of_property_read_u32(lradc_node, "fsl,ave-ctrl", &adapt);
+ if (ret == 0)
+ lradc->over_sample_cnt = adapt;
+
+ lradc->over_sample_delay = 2;
+ ret = of_property_read_u32(lradc_node, "fsl,ave-delay", &adapt);
+ if (ret == 0)
+ lradc->over_sample_delay = adapt;
+
+ lradc->settling_delay = 10;
+ ret = of_property_read_u32(lradc_node, "fsl,settling", &adapt);
+ if (ret == 0)
+ lradc->settling_delay = adapt;
+
+ return 0;
+}
+
+static int mxs_lradc_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id =
+ of_match_device(mxs_lradc_dt_ids, &pdev->dev);
+ const struct mxs_lradc_of_config *of_cfg =
+ &mxs_lradc_of_config[(enum mxs_lradc_id)of_id->data];
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct mxs_lradc *lradc;
+ struct iio_dev *iio;
+ struct resource *iores;
+ int ret = 0, touch_ret;
+ int i, s;
+ uint64_t scale_uv;
+
+ /* Allocate the IIO device. */
+ iio = devm_iio_device_alloc(dev, sizeof(*lradc));
+ if (!iio) {
+ dev_err(dev, "Failed to allocate IIO device\n");
+ return -ENOMEM;
+ }
+
+ lradc = iio_priv(iio);
+ lradc->soc = (enum mxs_lradc_id)of_id->data;
+
+ /* Grab the memory area */
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ lradc->dev = &pdev->dev;
+ lradc->base = devm_ioremap_resource(dev, iores);
+ if (IS_ERR(lradc->base))
+ return PTR_ERR(lradc->base);
+
+ lradc->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(lradc->clk)) {
+ dev_err(dev, "Failed to get the delay unit clock\n");
+ return PTR_ERR(lradc->clk);
+ }
+ ret = clk_prepare_enable(lradc->clk);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable the delay unit clock\n");
+ return ret;
+ }
+
+ touch_ret = mxs_lradc_probe_touchscreen(lradc, node);
+
+ /* Grab all IRQ sources */
+ for (i = 0; i < of_cfg->irq_count; i++) {
+ lradc->irq[i] = platform_get_irq(pdev, i);
+ if (lradc->irq[i] < 0)
+ return lradc->irq[i];
+
+ ret = devm_request_irq(dev, lradc->irq[i],
+ mxs_lradc_handle_irq, 0,
+ of_cfg->irq_name[i], iio);
+ if (ret)
+ return ret;
+ }
+
+ lradc->vref_mv = of_cfg->vref_mv;
+
+ platform_set_drvdata(pdev, iio);
+
+ init_completion(&lradc->completion);
+ mutex_init(&lradc->lock);
+
+ iio->name = pdev->name;
+ iio->dev.parent = &pdev->dev;
+ iio->info = &mxs_lradc_iio_info;
+ iio->modes = INDIO_DIRECT_MODE;
+ iio->channels = mxs_lradc_chan_spec;
+ iio->num_channels = ARRAY_SIZE(mxs_lradc_chan_spec);
+ iio->masklength = LRADC_MAX_TOTAL_CHANS;
+
+ ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time,
+ &mxs_lradc_trigger_handler,
+ &mxs_lradc_buffer_ops);
+ if (ret)
+ return ret;
+
+ ret = mxs_lradc_trigger_init(iio);
+ if (ret)
+ goto err_trig;
+
+ /* Populate available ADC input ranges */
+ for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) {
+ for (s = 0; s < ARRAY_SIZE(lradc->scale_avail[i]); s++) {
+ /*
+ * [s=0] = optional divider by two disabled (default)
+ * [s=1] = optional divider by two enabled
+ *
+ * The scale is calculated by doing:
+ * Vref >> (realbits - s)
+ * which multiplies by two on the second component
+ * of the array.
+ */
+ scale_uv = ((u64)lradc->vref_mv[i] * 100000000) >>
+ (LRADC_RESOLUTION - s);
+ lradc->scale_avail[i][s].nano =
+ do_div(scale_uv, 100000000) * 10;
+ lradc->scale_avail[i][s].integer = scale_uv;
+ }
+ }
+
+ /* Configure the hardware. */
+ ret = mxs_lradc_hw_init(lradc);
+ if (ret)
+ goto err_dev;
+
+ /* Register the touchscreen input device. */
+ if (touch_ret == 0) {
+ ret = mxs_lradc_ts_register(lradc);
+ if (ret)
+ goto err_ts_register;
+ }
+
+ /* Register IIO device. */
+ ret = iio_device_register(iio);
+ if (ret) {
+ dev_err(dev, "Failed to register IIO device\n");
+ goto err_ts;
+ }
+
+ return 0;
+
+err_ts:
+ mxs_lradc_ts_unregister(lradc);
+err_ts_register:
+ mxs_lradc_hw_stop(lradc);
+err_dev:
+ mxs_lradc_trigger_remove(iio);
+err_trig:
+ iio_triggered_buffer_cleanup(iio);
+ return ret;
+}
+
+static int mxs_lradc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *iio = platform_get_drvdata(pdev);
+ struct mxs_lradc *lradc = iio_priv(iio);
+
+ iio_device_unregister(iio);
+ mxs_lradc_ts_unregister(lradc);
+ mxs_lradc_hw_stop(lradc);
+ mxs_lradc_trigger_remove(iio);
+ iio_triggered_buffer_cleanup(iio);
+
+ clk_disable_unprepare(lradc->clk);
+ return 0;
+}
+
+static struct platform_driver mxs_lradc_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = mxs_lradc_dt_ids,
+ },
+ .probe = mxs_lradc_probe,
+ .remove = mxs_lradc_remove,
+};
+
+module_platform_driver(mxs_lradc_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("Freescale i.MX28 LRADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/staging/iio/adc/spear_adc.c b/drivers/staging/iio/adc/spear_adc.c
new file mode 100644
index 00000000000..c5492ba5075
--- /dev/null
+++ b/drivers/staging/iio/adc/spear_adc.c
@@ -0,0 +1,401 @@
+/*
+ * ST SPEAr ADC driver
+ *
+ * Copyright 2012 Stefan Roese <sr@denx.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+/* SPEAR registers definitions */
+#define SPEAR600_ADC_SCAN_RATE_LO(x) ((x) & 0xFFFF)
+#define SPEAR600_ADC_SCAN_RATE_HI(x) (((x) >> 0x10) & 0xFFFF)
+#define SPEAR_ADC_CLK_LOW(x) (((x) & 0xf) << 0)
+#define SPEAR_ADC_CLK_HIGH(x) (((x) & 0xf) << 4)
+
+/* Bit definitions for SPEAR_ADC_STATUS */
+#define SPEAR_ADC_STATUS_START_CONVERSION (1 << 0)
+#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1)
+#define SPEAR_ADC_STATUS_ADC_ENABLE (1 << 4)
+#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5)
+#define SPEAR_ADC_STATUS_VREF_INTERNAL (1 << 9)
+
+#define SPEAR_ADC_DATA_MASK 0x03ff
+#define SPEAR_ADC_DATA_BITS 10
+
+#define SPEAR_ADC_MOD_NAME "spear-adc"
+
+#define SPEAR_ADC_CHANNEL_NUM 8
+
+#define SPEAR_ADC_CLK_MIN 2500000
+#define SPEAR_ADC_CLK_MAX 20000000
+
+struct adc_regs_spear3xx {
+ u32 status;
+ u32 average;
+ u32 scan_rate;
+ u32 clk; /* Not avail for 1340 & 1310 */
+ u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM];
+ u32 ch_data[SPEAR_ADC_CHANNEL_NUM];
+};
+
+struct chan_data {
+ u32 lsb;
+ u32 msb;
+};
+
+struct adc_regs_spear6xx {
+ u32 status;
+ u32 pad[2];
+ u32 clk;
+ u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM];
+ struct chan_data ch_data[SPEAR_ADC_CHANNEL_NUM];
+ u32 scan_rate_lo;
+ u32 scan_rate_hi;
+ struct chan_data average;
+};
+
+struct spear_adc_state {
+ struct device_node *np;
+ struct adc_regs_spear3xx __iomem *adc_base_spear3xx;
+ struct adc_regs_spear6xx __iomem *adc_base_spear6xx;
+ struct clk *clk;
+ struct completion completion;
+ u32 current_clk;
+ u32 sampling_freq;
+ u32 avg_samples;
+ u32 vref_external;
+ u32 value;
+};
+
+/*
+ * Functions to access some SPEAr ADC register. Abstracted into
+ * static inline functions, because of different register offsets
+ * on different SoC variants (SPEAr300 vs SPEAr600 etc).
+ */
+static void spear_adc_set_status(struct spear_adc_state *st, u32 val)
+{
+ __raw_writel(val, &st->adc_base_spear6xx->status);
+}
+
+static void spear_adc_set_clk(struct spear_adc_state *st, u32 val)
+{
+ u32 clk_high, clk_low, count;
+ u32 apb_clk = clk_get_rate(st->clk);
+
+ count = (apb_clk + val - 1) / val;
+ clk_low = count / 2;
+ clk_high = count - clk_low;
+ st->current_clk = apb_clk / count;
+
+ __raw_writel(SPEAR_ADC_CLK_LOW(clk_low) | SPEAR_ADC_CLK_HIGH(clk_high),
+ &st->adc_base_spear6xx->clk);
+}
+
+static void spear_adc_set_ctrl(struct spear_adc_state *st, int n,
+ u32 val)
+{
+ __raw_writel(val, &st->adc_base_spear6xx->ch_ctrl[n]);
+}
+
+static u32 spear_adc_get_average(struct spear_adc_state *st)
+{
+ if (of_device_is_compatible(st->np, "st,spear600-adc")) {
+ return __raw_readl(&st->adc_base_spear6xx->average.msb) &
+ SPEAR_ADC_DATA_MASK;
+ } else {
+ return __raw_readl(&st->adc_base_spear3xx->average) &
+ SPEAR_ADC_DATA_MASK;
+ }
+}
+
+static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate)
+{
+ if (of_device_is_compatible(st->np, "st,spear600-adc")) {
+ __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate),
+ &st->adc_base_spear6xx->scan_rate_lo);
+ __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate),
+ &st->adc_base_spear6xx->scan_rate_hi);
+ } else {
+ __raw_writel(rate, &st->adc_base_spear3xx->scan_rate);
+ }
+}
+
+static int spear_adc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct spear_adc_state *st = iio_priv(indio_dev);
+ u32 status;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&indio_dev->mlock);
+
+ status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) |
+ SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) |
+ SPEAR_ADC_STATUS_START_CONVERSION |
+ SPEAR_ADC_STATUS_ADC_ENABLE;
+ if (st->vref_external == 0)
+ status |= SPEAR_ADC_STATUS_VREF_INTERNAL;
+
+ spear_adc_set_status(st, status);
+ wait_for_completion(&st->completion); /* set by ISR */
+ *val = st->value;
+
+ mutex_unlock(&indio_dev->mlock);
+
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref_external;
+ *val2 = SPEAR_ADC_DATA_BITS;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->current_clk;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static int spear_adc_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long mask)
+{
+ struct spear_adc_state *st = iio_priv(indio_dev);
+ int ret = 0;
+
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ)
+ return -EINVAL;
+
+ mutex_lock(&indio_dev->mlock);
+
+ if ((val < SPEAR_ADC_CLK_MIN) ||
+ (val > SPEAR_ADC_CLK_MAX) ||
+ (val2 != 0)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ spear_adc_set_clk(st, val);
+
+out:
+ mutex_unlock(&indio_dev->mlock);
+ return ret;
+}
+
+#define SPEAR_ADC_CHAN(idx) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+ .channel = idx, \
+}
+
+static const struct iio_chan_spec spear_adc_iio_channels[] = {
+ SPEAR_ADC_CHAN(0),
+ SPEAR_ADC_CHAN(1),
+ SPEAR_ADC_CHAN(2),
+ SPEAR_ADC_CHAN(3),
+ SPEAR_ADC_CHAN(4),
+ SPEAR_ADC_CHAN(5),
+ SPEAR_ADC_CHAN(6),
+ SPEAR_ADC_CHAN(7),
+};
+
+static irqreturn_t spear_adc_isr(int irq, void *dev_id)
+{
+ struct spear_adc_state *st = (struct spear_adc_state *)dev_id;
+
+ /* Read value to clear IRQ */
+ st->value = spear_adc_get_average(st);
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+}
+
+static int spear_adc_configure(struct spear_adc_state *st)
+{
+ int i;
+
+ /* Reset ADC core */
+ spear_adc_set_status(st, 0);
+ __raw_writel(0, &st->adc_base_spear6xx->clk);
+ for (i = 0; i < 8; i++)
+ spear_adc_set_ctrl(st, i, 0);
+ spear_adc_set_scanrate(st, 0);
+
+ spear_adc_set_clk(st, st->sampling_freq);
+
+ return 0;
+}
+
+static const struct iio_info spear_adc_info = {
+ .read_raw = &spear_adc_read_raw,
+ .write_raw = &spear_adc_write_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static int spear_adc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct spear_adc_state *st;
+ struct iio_dev *indio_dev = NULL;
+ int ret = -ENODEV;
+ int irq;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state));
+ if (!indio_dev) {
+ dev_err(dev, "failed allocating iio device\n");
+ return -ENOMEM;
+ }
+
+ st = iio_priv(indio_dev);
+ st->np = np;
+
+ /*
+ * SPEAr600 has a different register layout than other SPEAr SoC's
+ * (e.g. SPEAr3xx). Let's provide two register base addresses
+ * to support multi-arch kernels.
+ */
+ st->adc_base_spear6xx = of_iomap(np, 0);
+ if (!st->adc_base_spear6xx) {
+ dev_err(dev, "failed mapping memory\n");
+ return -ENOMEM;
+ }
+ st->adc_base_spear3xx =
+ (struct adc_regs_spear3xx __iomem *)st->adc_base_spear6xx;
+
+ st->clk = clk_get(dev, NULL);
+ if (IS_ERR(st->clk)) {
+ dev_err(dev, "failed getting clock\n");
+ goto errout1;
+ }
+
+ ret = clk_prepare_enable(st->clk);
+ if (ret) {
+ dev_err(dev, "failed enabling clock\n");
+ goto errout2;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(dev, "failed getting interrupt resource\n");
+ ret = -EINVAL;
+ goto errout3;
+ }
+
+ ret = devm_request_irq(dev, irq, spear_adc_isr, 0, SPEAR_ADC_MOD_NAME,
+ st);
+ if (ret < 0) {
+ dev_err(dev, "failed requesting interrupt\n");
+ goto errout3;
+ }
+
+ if (of_property_read_u32(np, "sampling-frequency",
+ &st->sampling_freq)) {
+ dev_err(dev, "sampling-frequency missing in DT\n");
+ ret = -EINVAL;
+ goto errout3;
+ }
+
+ /*
+ * Optional avg_samples defaults to 0, resulting in single data
+ * conversion
+ */
+ of_property_read_u32(np, "average-samples", &st->avg_samples);
+
+ /*
+ * Optional vref_external defaults to 0, resulting in internal vref
+ * selection
+ */
+ of_property_read_u32(np, "vref-external", &st->vref_external);
+
+ spear_adc_configure(st);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ init_completion(&st->completion);
+
+ indio_dev->name = SPEAR_ADC_MOD_NAME;
+ indio_dev->dev.parent = dev;
+ indio_dev->info = &spear_adc_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = spear_adc_iio_channels;
+ indio_dev->num_channels = ARRAY_SIZE(spear_adc_iio_channels);
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto errout3;
+
+ dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq);
+
+ return 0;
+
+errout3:
+ clk_disable_unprepare(st->clk);
+errout2:
+ clk_put(st->clk);
+errout1:
+ iounmap(st->adc_base_spear6xx);
+ return ret;
+}
+
+static int spear_adc_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct spear_adc_state *st = iio_priv(indio_dev);
+
+ iio_device_unregister(indio_dev);
+ clk_disable_unprepare(st->clk);
+ clk_put(st->clk);
+ iounmap(st->adc_base_spear6xx);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spear_adc_dt_ids[] = {
+ { .compatible = "st,spear600-adc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, spear_adc_dt_ids);
+#endif
+
+static struct platform_driver spear_adc_driver = {
+ .probe = spear_adc_probe,
+ .remove = spear_adc_remove,
+ .driver = {
+ .name = SPEAR_ADC_MOD_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(spear_adc_dt_ids),
+ },
+};
+
+module_platform_driver(spear_adc_driver);
+
+MODULE_AUTHOR("Stefan Roese <sr@denx.de>");
+MODULE_DESCRIPTION("SPEAr ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/iio/addac/Kconfig b/drivers/staging/iio/addac/Kconfig
new file mode 100644
index 00000000000..e6795e0bed1
--- /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 GPIOLIB
+ 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 00000000000..4c768613369
--- /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 00000000000..75ddd4f801a
--- /dev/null
+++ b/drivers/staging/iio/addac/adt7316-i2c.c
@@ -0,0 +1,136 @@
+/*
+ * 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 <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+
+#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 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 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,
+ .id_table = adt7316_i2c_id,
+};
+module_i2c_driver(adt7316_driver);
+
+MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
+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 00000000000..e480abb72e4
--- /dev/null
+++ b/drivers/staging/iio/addac/adt7316-spi.c
@@ -0,0 +1,144 @@
+/*
+ * 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 <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+
+#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 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 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,
+ .id_table = adt7316_spi_id,
+};
+module_spi_driver(adt7316_driver);
+
+MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
+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 00000000000..5f1770e6f6c
--- /dev/null
+++ b/drivers/staging/iio/addac/adt7316.c
@@ -0,0 +1,2190 @@
+/*
+ * 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 <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/i2c.h>
+#include <linux/rtc.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/events.h>
+#include <linux/iio/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 specific 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 specific 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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ int enable;
+
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(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 (buf[0] == '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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config2;
+ u8 data;
+ int ret;
+
+ if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE))
+ return -EPERM;
+
+ ret = kstrtou8(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config2;
+ int ret;
+
+ config2 = chip->config2 & (~ADT7316_DISABLE_AVERAGING);
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config2;
+ int ret;
+
+ config2 = chip->config2 & (~ADT7316_EN_SMBUS_TIMEOUT);
+ if (buf[0] == '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_show_powerdown(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *dev_info = dev_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config1;
+ int ret;
+
+ config1 = chip->config1 & (~ADT7316_PD);
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config3;
+ int ret;
+
+ config3 = chip->config3 & (~ADT7316_ADCLK_22_5);
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config3;
+ int ret;
+
+ chip->dac_bits = 8;
+
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(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 (buf[0] != '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config3;
+ int ret;
+
+ config3 = chip->config3 & (~ADT7316_EN_IN_TEMP_PROP_DACA);
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config3;
+ int ret;
+
+ config3 = chip->config3 & (~ADT7316_EN_EX_TEMP_PROP_DACB);
+ if (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 dac_config;
+ u8 data;
+ int ret;
+
+ ret = kstrtou8(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 dac_config;
+ u8 data;
+ int ret;
+
+ if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA))
+ return -EPERM;
+
+ ret = kstrtou8(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 ldac_config;
+ u8 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 = kstrtou8(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_to_iio_dev(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_to_iio_dev(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 (buf[0] == '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_to_iio_dev(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_to_iio_dev(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 (buf[0] == '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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 ldac_config;
+ u8 data;
+ int ret;
+
+ if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) {
+ ret = kstrtou8(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 = kstrtou8(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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)
+{
+ int data;
+ u8 val;
+ int ret;
+
+ ret = kstrtoint(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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;
+ u16 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 = kstrtou16(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_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_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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u16 data;
+ int ret;
+ u8 mask;
+
+ ret = kstrtou16(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ int data;
+ u8 val;
+ int ret;
+
+ if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX &&
+ this_attr->address > ADT7316_EX_TEMP_LOW)
+ return -EPERM;
+
+ ret = kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ struct adt7316_chip_info *chip = iio_priv(dev_info);
+ u8 config1;
+ int ret;
+
+ config1 = chip->config1 & (~ADT7316_INT_EN);
+ if (buf[0] == '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 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 = devm_iio_device_alloc(dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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 = devm_request_threaded_irq(dev, chip->bus.irq,
+ NULL,
+ &adt7316_event_handler,
+ chip->bus.irq_flags |
+ IRQF_ONESHOT,
+ indio_dev->name,
+ indio_dev);
+ if (ret)
+ return ret;
+
+ 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)
+ return -EIO;
+
+ ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, chip->config3);
+ if (ret)
+ return -EIO;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "%s temperature sensor, ADC and DAC registered.\n",
+ indio_dev->name);
+
+ return 0;
+}
+EXPORT_SYMBOL(adt7316_probe);
+
+MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
+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 00000000000..ec50bf34628
--- /dev/null
+++ b/drivers/staging/iio/addac/adt7316.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/pm.h>
+
+#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);
+
+#endif
diff --git a/drivers/staging/iio/cdc/Kconfig b/drivers/staging/iio/cdc/Kconfig
new file mode 100644
index 00000000000..80211df8c57
--- /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 00000000000..a5fbabf5c8b
--- /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 00000000000..047af237630
--- /dev/null
+++ b/drivers/staging/iio/cdc/ad7150.c
@@ -0,0 +1,683 @@
+/*
+ * AD7150 capacitive sensor driver supporting AD7150/1/6
+ *
+ * Copyright 2010-2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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 IIO_CHAN_INFO_RAW:
+ 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,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ int ret;
+ u8 threshtype;
+ bool adaptive;
+ struct ad7150_chip_info *chip = iio_priv(indio_dev);
+
+ ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG);
+ if (ret < 0)
+ return ret;
+
+ threshtype = (ret >> 5) & 0x03;
+ adaptive = !!(ret & 0x80);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ if (dir == IIO_EV_DIR_RISING)
+ return adaptive && (threshtype == 0x1);
+ else
+ return adaptive && (threshtype == 0x0);
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ if (dir == IIO_EV_DIR_RISING)
+ return adaptive && (threshtype == 0x3);
+ else
+ return adaptive && (threshtype == 0x2);
+
+ case IIO_EV_TYPE_THRESH:
+ if (dir == IIO_EV_DIR_RISING)
+ return !adaptive && (threshtype == 0x1);
+ else
+ return !adaptive && (threshtype == 0x0);
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+/* lock should be held */
+static int ad7150_write_event_params(struct iio_dev *indio_dev,
+ unsigned int chan, enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ int ret;
+ u16 value;
+ u8 sens, timeout;
+ struct ad7150_chip_info *chip = iio_priv(indio_dev);
+ int rising = (dir == IIO_EV_DIR_RISING);
+ u64 event_code;
+
+ event_code = IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, chan, type, dir);
+
+ if (event_code != chip->current_event)
+ return 0;
+
+ switch (type) {
+ /* 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,
+ const struct iio_chan_spec *chan, enum iio_event_type type,
+ enum iio_event_direction dir, int state)
+{
+ u8 thresh_type, cfg, adaptive;
+ int ret;
+ struct ad7150_chip_info *chip = iio_priv(indio_dev);
+ int rising = (dir == IIO_EV_DIR_RISING);
+ u64 event_code;
+
+ /* Something must always be turned on */
+ if (state == 0)
+ return -EINVAL;
+
+ event_code = IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir);
+ 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 (type) {
+ 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, chan->channel, type, dir);
+error_ret:
+ mutex_unlock(&chip->state_lock);
+
+ return 0;
+}
+
+static int ad7150_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct ad7150_chip_info *chip = iio_priv(indio_dev);
+ int rising = (dir == IIO_EV_DIR_RISING);
+
+ /* Complex register sharing going on here */
+ switch (type) {
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ *val = chip->mag_sensitivity[rising][chan->channel];
+ return IIO_VAL_INT;
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ *val = chip->thresh_sensitivity[rising][chan->channel];
+ return IIO_VAL_INT;
+ case IIO_EV_TYPE_THRESH:
+ *val = chip->threshold[rising][chan->channel];
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad7150_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ int ret;
+ struct ad7150_chip_info *chip = iio_priv(indio_dev);
+ int rising = (dir == IIO_EV_DIR_RISING);
+
+ mutex_lock(&chip->state_lock);
+ switch (type) {
+ case IIO_EV_TYPE_MAG_ADAPTIVE:
+ chip->mag_sensitivity[rising][chan->channel] = val;
+ break;
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ chip->thresh_sensitivity[rising][chan->channel] = val;
+ break;
+ case IIO_EV_TYPE_THRESH:
+ chip->threshold[rising][chan->channel] = val;
+ break;
+ default:
+ ret = -EINVAL;
+ goto error_ret;
+ }
+
+ /* write back if active */
+ ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir);
+
+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_to_iio_dev(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_to_iio_dev(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);
+ enum iio_event_direction dir;
+ enum iio_event_type type;
+ int rising;
+ u8 data;
+ int ret;
+
+ type = IIO_EVENT_CODE_EXTRACT_TYPE(this_attr->address);
+ dir = IIO_EVENT_CODE_EXTRACT_DIR(this_attr->address);
+ rising = (dir == IIO_EV_DIR_RISING);
+
+ ret = kstrtou8(buf, 10, &data);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&chip->state_lock);
+ switch (type) {
+ 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, chan, type, dir);
+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_event_spec ad7150_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_MAG_ADAPTIVE,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec ad7150_channels[] = {
+ {
+ .type = IIO_CAPACITANCE,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW),
+ .event_spec = ad7150_events,
+ .num_event_specs = ARRAY_SIZE(ad7150_events),
+ }, {
+ .type = IIO_CAPACITANCE,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_AVERAGE_RAW),
+ .event_spec = ad7150_events,
+ .num_event_specs = ARRAY_SIZE(ad7150_events),
+ },
+};
+
+/*
+ * 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 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 = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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 = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL,
+ &ad7150_event_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "ad7150_irq1",
+ indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ if (client->dev.platform_data) {
+ ret = devm_request_threaded_irq(&client->dev, *(unsigned int *)
+ client->dev.platform_data,
+ NULL,
+ &ad7150_event_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "ad7150_irq2",
+ indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ return ret;
+
+ dev_info(&client->dev, "%s capacitive sensor registered,irq: %d\n",
+ id->name, client->irq);
+
+ return 0;
+}
+
+static int ad7150_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(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 = 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 00000000000..87110d940e9
--- /dev/null
+++ b/drivers/staging/iio/cdc/ad7152.c
@@ -0,0 +1,543 @@
+/*
+ * AD7152 capacitive sensor driver supporting AD7152/3
+ *
+ * Copyright 2010-2011a Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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 specific 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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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 IIO_CHAN_INFO_RAW:
+ /* 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_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }, {
+ .type = IIO_CAPACITANCE,
+ .differential = 1,
+ .indexed = 1,
+ .channel = 0,
+ .channel2 = 2,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }, {
+ .type = IIO_CAPACITANCE,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }, {
+ .type = IIO_CAPACITANCE,
+ .differential = 1,
+ .indexed = 1,
+ .channel = 1,
+ .channel2 = 3,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }
+};
+/*
+ * device probe and remove
+ */
+
+static int 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 = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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)
+ return ret;
+
+ dev_err(&client->dev, "%s capacitive sensor registered\n", id->name);
+
+ return 0;
+}
+
+static int ad7152_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(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 = 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 00000000000..e6e9eaa9eab
--- /dev/null
+++ b/drivers/staging/iio/cdc/ad7746.c
@@ -0,0 +1,791 @@
+/*
+ * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/stat.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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 specific 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;
+
+ union {
+ __be32 d32;
+ u8 d8[4];
+ } data ____cacheline_aligned;
+};
+
+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_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .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_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .address = AD7746_REG_VT_DATA_HIGH << 8 |
+ AD7746_VTSETUP_VTMD_VDD_MON,
+ },
+ [TEMP_INT] = {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = AD7746_REG_VT_DATA_HIGH << 8 |
+ AD7746_VTSETUP_VTMD_INT_TEMP,
+ },
+ [TEMP_EXT] = {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = AD7746_REG_VT_DATA_HIGH << 8 |
+ AD7746_VTSETUP_VTMD_EXT_TEMP,
+ },
+ [CIN1] = {
+ .type = IIO_CAPACITANCE,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = AD7746_REG_CAP_DATA_HIGH << 8,
+ },
+ [CIN1_DIFF] = {
+ .type = IIO_CAPACITANCE,
+ .differential = 1,
+ .indexed = 1,
+ .channel = 0,
+ .channel2 = 2,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = AD7746_REG_CAP_DATA_HIGH << 8 |
+ AD7746_CAPSETUP_CAPDIFF
+ },
+ [CIN2] = {
+ .type = IIO_CAPACITANCE,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .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_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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_to_iio_dev(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;
+
+ mutex_lock(&indio_dev->mlock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+ 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, &chip->data.d8[1]);
+
+ if (ret < 0)
+ goto out;
+
+ *val = (be32_to_cpu(chip->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 */
+ *val = 0;
+ *val2 = 488;
+ ret = IIO_VAL_INT_PLUS_NANO;
+ break;
+ case IIO_VOLTAGE:
+ /* 1170mV / 2^23 */
+ *val = 1170;
+ *val2 = 23;
+ ret = IIO_VAL_FRACTIONAL_LOG2;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ 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 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 = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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)
+ return ret;
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ return ret;
+
+ dev_info(&client->dev, "%s capacitive sensor registered\n", id->name);
+
+ return 0;
+}
+
+static int ad7746_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(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 = ad7746_remove,
+ .id_table = ad7746_id,
+};
+module_i2c_driver(ad7746_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+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 00000000000..ea8572d1df0
--- /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/frequency/Kconfig b/drivers/staging/iio/frequency/Kconfig
new file mode 100644
index 00000000000..93b7141b2c1
--- /dev/null
+++ b/drivers/staging/iio/frequency/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/frequency/Makefile b/drivers/staging/iio/frequency/Makefile
new file mode 100644
index 00000000000..147746176b9
--- /dev/null
+++ b/drivers/staging/iio/frequency/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/frequency/ad5930.c b/drivers/staging/iio/frequency/ad5930.c
new file mode 100644
index 00000000000..a4aeee6ffdf
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad5930.c
@@ -0,0 +1,140 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_transfer xfer;
+ int ret;
+ struct ad5903_config *config = (struct ad5903_config *)buf;
+ struct iio_dev *idev = dev_to_iio_dev(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);
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 ad5930_probe(struct spi_device *spi)
+{
+ struct ad5930_state *st;
+ struct iio_dev *idev;
+ int ret = 0;
+
+ idev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!idev)
+ return -ENOMEM;
+ 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)
+ return ret;
+ spi->max_speed_hz = 2000000;
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 16;
+ spi_setup(spi);
+
+ return 0;
+}
+
+static int ad5930_remove(struct spi_device *spi)
+{
+ iio_device_unregister(spi_get_drvdata(spi));
+
+ return 0;
+}
+
+static struct spi_driver ad5930_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ad5930_probe,
+ .remove = 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/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c
new file mode 100644
index 00000000000..c7d0307c8e7
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9832.c
@@ -0,0 +1,354 @@
+/*
+ * AD9832 SPI DDS driver
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <asm/div64.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_to_iio_dev(dev);
+ struct ad9832_state *st = iio_priv(indio_dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int ret;
+ unsigned long val;
+
+ ret = kstrtoul(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_out_altvoltage0_frequency0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase2.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase3.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_pincontrol_en.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_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 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 = devm_regulator_get(&spi->dev, "vcc");
+ if (!IS_ERR(reg)) {
+ ret = regulator_enable(reg);
+ if (ret)
+ return ret;
+ }
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, 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_disable_reg;
+ }
+
+ ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3);
+ 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(reg))
+ regulator_disable(reg);
+
+ return ret;
+}
+
+static int 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);
+
+ 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 = ad9832_remove,
+ .id_table = ad9832_id,
+};
+module_spi_driver(ad9832_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("Analog Devices AD9832/AD9835 DDS");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/iio/frequency/ad9832.h b/drivers/staging/iio/frequency/ad9832.h
new file mode 100644
index 00000000000..386f4dc8c9a
--- /dev/null
+++ b/drivers/staging/iio/frequency/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 {
+ __be16 freq_data[4]____cacheline_aligned;
+ __be16 phase_data[2];
+ __be16 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/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c
new file mode 100644
index 00000000000..86cda617609
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9834.c
@@ -0,0 +1,457 @@
+/*
+ * AD9833/AD9834/AD9837/AD9838 SPI DDS driver
+ *
+ * Copyright 2010-2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/list.h>
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <asm/div64.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_to_iio_dev(dev);
+ struct ad9834_state *st = iio_priv(indio_dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ int ret;
+ unsigned long val;
+
+ ret = kstrtoul(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_to_iio_dev(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_to_iio_dev(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(out_altvoltage0_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_to_iio_dev(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(out_altvoltage0_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_out_altvoltage0_frequency0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_pincontrol_en.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out_enable.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out1_enable.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out0_wavetype.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out1_wavetype.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out0_wavetype_available.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out1_wavetype_available.dev_attr.attr,
+ NULL,
+};
+
+static struct attribute *ad9833_attributes[] = {
+ &iio_dev_attr_out_altvoltage0_frequency0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr,
+ &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out_enable.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_out0_wavetype.dev_attr.attr,
+ &iio_dev_attr_out_altvoltage0_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 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 = devm_regulator_get(&spi->dev, "vcc");
+ if (!IS_ERR(reg)) {
+ ret = regulator_enable(reg);
+ if (ret)
+ return ret;
+ }
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, 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_disable_reg;
+ }
+
+ ret = ad9834_write_frequency(st, AD9834_REG_FREQ0, pdata->freq0);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9834_write_frequency(st, AD9834_REG_FREQ1, pdata->freq1);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9834_write_phase(st, AD9834_REG_PHASE0, pdata->phase0);
+ if (ret)
+ goto error_disable_reg;
+
+ ret = ad9834_write_phase(st, AD9834_REG_PHASE1, pdata->phase1);
+ 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(reg))
+ regulator_disable(reg);
+
+ return ret;
+}
+
+static int 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);
+
+ 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 = ad9834_remove,
+ .id_table = ad9834_id,
+};
+module_spi_driver(ad9834_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+MODULE_DESCRIPTION("Analog Devices AD9833/AD9834/AD9837/AD9838 DDS");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/staging/iio/frequency/ad9834.h b/drivers/staging/iio/frequency/ad9834.h
new file mode 100644
index 00000000000..8ca6e52bae6
--- /dev/null
+++ b/drivers/staging/iio/frequency/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.
+ */
+ __be16 data ____cacheline_aligned;
+ __be16 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/frequency/ad9850.c b/drivers/staging/iio/frequency/ad9850.c
new file mode 100644
index 00000000000..af877ff680e
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9850.c
@@ -0,0 +1,126 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_transfer xfer;
+ int ret;
+ struct ad9850_config *config = (struct ad9850_config *)buf;
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct ad9850_state *st = iio_priv(idev);
+
+ xfer.len = len;
+ xfer.tx_buf = config;
+ mutex_lock(&st->lock);
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 ad9850_probe(struct spi_device *spi)
+{
+ struct ad9850_state *st;
+ struct iio_dev *idev;
+ int ret = 0;
+
+ idev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!idev)
+ return -ENOMEM;
+ 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)
+ return ret;
+ spi->max_speed_hz = 2000000;
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 16;
+ spi_setup(spi);
+
+ return 0;
+}
+
+static int ad9850_remove(struct spi_device *spi)
+{
+ iio_device_unregister(spi_get_drvdata(spi));
+
+ return 0;
+}
+
+static struct spi_driver ad9850_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ad9850_probe,
+ .remove = 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/frequency/ad9852.c b/drivers/staging/iio/frequency/ad9852.c
new file mode 100644
index 00000000000..11e4367375d
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9852.c
@@ -0,0 +1,253 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_transfer xfer;
+ int ret;
+ struct ad9852_config *config = (struct ad9852_config *)buf;
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct ad9852_state *st = iio_priv(idev);
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->phajst0[0];
+ mutex_lock(&st->lock);
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->phajst1[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 6;
+ xfer.tx_buf = &config->fretun1[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 6;
+ xfer.tx_buf = &config->fretun2[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 6;
+ xfer.tx_buf = &config->dltafre[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->updtclk[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 4;
+ xfer.tx_buf = &config->ramprat[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->control[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->outpskm[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 2;
+ xfer.tx_buf = &config->outpskr[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->daccntl[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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_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;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 ad9852_probe(struct spi_device *spi)
+{
+ struct ad9852_state *st;
+ struct iio_dev *idev;
+ int ret = 0;
+
+ idev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!idev)
+ return -ENOMEM;
+ 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)
+ return ret;
+ spi->max_speed_hz = 2000000;
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 8;
+ spi_setup(spi);
+ ad9852_init(st);
+
+ return 0;
+}
+
+static int ad9852_remove(struct spi_device *spi)
+{
+ iio_device_unregister(spi_get_drvdata(spi));
+
+ return 0;
+}
+
+static struct spi_driver ad9852_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ad9852_probe,
+ .remove = 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/frequency/ad9910.c b/drivers/staging/iio/frequency/ad9910.c
new file mode 100644
index 00000000000..755e0482681
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9910.c
@@ -0,0 +1,371 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_transfer xfer;
+ int ret;
+ struct ad9910_config *config = (struct ad9910_config *)buf;
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct ad9910_state *st = iio_priv(idev);
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->auxdac[0];
+ mutex_lock(&st->lock);
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->ioupd[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->ftw[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->pow[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->asf[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->multc[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->dig_rampl[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->dig_ramps[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->dig_rampr[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep0[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep1[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep2[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep3[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep4[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep5[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep6[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 9;
+ xfer.tx_buf = &config->sin_tonep7[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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_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 = &cfr;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 = &cfr;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 = &cfr;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 ad9910_probe(struct spi_device *spi)
+{
+ struct ad9910_state *st;
+ struct iio_dev *idev;
+ int ret = 0;
+
+ idev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!idev)
+ return -ENOMEM;
+ 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)
+ return ret;
+ spi->max_speed_hz = 2000000;
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 8;
+ spi_setup(spi);
+ ad9910_init(st);
+ return 0;
+}
+
+static int ad9910_remove(struct spi_device *spi)
+{
+ iio_device_unregister(spi_get_drvdata(spi));
+
+ return 0;
+}
+
+static struct spi_driver ad9910_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ad9910_probe,
+ .remove = 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/frequency/ad9951.c b/drivers/staging/iio/frequency/ad9951.c
new file mode 100644
index 00000000000..5e8990a0210
--- /dev/null
+++ b/drivers/staging/iio/frequency/ad9951.c
@@ -0,0 +1,209 @@
+/*
+ * 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 <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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_transfer xfer;
+ int ret;
+ struct ad9951_config *config = (struct ad9951_config *)buf;
+ struct iio_dev *idev = dev_to_iio_dev(dev);
+ struct ad9951_state *st = iio_priv(idev);
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->asf[0];
+ mutex_lock(&st->lock);
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 2;
+ xfer.tx_buf = &config->arr[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 5;
+ xfer.tx_buf = &config->ftw0[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ if (ret)
+ goto error_ret;
+
+ xfer.len = 3;
+ xfer.tx_buf = &config->ftw1[0];
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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_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 = &cfr;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 = &cfr;
+
+ ret = spi_sync_transfer(st->sdev, &xfer, 1);
+ 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 ad9951_probe(struct spi_device *spi)
+{
+ struct ad9951_state *st;
+ struct iio_dev *idev;
+ int ret = 0;
+
+ idev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!idev)
+ return -ENOMEM;
+ 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)
+ return ret;
+ spi->max_speed_hz = 2000000;
+ spi->mode = SPI_MODE_3;
+ spi->bits_per_word = 8;
+ spi_setup(spi);
+ ad9951_init(st);
+ return 0;
+}
+
+static int ad9951_remove(struct spi_device *spi)
+{
+ iio_device_unregister(spi_get_drvdata(spi));
+
+ return 0;
+}
+
+static struct spi_driver ad9951_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ad9951_probe,
+ .remove = 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/frequency/dds.h b/drivers/staging/iio/frequency/dds.h
new file mode 100644
index 00000000000..c3342f6e052
--- /dev/null
+++ b/drivers/staging/iio/frequency/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/.../out_altvoltageX_frequencyY
+ */
+
+#define IIO_DEV_ATTR_FREQ(_channel, _num, _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_frequency##_num, \
+ _mode, _show, _store, _addr)
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_frequencyY_scale
+ */
+
+#define IIO_CONST_ATTR_FREQ_SCALE(_channel, _string) \
+ IIO_CONST_ATTR(out_altvoltage##_channel##_frequency_scale, _string)
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_frequencysymbol
+ */
+
+#define IIO_DEV_ATTR_FREQSYMBOL(_channel, _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_frequencysymbol, \
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_phaseY
+ */
+
+#define IIO_DEV_ATTR_PHASE(_channel, _num, _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_phase##_num, \
+ _mode, _show, _store, _addr)
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_phaseY_scale
+ */
+
+#define IIO_CONST_ATTR_PHASE_SCALE(_channel, _string) \
+ IIO_CONST_ATTR(out_altvoltage##_channel##_phase_scale, _string)
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_phasesymbol
+ */
+
+#define IIO_DEV_ATTR_PHASESYMBOL(_channel, _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_phasesymbol, \
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_en
+ */
+
+#define IIO_DEV_ATTR_PINCONTROL_EN(_channel, _mode, _show, _store, _addr)\
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_en, \
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_frequency_en
+ */
+
+#define IIO_DEV_ATTR_PINCONTROL_FREQ_EN(_channel, _mode, _show, _store, _addr)\
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_frequency_en,\
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_phase_en
+ */
+
+#define IIO_DEV_ATTR_PINCONTROL_PHASE_EN(_channel, _mode, _show, _store, _addr)\
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_phase_en, \
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_out_enable
+ */
+
+#define IIO_DEV_ATTR_OUT_ENABLE(_channel, _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_out_enable, \
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_outY_enable
+ */
+
+#define IIO_DEV_ATTR_OUTY_ENABLE(_channel, _output, \
+ _mode, _show, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_out##_output##_enable,\
+ _mode, _show, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype
+ */
+
+#define IIO_DEV_ATTR_OUT_WAVETYPE(_channel, _output, _store, _addr) \
+ IIO_DEVICE_ATTR(out_altvoltage##_channel##_out##_output##_wavetype,\
+ S_IWUSR, NULL, _store, _addr);
+
+/**
+ * /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype_available
+ */
+
+#define IIO_CONST_ATTR_OUT_WAVETYPES_AVAILABLE(_channel, _output, _modes)\
+ IIO_CONST_ATTR( \
+ out_altvoltage##_channel##_out##_output##_wavetype_available, _modes);
diff --git a/drivers/staging/iio/gyro/Kconfig b/drivers/staging/iio/gyro/Kconfig
new file mode 100644
index 00000000000..88b199bb292
--- /dev/null
+++ b/drivers/staging/iio/gyro/Kconfig
@@ -0,0 +1,13 @@
+#
+# 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.
+
+endmenu
diff --git a/drivers/staging/iio/gyro/Makefile b/drivers/staging/iio/gyro/Makefile
new file mode 100644
index 00000000000..cf22d6d55e2
--- /dev/null
+++ b/drivers/staging/iio/gyro/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for digital gyroscope sensor drivers
+#
+
+adis16060-y := adis16060_core.o
+obj-$(CONFIG_ADIS16060) += adis16060.o
diff --git a/drivers/staging/iio/gyro/adis16060_core.c b/drivers/staging/iio/gyro/adis16060_core.c
new file mode 100644
index 00000000000..d5d395c2e3e
--- /dev/null
+++ b/drivers/staging/iio/gyro/adis16060_core.c
@@ -0,0 +1,245 @@
+/*
+ * ADIS16060 Wide Bandwidth Yaw Rate Gyroscope with SPI driver
+ *
+ * Copyright 2010 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/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 IIO_CHAN_INFO_RAW:
+ /* 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,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = ADIS16060_GYRO,
+ }, {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = ADIS16060_AIN1,
+ }, {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .address = ADIS16060_AIN2,
+ }, {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE),
+ .address = ADIS16060_TEMP_OUT,
+ }
+};
+
+static int 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 = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ /* 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 = devm_iio_device_register(&spi->dev, indio_dev);
+ if (ret)
+ return ret;
+
+ adis16060_iio_dev = indio_dev;
+ return 0;
+}
+
+static int 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,
+};
+
+static struct spi_driver adis16060_w_driver = {
+ .driver = {
+ .name = "adis16060_w",
+ .owner = THIS_MODULE,
+ },
+ .probe = adis16060_w_probe,
+ .remove = 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/iio_dummy_evgen.c b/drivers/staging/iio/iio_dummy_evgen.c
new file mode 100644
index 00000000000..132d278c501
--- /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 <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/sysfs.h>
+
+#include "iio_dummy_evgen.h"
+#include <linux/iio/iio.h>
+#include <linux/iio/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]) {
+ 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 <jic23@kernel.org>");
+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 00000000000..d8845e2288b
--- /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_simple_dummy.c b/drivers/staging/iio/iio_simple_dummy.c
new file mode 100644
index 00000000000..fd334a03a49
--- /dev/null
+++ b/drivers/staging/iio/iio_simple_dummy.c
@@ -0,0 +1,588 @@
+/**
+ * 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 <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/iio/buffer.h>
+#include "iio_simple_dummy.h"
+
+/*
+ * A few elements needed to fake a bus for this driver
+ * Note instances parameter 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.000013 */
+};
+
+#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS
+
+/*
+ * simple event - triggered when value rises above
+ * a threshold
+ */
+static const struct iio_event_spec iio_dummy_event = {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE),
+};
+
+#endif
+
+/*
+ * 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 const 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_separate =
+ /*
+ * in_voltage0_raw
+ * Raw (unscaled no bias removal etc) measurement
+ * from the device.
+ */
+ BIT(IIO_CHAN_INFO_RAW) |
+ /*
+ * in_voltage0_offset
+ * Offset for userspace to apply prior to scale
+ * when converting to standard units (microvolts)
+ */
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ /*
+ * in_voltage0_scale
+ * Multipler for userspace to apply post offset
+ * when converting to standard units (microvolts)
+ */
+ BIT(IIO_CHAN_INFO_SCALE),
+ /*
+ * sampling_frequency
+ * The frequency in Hz at which the channels are sampled
+ */
+ .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ /* 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
+ .event_spec = &iio_dummy_event,
+ .num_event_specs = 1,
+#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,
+ /*
+ * in_voltage1-voltage2_raw
+ * Raw (unscaled no bias removal etc) measurement
+ * from the device.
+ */
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ /*
+ * in_voltage-voltage_scale
+ * Shared version of scale - shared by differential
+ * input channels of type IIO_VOLTAGE.
+ */
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ /*
+ * sampling_frequency
+ * The frequency in Hz at which the channels are sampled
+ */
+ .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_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .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_separate = BIT(IIO_CHAN_INFO_RAW) |
+ /*
+ * Internal bias and gain correction values. Applied
+ * by the hardware or driver prior to userspace
+ * seeing the readings. Typically part of hardware
+ * calibration.
+ */
+ BIT(IIO_CHAN_INFO_CALIBSCALE) |
+ BIT(IIO_CHAN_INFO_CALIBBIAS),
+ .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_index = accelx,
+ .scan_type = { /* Description of storage in buffer */
+ .sign = 's', /* signed */
+ .realbits = 16, /* 16 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,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .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 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 IIO_CHAN_INFO_RAW: /* 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;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = 3;
+ *val2 = 33;
+ ret = IIO_VAL_INT_PLUS_NANO;
+ 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 written
+ * @val: first element of value to set (typically INT)
+ * @val2: second element of value to set (typically MICRO)
+ * @mask: what we actually want to write 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 IIO_CHAN_INFO_RAW:
+ 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_CALIBSCALE:
+ 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;
+ case IIO_CHAN_INFO_CALIBBIAS:
+ mutex_lock(&st->lock);
+ st->accel_calibbias = val;
+ mutex_unlock(&st->lock);
+ return 0;
+
+ 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 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_device_alloc(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 and register the channels with the
+ * buffer, but avoid the output channel being registered by reducing the
+ * number of channels by 1.
+ */
+ ret = iio_simple_dummy_configure_buffer(indio_dev,
+ iio_dummy_channels, 5);
+ if (ret < 0)
+ goto error_unregister_events;
+
+ ret = iio_device_register(indio_dev);
+ if (ret < 0)
+ goto error_unconfigure_buffer;
+
+ return 0;
+error_unconfigure_buffer:
+ iio_simple_dummy_unconfigure_buffer(indio_dev);
+error_unregister_events:
+ iio_simple_dummy_events_unregister(indio_dev);
+error_free_device:
+ iio_device_free(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_simple_dummy_unconfigure_buffer(indio_dev);
+
+ ret = iio_simple_dummy_events_unregister(indio_dev);
+ if (ret)
+ goto error_ret;
+
+ /* Free all structures */
+ iio_device_free(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 <jic23@kernel.org>");
+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 00000000000..b126196cdf3
--- /dev/null
+++ b/drivers/staging/iio/iio_simple_dummy.h
@@ -0,0 +1,120 @@
+/**
+ * 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 <linux/kernel.h>
+
+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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir);
+
+int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state);
+
+int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val,
+ int *val2);
+
+int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int val,
+ int val2);
+
+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,
+ const struct iio_chan_spec *channels, unsigned int num_channels);
+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,
+ const struct iio_chan_spec *channels, unsigned int num_channels)
+{
+ 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 00000000000..46c134b2a5d
--- /dev/null
+++ b/drivers/staging/iio/iio_simple_dummy_buffer.c
@@ -0,0 +1,199 @@
+/**
+ * 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 <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/bitmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/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 occurring,
+ * 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;
+ int len = 0;
+ u16 *data;
+
+ data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+ if (data == NULL)
+ goto done;
+
+ 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.
+ * software 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 fiddliest option by far.
+ * Here let's 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++) {
+ j = find_next_bit(indio_dev->active_scan_mask,
+ indio_dev->masklength, j);
+ /* random access read from the 'device' */
+ data[i] = fakedata[j];
+ len += 2;
+ }
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data, iio_get_time_ns());
+
+ kfree(data);
+
+done:
+ /*
+ * 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_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,
+ const struct iio_chan_spec *channels, unsigned int num_channels)
+{
+ 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;
+ }
+
+ iio_device_attach_buffer(indio_dev, 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 bottom 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;
+
+ ret = iio_buffer_register(indio_dev, channels, num_channels);
+ if (ret)
+ goto error_dealloc_pollfunc;
+
+ return 0;
+
+error_dealloc_pollfunc:
+ iio_dealloc_pollfunc(indio_dev->pollfunc);
+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_buffer_unregister(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 00000000000..812ebd05a7f
--- /dev/null
+++ b/drivers/staging/iio/iio_simple_dummy_events.c
@@ -0,0 +1,210 @@
+/**
+ * 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 <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/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
+ * @chan: channel for the event whose state is being queried
+ * @type: type of the event whose state is being queried
+ * @dir: direction of the vent whose state is 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ 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
+ * @chan: channel for the event whose state is being set
+ * @type: type of the event whose state is being set
+ * @dir: direction of the vent whose state is being set
+ * @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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ 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 (chan->type) {
+ case IIO_VOLTAGE:
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ if (dir == 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
+ * @chan: channel for the event whose value is being read
+ * @type: type of the event whose value is being read
+ * @dir: direction of the vent whose value is being read
+ * @info: info type of the event whose value is being read
+ * @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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct iio_dummy_state *st = iio_priv(indio_dev);
+
+ *val = st->event_val;
+
+ return IIO_VAL_INT;
+}
+
+/**
+ * iio_simple_dummy_write_event_value() - set value associate with event
+ * @indio_dev: device instance specific data
+ * @chan: channel for the event whose value is being set
+ * @type: type of the event whose value is being set
+ * @dir: direction of the vent whose value is being set
+ * @info: info type of 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,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ 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 occurred 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 00000000000..dd97b6bb3fd
--- /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_KFIFO_BUF
+ 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 00000000000..7604d786583
--- /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 00000000000..2b96665da8a
--- /dev/null
+++ b/drivers/staging/iio/impedance-analyzer/ad5933.c
@@ -0,0 +1,821 @@
+/*
+ * AD5933 AD5934 Impedance Converter, Network Analyzer
+ *
+ * Copyright 2011 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/sysfs.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <asm/div64.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.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 const struct iio_chan_spec ad5933_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .address = AD5933_REG_TEMP_DATA,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 14,
+ .storagebits = 16,
+ },
+ }, { /* Ring Channels */
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 0,
+ .extend_name = "real_raw",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = AD5933_REG_REAL_DATA,
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ },
+ }, {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .channel = 0,
+ .extend_name = "imag_raw",
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = AD5933_REG_IMAG_DATA,
+ .scan_index = 1,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ },
+ },
+};
+
+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_to_iio_dev(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_to_iio_dev(dev);
+ struct ad5933_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;
+
+ 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_to_iio_dev(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_to_iio_dev(dev);
+ struct ad5933_state *st = iio_priv(indio_dev);
+ struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
+ u16 val;
+ int i, ret = 0;
+ unsigned short dat;
+
+ if (this_attr->address != AD5933_IN_PGA_GAIN) {
+ ret = kstrtou16(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, (u16)0, (u16)0x7FF);
+ 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, (u16)0, (u16)511);
+ 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 IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+ 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);
+ int ret;
+
+ if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength))
+ return -EINVAL;
+
+ 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)
+{
+ struct iio_buffer *buffer;
+
+ buffer = iio_kfifo_allocate(indio_dev);
+ if (!buffer)
+ return -ENOMEM;
+
+ iio_device_attach_buffer(indio_dev, buffer);
+
+ /* 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);
+ 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]);
+ }
+ iio_push_to_buffers(indio_dev, buf);
+ } 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 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;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, 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 = devm_regulator_get(&client->dev, "vcc");
+ if (!IS_ERR(st->reg)) {
+ ret = regulator_enable(st->reg);
+ if (ret)
+ return ret;
+ 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_kfifo_free(indio_dev->buffer);
+error_disable_reg:
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ return ret;
+}
+
+static 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_kfifo_free(indio_dev->buffer);
+ if (!IS_ERR(st->reg))
+ regulator_disable(st->reg);
+
+ 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 = ad5933_remove,
+ .id_table = ad5933_id,
+};
+module_i2c_driver(ad5933_driver);
+
+MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
+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 00000000000..b140e42d67c
--- /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/light/Kconfig b/drivers/staging/iio/light/Kconfig
new file mode 100644
index 00000000000..ca8d6e66c89
--- /dev/null
+++ b/drivers/staging/iio/light/Kconfig
@@ -0,0 +1,43 @@
+#
+# Light sensors
+#
+menu "Light sensors"
+
+config SENSORS_ISL29018
+ tristate "ISL 29018 light and proximity sensor"
+ depends on I2C
+ select REGMAP_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_ISL29028
+ tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Provides driver for the Intersil's ISL29028 device.
+ This driver supports the sysfs interface to get the ALS, IR intensity,
+ Proximity value via iio. The ISL29028 provides the concurrent sensing
+ of ambient light and proximity.
+
+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.
+
+config TSL2x7x
+ tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors"
+ depends on I2C
+ help
+ Support for: tsl2571, tsl2671, tmd2671, tsl2771, tmd2771, tsl2572, tsl2672,
+ tmd2672, tsl2772, tmd2772 devices.
+ Provides iio_events and direct access via sysfs.
+
+endmenu
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
new file mode 100644
index 00000000000..9960fdf7c15
--- /dev/null
+++ b/drivers/staging/iio/light/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for industrial I/O Light sensors
+#
+
+obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
+obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o
+obj-$(CONFIG_TSL2583) += tsl2583.o
+obj-$(CONFIG_TSL2x7x) += tsl2x7x_core.o
diff --git a/drivers/staging/iio/light/isl29018.c b/drivers/staging/iio/light/isl29018.c
new file mode 100644
index 00000000000..3660a43b5f0
--- /dev/null
+++ b/drivers/staging/iio/light/isl29018.c
@@ -0,0 +1,662 @@
+/*
+ * 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 <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/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_REG_TEST 0x08
+#define ISL29018_TEST_SHIFT 0
+#define ISL29018_TEST_MASK (0xFF << ISL29018_TEST_SHIFT)
+
+struct isl29018_chip {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ unsigned int lux_scale;
+ unsigned int lux_uscale;
+ unsigned int range;
+ unsigned int adc_bit;
+ int prox_scheme;
+ bool suspended;
+};
+
+static int isl29018_set_range(struct isl29018_chip *chip, 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 regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII,
+ COMMANDII_RANGE_MASK, i << COMMANDII_RANGE_SHIFT);
+}
+
+static int isl29018_set_resolution(struct isl29018_chip *chip,
+ 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 regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII,
+ COMMANDII_RESOLUTION_MASK,
+ i << COMMANDII_RESOLUTION_SHIFT);
+}
+
+static int isl29018_read_sensor_input(struct isl29018_chip *chip, int mode)
+{
+ int status;
+ unsigned int lsb;
+ unsigned int msb;
+
+ /* Set mode */
+ status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1,
+ mode << COMMMAND1_OPMODE_SHIFT);
+ if (status) {
+ dev_err(chip->dev,
+ "Error in setting operating mode err %d\n", status);
+ return status;
+ }
+ msleep(CONVERSION_TIME_MS);
+ status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_LSB, &lsb);
+ if (status < 0) {
+ dev_err(chip->dev,
+ "Error in reading LSB DATA with err %d\n", status);
+ return status;
+ }
+
+ status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_MSB, &msb);
+ if (status < 0) {
+ dev_err(chip->dev,
+ "Error in reading MSB DATA with error %d\n", status);
+ return status;
+ }
+ dev_vdbg(chip->dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb);
+
+ return (msb << 8) | lsb;
+}
+
+static int isl29018_read_lux(struct isl29018_chip *chip, int *lux)
+{
+ int lux_data;
+ unsigned int data_x_range, lux_unshifted;
+
+ lux_data = isl29018_read_sensor_input(chip, COMMMAND1_OPMODE_ALS_ONCE);
+
+ if (lux_data < 0)
+ return lux_data;
+
+ /* To support fractional scaling, separate the unshifted lux
+ * into two calculations: int scaling and micro-scaling.
+ * lux_uscale ranges from 0-999999, so about 20 bits. Split
+ * the /1,000,000 in two to reduce the risk of over/underflow.
+ */
+ data_x_range = lux_data * chip->range;
+ lux_unshifted = data_x_range * chip->lux_scale;
+ lux_unshifted += data_x_range / 1000 * chip->lux_uscale / 1000;
+ *lux = lux_unshifted >> chip->adc_bit;
+
+ return 0;
+}
+
+static int isl29018_read_ir(struct isl29018_chip *chip, int *ir)
+{
+ int ir_data;
+
+ ir_data = isl29018_read_sensor_input(chip, COMMMAND1_OPMODE_IR_ONCE);
+
+ if (ir_data < 0)
+ return ir_data;
+
+ *ir = ir_data;
+
+ return 0;
+}
+
+static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme,
+ int *near_ir)
+{
+ int status;
+ int prox_data = -1;
+ int ir_data = -1;
+
+ /* Do proximity sensing with required scheme */
+ status = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII,
+ COMMANDII_SCHEME_MASK,
+ scheme << COMMANDII_SCHEME_SHIFT);
+ if (status) {
+ dev_err(chip->dev, "Error in setting operating mode\n");
+ return status;
+ }
+
+ prox_data = isl29018_read_sensor_input(chip,
+ 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(chip, 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_to_iio_dev(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_to_iio_dev(dev);
+ struct isl29018_chip *chip = iio_priv(indio_dev);
+ int status;
+ unsigned long lval;
+ unsigned int new_range;
+
+ if (kstrtoul(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(chip, lval, &new_range);
+ if (status < 0) {
+ mutex_unlock(&chip->lock);
+ dev_err(dev,
+ "Error in setting max range with err %d\n", status);
+ 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_to_iio_dev(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_to_iio_dev(dev);
+ struct isl29018_chip *chip = iio_priv(indio_dev);
+ int status;
+ unsigned int val;
+ unsigned int new_adc_bit;
+
+ if (kstrtouint(buf, 10, &val))
+ return -EINVAL;
+ if (!(val == 4 || val == 8 || val == 12 || val == 16)) {
+ dev_err(dev, "The resolution is not supported\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->lock);
+ status = isl29018_set_resolution(chip, val, &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_suppression(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct isl29018_chip *chip = iio_priv(indio_dev);
+
+ /* return the "proximity scheme" i.e. if the chip does on chip
+ infrared suppression (1 means perform on chip suppression) */
+ return sprintf(buf, "%d\n", chip->prox_scheme);
+}
+
+static ssize_t store_prox_infrared_suppression(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct isl29018_chip *chip = iio_priv(indio_dev);
+ int val;
+
+ if (kstrtoint(buf, 10, &val))
+ return -EINVAL;
+ if (!(val == 0 || val == 1)) {
+ dev_err(dev, "The mode is not supported\n");
+ return -EINVAL;
+ }
+
+ /* get the "proximity scheme" i.e. if the chip does on chip
+ infrared suppression (1 means perform on chip suppression) */
+ mutex_lock(&chip->lock);
+ chip->prox_scheme = val;
+ 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;
+ /* With no write_raw_get_fmt(), val2 is a MICRO fraction. */
+ chip->lux_uscale = val2;
+ ret = 0;
+ }
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+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);
+
+ mutex_lock(&chip->lock);
+ if (chip->suspended) {
+ mutex_unlock(&chip->lock);
+ return -EBUSY;
+ }
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = isl29018_read_lux(chip, val);
+ break;
+ case IIO_INTENSITY:
+ ret = isl29018_read_ir(chip, val);
+ break;
+ case IIO_PROXIMITY:
+ ret = isl29018_read_proximity_ir(chip,
+ 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;
+ *val2 = chip->lux_uscale;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ }
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static const struct iio_chan_spec isl29018_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .indexed = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_CALIBSCALE),
+ }, {
+ .type = IIO_INTENSITY,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .channel2 = IIO_MOD_LIGHT_IR,
+ }, {
+ /* Unindexed in current ABI. But perhaps it should be. */
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }
+};
+
+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_suppression,
+ S_IRUGO | S_IWUSR,
+ show_prox_infrared_suppression,
+ store_prox_infrared_suppression, 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_suppression),
+ NULL
+};
+
+static const struct attribute_group isl29108_group = {
+ .attrs = isl29018_attributes,
+};
+
+static int isl29018_chip_init(struct isl29018_chip *chip)
+{
+ int status;
+ int new_adc_bit;
+ unsigned int new_range;
+
+ /* 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 = regmap_write(chip->regmap, ISL29018_REG_TEST, 0x0);
+ if (status < 0) {
+ dev_err(chip->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 = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, 0);
+ if (status < 0) {
+ dev_err(chip->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(chip, chip->range, &new_range);
+ if (status < 0) {
+ dev_err(chip->dev, "Init of isl29018 fails\n");
+ return status;
+ }
+
+ status = isl29018_set_resolution(chip, 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 bool is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ISL29018_REG_ADD_DATA_LSB:
+ case ISL29018_REG_ADD_DATA_MSB:
+ case ISL29018_REG_ADD_COMMAND1:
+ case ISL29018_REG_TEST:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * isl29018_regmap_config: regmap configuration.
+ * Use RBTREE mechanism for caching.
+ */
+static const struct regmap_config isl29018_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = is_volatile_reg,
+ .max_register = ISL29018_REG_TEST,
+ .num_reg_defaults_raw = ISL29018_REG_TEST + 1,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int 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 = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (indio_dev == NULL) {
+ dev_err(&client->dev, "iio allocation fails\n");
+ return -ENOMEM;
+ }
+ chip = iio_priv(indio_dev);
+
+ i2c_set_clientdata(client, indio_dev);
+ chip->dev = &client->dev;
+
+ mutex_init(&chip->lock);
+
+ chip->lux_scale = 1;
+ chip->lux_uscale = 0;
+ chip->range = 1000;
+ chip->adc_bit = 16;
+ chip->suspended = false;
+
+ chip->regmap = devm_regmap_init_i2c(client, &isl29018_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ err = PTR_ERR(chip->regmap);
+ dev_err(chip->dev, "regmap initialization failed: %d\n", err);
+ return err;
+ }
+
+ err = isl29018_chip_init(chip);
+ if (err)
+ return err;
+
+ 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 = devm_iio_device_register(&client->dev, indio_dev);
+ if (err) {
+ dev_err(&client->dev, "iio registration fails\n");
+ return err;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int isl29018_suspend(struct device *dev)
+{
+ struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev));
+
+ mutex_lock(&chip->lock);
+
+ /* Since this driver uses only polling commands, we are by default in
+ * auto shutdown (ie, power-down) mode.
+ * So we do not have much to do here.
+ */
+ chip->suspended = true;
+
+ mutex_unlock(&chip->lock);
+ return 0;
+}
+
+static int isl29018_resume(struct device *dev)
+{
+ struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev));
+ int err;
+
+ mutex_lock(&chip->lock);
+
+ err = isl29018_chip_init(chip);
+ if (!err)
+ chip->suspended = false;
+
+ mutex_unlock(&chip->lock);
+ return err;
+}
+
+static SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend, isl29018_resume);
+#define ISL29018_PM_OPS (&isl29018_pm_ops)
+#else
+#define ISL29018_PM_OPS NULL
+#endif
+
+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 = "isil,isl29018", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, isl29018_of_match);
+
+static struct i2c_driver isl29018_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "isl29018",
+ .pm = ISL29018_PM_OPS,
+ .owner = THIS_MODULE,
+ .of_match_table = isl29018_of_match,
+ },
+ .probe = isl29018_probe,
+ .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/isl29028.c b/drivers/staging/iio/light/isl29028.c
new file mode 100644
index 00000000000..6014625920b
--- /dev/null
+++ b/drivers/staging/iio/light/isl29028.c
@@ -0,0 +1,561 @@
+/*
+ * IIO driver for the light sensor ISL29028.
+ * ISL29028 is Concurrent Ambient Light and Proximity Sensor
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#define CONVERSION_TIME_MS 100
+
+#define ISL29028_REG_CONFIGURE 0x01
+
+#define CONFIGURE_ALS_IR_MODE_ALS 0
+#define CONFIGURE_ALS_IR_MODE_IR BIT(0)
+#define CONFIGURE_ALS_IR_MODE_MASK BIT(0)
+
+#define CONFIGURE_ALS_RANGE_LOW_LUX 0
+#define CONFIGURE_ALS_RANGE_HIGH_LUX BIT(1)
+#define CONFIGURE_ALS_RANGE_MASK BIT(1)
+
+#define CONFIGURE_ALS_DIS 0
+#define CONFIGURE_ALS_EN BIT(2)
+#define CONFIGURE_ALS_EN_MASK BIT(2)
+
+#define CONFIGURE_PROX_DRIVE BIT(3)
+
+#define CONFIGURE_PROX_SLP_SH 4
+#define CONFIGURE_PROX_SLP_MASK (7 << CONFIGURE_PROX_SLP_SH)
+
+#define CONFIGURE_PROX_EN BIT(7)
+#define CONFIGURE_PROX_EN_MASK BIT(7)
+
+#define ISL29028_REG_INTERRUPT 0x02
+
+#define ISL29028_REG_PROX_DATA 0x08
+#define ISL29028_REG_ALSIR_L 0x09
+#define ISL29028_REG_ALSIR_U 0x0A
+
+#define ISL29028_REG_TEST1_MODE 0x0E
+#define ISL29028_REG_TEST2_MODE 0x0F
+
+#define ISL29028_NUM_REGS (ISL29028_REG_TEST2_MODE + 1)
+
+enum als_ir_mode {
+ MODE_NONE = 0,
+ MODE_ALS,
+ MODE_IR
+};
+
+struct isl29028_chip {
+ struct device *dev;
+ struct mutex lock;
+ struct regmap *regmap;
+
+ unsigned int prox_sampling;
+ bool enable_prox;
+
+ int lux_scale;
+ int als_ir_mode;
+};
+
+static int isl29028_set_proxim_sampling(struct isl29028_chip *chip,
+ unsigned int sampling)
+{
+ static unsigned int prox_period[] = {800, 400, 200, 100, 75, 50, 12, 0};
+ int sel;
+ unsigned int period = DIV_ROUND_UP(1000, sampling);
+
+ for (sel = 0; sel < ARRAY_SIZE(prox_period); ++sel) {
+ if (period >= prox_period[sel])
+ break;
+ }
+ return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_PROX_SLP_MASK, sel << CONFIGURE_PROX_SLP_SH);
+}
+
+static int isl29028_enable_proximity(struct isl29028_chip *chip, bool enable)
+{
+ int ret;
+ int val = 0;
+
+ if (enable)
+ val = CONFIGURE_PROX_EN;
+ ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_PROX_EN_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ /* Wait for conversion to be complete for first sample */
+ mdelay(DIV_ROUND_UP(1000, chip->prox_sampling));
+ return 0;
+}
+
+static int isl29028_set_als_scale(struct isl29028_chip *chip, int lux_scale)
+{
+ int val = (lux_scale == 2000) ? CONFIGURE_ALS_RANGE_HIGH_LUX :
+ CONFIGURE_ALS_RANGE_LOW_LUX;
+
+ return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_RANGE_MASK, val);
+}
+
+static int isl29028_set_als_ir_mode(struct isl29028_chip *chip,
+ enum als_ir_mode mode)
+{
+ int ret = 0;
+
+ switch (mode) {
+ case MODE_ALS:
+ ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_IR_MODE_MASK, CONFIGURE_ALS_IR_MODE_ALS);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_RANGE_MASK, CONFIGURE_ALS_RANGE_HIGH_LUX);
+ break;
+
+ case MODE_IR:
+ ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_IR_MODE_MASK, CONFIGURE_ALS_IR_MODE_IR);
+ break;
+
+ case MODE_NONE:
+ return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_EN_MASK, CONFIGURE_ALS_DIS);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* Enable the ALS/IR */
+ ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE,
+ CONFIGURE_ALS_EN_MASK, CONFIGURE_ALS_EN);
+ if (ret < 0)
+ return ret;
+
+ /* Need to wait for conversion time if ALS/IR mode enabled */
+ mdelay(CONVERSION_TIME_MS);
+ return 0;
+}
+
+static int isl29028_read_als_ir(struct isl29028_chip *chip, int *als_ir)
+{
+ unsigned int lsb;
+ unsigned int msb;
+ int ret;
+
+ ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_L, &lsb);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Error in reading register ALSIR_L err %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_U, &msb);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Error in reading register ALSIR_U err %d\n", ret);
+ return ret;
+ }
+
+ *als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF);
+ return 0;
+}
+
+static int isl29028_read_proxim(struct isl29028_chip *chip, int *prox)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(chip->regmap, ISL29028_REG_PROX_DATA, &data);
+ if (ret < 0) {
+ dev_err(chip->dev, "Error in reading register %d, error %d\n",
+ ISL29028_REG_PROX_DATA, ret);
+ return ret;
+ }
+ *prox = data;
+ return 0;
+}
+
+static int isl29028_proxim_get(struct isl29028_chip *chip, int *prox_data)
+{
+ int ret;
+
+ if (!chip->enable_prox) {
+ ret = isl29028_enable_proximity(chip, true);
+ if (ret < 0)
+ return ret;
+ chip->enable_prox = true;
+ }
+ return isl29028_read_proxim(chip, prox_data);
+}
+
+static int isl29028_als_get(struct isl29028_chip *chip, int *als_data)
+{
+ int ret;
+ int als_ir_data;
+
+ if (chip->als_ir_mode != MODE_ALS) {
+ ret = isl29028_set_als_ir_mode(chip, MODE_ALS);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Error in enabling ALS mode err %d\n", ret);
+ return ret;
+ }
+ chip->als_ir_mode = MODE_ALS;
+ }
+
+ ret = isl29028_read_als_ir(chip, &als_ir_data);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * convert als data count to lux.
+ * if lux_scale = 125, lux = count * 0.031
+ * if lux_scale = 2000, lux = count * 0.49
+ */
+ if (chip->lux_scale == 125)
+ als_ir_data = (als_ir_data * 31) / 1000;
+ else
+ als_ir_data = (als_ir_data * 49) / 100;
+
+ *als_data = als_ir_data;
+ return 0;
+}
+
+static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data)
+{
+ int ret;
+
+ if (chip->als_ir_mode != MODE_IR) {
+ ret = isl29028_set_als_ir_mode(chip, MODE_IR);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Error in enabling IR mode err %d\n", ret);
+ return ret;
+ }
+ chip->als_ir_mode = MODE_IR;
+ }
+ return isl29028_read_als_ir(chip, ir_data);
+}
+
+/* Channel IO */
+static int isl29028_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val, int val2, long mask)
+{
+ struct isl29028_chip *chip = iio_priv(indio_dev);
+ int ret = -EINVAL;
+
+ mutex_lock(&chip->lock);
+ switch (chan->type) {
+ case IIO_PROXIMITY:
+ if (mask != IIO_CHAN_INFO_SAMP_FREQ) {
+ dev_err(chip->dev,
+ "proximity: mask value 0x%08lx not supported\n",
+ mask);
+ break;
+ }
+ if (val < 1 || val > 100) {
+ dev_err(chip->dev,
+ "Samp_freq %d is not in range[1:100]\n", val);
+ break;
+ }
+ ret = isl29028_set_proxim_sampling(chip, val);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Setting proximity samp_freq fail, err %d\n",
+ ret);
+ break;
+ }
+ chip->prox_sampling = val;
+ break;
+
+ case IIO_LIGHT:
+ if (mask != IIO_CHAN_INFO_SCALE) {
+ dev_err(chip->dev,
+ "light: mask value 0x%08lx not supported\n",
+ mask);
+ break;
+ }
+ if ((val != 125) && (val != 2000)) {
+ dev_err(chip->dev,
+ "lux scale %d is invalid [125, 2000]\n", val);
+ break;
+ }
+ ret = isl29028_set_als_scale(chip, val);
+ if (ret < 0) {
+ dev_err(chip->dev,
+ "Setting lux scale fail with error %d\n", ret);
+ break;
+ }
+ chip->lux_scale = val;
+ break;
+
+ default:
+ dev_err(chip->dev, "Unsupported channel type\n");
+ break;
+ }
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static int isl29028_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+ struct isl29028_chip *chip = iio_priv(indio_dev);
+ int ret = -EINVAL;
+
+ mutex_lock(&chip->lock);
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = isl29028_als_get(chip, val);
+ break;
+ case IIO_INTENSITY:
+ ret = isl29028_ir_get(chip, val);
+ break;
+ case IIO_PROXIMITY:
+ ret = isl29028_proxim_get(chip, val);
+ break;
+ default:
+ break;
+ }
+ if (ret < 0)
+ break;
+ ret = IIO_VAL_INT;
+ break;
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ if (chan->type != IIO_PROXIMITY)
+ break;
+ *val = chip->prox_sampling;
+ ret = IIO_VAL_INT;
+ break;
+
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type != IIO_LIGHT)
+ break;
+ *val = chip->lux_scale;
+ ret = IIO_VAL_INT;
+ break;
+
+ default:
+ dev_err(chip->dev, "mask value 0x%08lx not supported\n", mask);
+ break;
+ }
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static IIO_CONST_ATTR(in_proximity_sampling_frequency_available,
+ "1, 3, 5, 10, 13, 20, 83, 100");
+static IIO_CONST_ATTR(in_illuminance_scale_available, "125, 2000");
+
+#define ISL29028_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+#define ISL29028_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr)
+static struct attribute *isl29028_attributes[] = {
+ ISL29028_CONST_ATTR(in_proximity_sampling_frequency_available),
+ ISL29028_CONST_ATTR(in_illuminance_scale_available),
+ NULL,
+};
+
+static const struct attribute_group isl29108_group = {
+ .attrs = isl29028_attributes,
+};
+
+static const struct iio_chan_spec isl29028_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ }, {
+ .type = IIO_INTENSITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ }, {
+ .type = IIO_PROXIMITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ }
+};
+
+static const struct iio_info isl29028_info = {
+ .attrs = &isl29108_group,
+ .driver_module = THIS_MODULE,
+ .read_raw = &isl29028_read_raw,
+ .write_raw = &isl29028_write_raw,
+};
+
+static int isl29028_chip_init(struct isl29028_chip *chip)
+{
+ int ret;
+
+ chip->enable_prox = false;
+ chip->prox_sampling = 20;
+ chip->lux_scale = 2000;
+ chip->als_ir_mode = MODE_NONE;
+
+ ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n",
+ __func__, ISL29028_REG_TEST1_MODE, ret);
+ return ret;
+ }
+ ret = regmap_write(chip->regmap, ISL29028_REG_TEST2_MODE, 0x0);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n",
+ __func__, ISL29028_REG_TEST2_MODE, ret);
+ return ret;
+ }
+
+ ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n",
+ __func__, ISL29028_REG_CONFIGURE, ret);
+ return ret;
+ }
+
+ ret = isl29028_set_proxim_sampling(chip, chip->prox_sampling);
+ if (ret < 0) {
+ dev_err(chip->dev, "%s(): setting the proximity, err = %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = isl29028_set_als_scale(chip, chip->lux_scale);
+ if (ret < 0)
+ dev_err(chip->dev, "%s(): setting als scale failed, err = %d\n",
+ __func__, ret);
+ return ret;
+}
+
+static bool is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ISL29028_REG_INTERRUPT:
+ case ISL29028_REG_PROX_DATA:
+ case ISL29028_REG_ALSIR_L:
+ case ISL29028_REG_ALSIR_U:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config isl29028_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = is_volatile_reg,
+ .max_register = ISL29028_NUM_REGS - 1,
+ .num_reg_defaults_raw = ISL29028_NUM_REGS,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int isl29028_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct isl29028_chip *chip;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev) {
+ dev_err(&client->dev, "iio allocation fails\n");
+ return -ENOMEM;
+ }
+
+ chip = iio_priv(indio_dev);
+
+ i2c_set_clientdata(client, indio_dev);
+ chip->dev = &client->dev;
+ mutex_init(&chip->lock);
+
+ chip->regmap = devm_regmap_init_i2c(client, &isl29028_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(chip->dev, "regmap initialization failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = isl29028_chip_init(chip);
+ if (ret < 0) {
+ dev_err(chip->dev, "chip initialization failed: %d\n", ret);
+ return ret;
+ }
+
+ indio_dev->info = &isl29028_info;
+ indio_dev->channels = isl29028_channels;
+ indio_dev->num_channels = ARRAY_SIZE(isl29028_channels);
+ indio_dev->name = id->name;
+ indio_dev->dev.parent = &client->dev;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ ret = iio_device_register(indio_dev);
+ if (ret < 0) {
+ dev_err(chip->dev, "iio registration fails with error %d\n",
+ ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int isl29028_remove(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev = i2c_get_clientdata(client);
+
+ iio_device_unregister(indio_dev);
+ return 0;
+}
+
+static const struct i2c_device_id isl29028_id[] = {
+ {"isl29028", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, isl29028_id);
+
+static const struct of_device_id isl29028_of_match[] = {
+ { .compatible = "isil,isl29028", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, isl29028_of_match);
+
+static struct i2c_driver isl29028_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "isl29028",
+ .owner = THIS_MODULE,
+ .of_match_table = isl29028_of_match,
+ },
+ .probe = isl29028_probe,
+ .remove = isl29028_remove,
+ .id_table = isl29028_id,
+};
+
+module_i2c_driver(isl29028_driver);
+
+MODULE_DESCRIPTION("ISL29028 Ambient Light and Proximity Sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
diff --git a/drivers/staging/iio/light/tsl2583.c b/drivers/staging/iio/light/tsl2583.c
new file mode 100644
index 00000000000..fa964987966
--- /dev/null
+++ b/drivers/staging/iio/light/tsl2583.c
@@ -0,0 +1,950 @@
+/*
+ * 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 <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/iio/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 register */
+ 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 ret;
+ }
+
+ /* 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 ret;
+ }
+ }
+
+ usleep_range(3000, 3500);
+ /* 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 ret;
+ }
+ 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_to_iio_dev(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_to_iio_dev(dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ struct tsl2583_chip *chip = iio_priv(indio_dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ struct tsl2583_chip *chip = iio_priv(indio_dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ struct tsl2583_chip *chip = iio_priv(indio_dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ struct tsl2583_chip *chip = iio_priv(indio_dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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_to_iio_dev(dev);
+ int value;
+
+ if (kstrtoint(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_to_iio_dev(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 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 func unsupported\n");
+ return -EOPNOTSUPP;
+ }
+
+ indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+ 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_byte to cmd reg failed in taos_probe(), err = %d\n",
+ ret);
+ return ret;
+ }
+ 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);
+ return ret;
+ }
+ 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");
+ return -EINVAL;
+ }
+
+ 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);
+ return ret;
+ }
+
+ 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");
+ return ret;
+ }
+
+ /* 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;
+}
+
+#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 taos_remove(struct i2c_client *client)
+{
+ iio_device_unregister(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 = taos_remove,
+};
+module_i2c_driver(taos_driver);
+
+MODULE_AUTHOR("J. August Brenner<jbrenner@taosinc.com>");
+MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/iio/light/tsl2x7x.h b/drivers/staging/iio/light/tsl2x7x.h
new file mode 100644