root/drivers/comedi/drivers/ni_tiocmd.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Command support for NI general purpose counters
 *
 * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net>
 */

/*
 * Module: ni_tiocmd
 * Description: National Instruments general purpose counters command support
 * 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 "ni_tio_internal.h"
#include "mite.h"
#include "ni_routes.h"

static void ni_tio_configure_dma(struct ni_gpct *counter,
                                 bool enable, bool read)
{
        struct ni_gpct_device *counter_dev = counter->counter_dev;
        unsigned int cidx = counter->counter_index;
        unsigned int mask;
        unsigned int bits;

        mask = GI_READ_ACKS_IRQ | GI_WRITE_ACKS_IRQ;
        bits = 0;

        if (enable) {
                if (read)
                        bits |= GI_READ_ACKS_IRQ;
                else
                        bits |= GI_WRITE_ACKS_IRQ;
        }
        ni_tio_set_bits(counter, NITIO_INPUT_SEL_REG(cidx), mask, bits);

        switch (counter_dev->variant) {
        case ni_gpct_variant_e_series:
                break;
        case ni_gpct_variant_m_series:
        case ni_gpct_variant_660x:
                mask = GI_DMA_ENABLE | GI_DMA_INT_ENA | GI_DMA_WRITE;
                bits = 0;

                if (enable)
                        bits |= GI_DMA_ENABLE | GI_DMA_INT_ENA;
                if (!read)
                        bits |= GI_DMA_WRITE;
                ni_tio_set_bits(counter, NITIO_DMA_CFG_REG(cidx), mask, 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 ret = 0;

        if (trig_num != cmd->start_arg)
                return -EINVAL;

        spin_lock_irqsave(&counter->lock, flags);
        if (counter->mite_chan)
                mite_dma_arm(counter->mite_chan);
        else
                ret = -EIO;
        spin_unlock_irqrestore(&counter->lock, flags);
        if (ret < 0)
                return ret;
        ret = ni_tio_arm(counter, true, NI_GPCT_ARM_IMMEDIATE);
        s->async->inttrig = NULL;

        return ret;
}

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;
        const struct ni_route_tables *routing_tables =
                counter_dev->routing_tables;
        unsigned int cidx = counter->counter_index;
        struct comedi_async *async = s->async;
        struct comedi_cmd *cmd = &async->cmd;
        int ret = 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;
        }
        ni_tio_set_bits(counter, NITIO_CMD_REG(cidx), GI_SAVE_TRACE, 0);
        ni_tio_configure_dma(counter, true, true);

        if (cmd->start_src == TRIG_INT) {
                async->inttrig = &ni_tio_input_inttrig;
        } else {        /* TRIG_NOW || TRIG_EXT || TRIG_OTHER */
                async->inttrig = NULL;
                mite_dma_arm(counter->mite_chan);

                if (cmd->start_src == TRIG_NOW)
                        ret = ni_tio_arm(counter, true, NI_GPCT_ARM_IMMEDIATE);
                else if (cmd->start_src == TRIG_EXT) {
                        int reg = CR_CHAN(cmd->start_arg);

                        if (reg >= NI_NAMES_BASE) {
                                /* using a device-global name. lookup reg */
                                reg = ni_get_reg_value(reg,
                                                       NI_CtrArmStartTrigger(cidx),
                                                       routing_tables);
                                /* mark this as a raw register value */
                                reg |= NI_GPCT_HW_ARM;
                        }
                        ret = ni_tio_arm(counter, true, reg);
                }
        }
        return ret;
}

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;
}

static int ni_tio_cmd_setup(struct comedi_subdevice *s)
{
        struct comedi_cmd *cmd = &s->async->cmd;
        struct ni_gpct *counter = s->private;
        unsigned int cidx = counter->counter_index;
        const struct ni_route_tables *routing_tables =
                counter->counter_dev->routing_tables;
        int set_gate_source = 0;
        unsigned int 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) {
                if (CR_CHAN(gate_source) >= NI_NAMES_BASE) {
                        /* Lookup and use the real register values */
                        int reg = ni_get_reg_value(CR_CHAN(gate_source),
                                                   NI_CtrGate(cidx),
                                                   routing_tables);
                        if (reg < 0)
                                return -EINVAL;
                        retval = ni_tio_set_gate_src_raw(counter, 0, reg);
                } else {
                        /*
                         * This function must be used separately since it does
                         * not expect real register values and attempts to
                         * convert these to real register values.
                         */
                        retval = ni_tio_set_gate_src(counter, 0, gate_source);
                }
        }
        if (cmd->flags & CMDF_WAKE_EOS) {
                ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx),
                                GI_GATE_INTERRUPT_ENABLE(cidx),
                                GI_GATE_INTERRUPT_ENABLE(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) {
                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;
        unsigned int cidx = counter->counter_index;
        const struct ni_route_tables *routing_tables =
                counter->counter_dev->routing_tables;
        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 |= comedi_check_trigger_src(&cmd->start_src, sources);

        err |= comedi_check_trigger_src(&cmd->scan_begin_src,
                                        TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER);
        err |= comedi_check_trigger_src(&cmd->convert_src,
                                        TRIG_NOW | TRIG_EXT | TRIG_OTHER);
        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);

        if (err)
                return 1;

        /* Step 2a : make sure trigger sources are unique */

        err |= comedi_check_trigger_is_unique(cmd->start_src);
        err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
        err |= comedi_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 |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
                break;
        case TRIG_EXT:
                /* start_arg is the start_trigger passed to ni_tio_arm() */
                /*
                 * This should be done, but we don't yet know the actual
                 * register values.  These should be tested and then documented
                 * in the ni_route_values/ni_*.csv files, with indication of
                 * who/when/which/how these were tested.
                 * When at least a e/m/660x series have been tested, this code
                 * should be uncommented:
                 *
                 * err |= ni_check_trigger_arg(CR_CHAN(cmd->start_arg),
                 *                          NI_CtrArmStartTrigger(cidx),
                 *                          routing_tables);
                 */
                break;
        }

        /*
         * It seems that convention is to allow either scan_begin_arg or
         * convert_arg to specify the Gate source, with scan_begin_arg taking
         * precedence.
         */
        if (cmd->scan_begin_src != TRIG_EXT)
                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
        else
                err |= ni_check_trigger_arg(CR_CHAN(cmd->scan_begin_arg),
                                            NI_CtrGate(cidx), routing_tables);

        if (cmd->convert_src != TRIG_EXT)
                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
        else
                err |= ni_check_trigger_arg(CR_CHAN(cmd->convert_arg),
                                            NI_CtrGate(cidx), routing_tables);

        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
                                           cmd->chanlist_len);
        err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);

        if (err)
                return 3;

        /* Step 4: fix up any arguments */

        /* Step 5: check channel list if it exists */

        return 0;
}
EXPORT_SYMBOL_GPL(ni_tio_cmdtest);

int ni_tio_cancel(struct ni_gpct *counter)
{
        unsigned int cidx = counter->counter_index;
        unsigned long flags;

        ni_tio_arm(counter, false, 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, false, false);

        ni_tio_set_bits(counter, NITIO_INT_ENA_REG(cidx),
                        GI_GATE_INTERRUPT_ENABLE(cidx), 0x0);
        return 0;
}
EXPORT_SYMBOL_GPL(ni_tio_cancel);

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:
        case ni_gpct_variant_660x:
                /*
                 * not sure if 660x really supports gate interrupts
                 * (the bits are not listed in register-level manual)
                 */
                return 1;
        case ni_gpct_variant_e_series:
                /*
                 * 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.
                 */
                spin_lock_irqsave(&counter->lock, flags);
                {
                        if (!counter->mite_chan ||
                            counter->mite_chan->dir != COMEDI_INPUT ||
                            (mite_done(counter->mite_chan))) {
                                retval = 1;
                        }
                }
                spin_unlock_irqrestore(&counter->lock, flags);
                break;
        }
        return retval;
}

static void ni_tio_acknowledge_and_confirm(struct ni_gpct *counter,
                                           int *gate_error,
                                           int *tc_error,
                                           int *perm_stale_data)
{
        unsigned int cidx = counter->counter_index;
        const unsigned short gxx_status = ni_tio_read(counter,
                                                NITIO_SHARED_STATUS_REG(cidx));
        const unsigned short gi_status = ni_tio_read(counter,
                                                NITIO_STATUS_REG(cidx));
        unsigned int ack = 0;

        if (gate_error)
                *gate_error = 0;
        if (tc_error)
                *tc_error = 0;
        if (perm_stale_data)
                *perm_stale_data = 0;

        if (gxx_status & GI_GATE_ERROR(cidx)) {
                ack |= GI_GATE_ERROR_CONFIRM(cidx);
                if (gate_error) {
                        /*
                         * 660x don't support automatic acknowledgment
                         * 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(cidx)) {
                ack |= GI_TC_ERROR_CONFIRM(cidx);
                if (tc_error)
                        *tc_error = 1;
        }
        if (gi_status & GI_TC)
                ack |= GI_TC_INTERRUPT_ACK;
        if (gi_status & GI_GATE_INTERRUPT) {
                if (should_ack_gate(counter))
                        ack |= GI_GATE_INTERRUPT_ACK;
        }
        if (ack)
                ni_tio_write(counter, ack, NITIO_INT_ACK_REG(cidx));
        if (ni_tio_get_soft_copy(counter, NITIO_MODE_REG(cidx)) &
            GI_LOADING_ON_GATE) {
                if (ni_tio_read(counter, NITIO_STATUS2_REG(cidx)) &
                    GI_PERMANENT_STALE(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;
                }
        }
}

void ni_tio_acknowledge(struct ni_gpct *counter)
{
        ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL);
}
EXPORT_SYMBOL_GPL(ni_tio_acknowledge);

void ni_tio_handle_interrupt(struct ni_gpct *counter,
                             struct comedi_subdevice *s)
{
        unsigned int cidx = counter->counter_index;
        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);
        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 (ni_tio_read(counter, NITIO_DMA_STATUS_REG(cidx)) &
                    GI_DRQ_ERROR) {
                        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)
                mite_ack_linkc(counter->mite_chan, s, true);
        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");