All of lore.kernel.org
 help / color / mirror / Atom feed
* Comments requested: driver for Quatech PCI serial cards
@ 2012-11-14  6:07 Jonathan Woithe
  2012-11-14 12:12 ` Alan Cox
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-14  6:07 UTC (permalink / raw)
  To: linux-serial; +Cc: jwoithe

Quatech (now B&B Electronics) make a range of multiport PCI serial cards for
RS232 and RS422 connectivity.  A GPL Linux driver is available on the
Quatech web site (http://www.quatech.com/support/drivers_pci.php) but as far
as I can tell no attempt has been made to mainline it.  The driver's
maintenance by Quatech appears to have ceased in 2007 and as a result, the
driver they provide no longer compiles against mainline.

The driver supports a wide range of Quatech DSC/ESC/QSC cards, including the
custom functionality which allow various aspects of the signal routing to be
altered under software control.

I have ported the driver to compile again (most recent kernel tested is
currently 3.4.4).  I've also cleaned up some formatting issues, removed the
most obvious redundant code and fixed a few bugs I discovered along the way. 
The result consists of two files which I have included at the end of this
message.

I am fully aware that there are still a bunch of things which need to be
done before this can be considered for mainline: things like the
module/filename naming convention for starters.  What I would like to
determine is how much extra work would be required before this module could
be considered for mainline.  While I do not have the time or resources to
embark on a full rewrite, I am very willing to address stylistic issues or
minor quibbles with the code.

Things I know will need addressing:
 - debug / printk usage
 - indenting convention
 - naming conventions, especially of internal structures
 - the name of the module.  Anything with "qt" in it will confuse people.
   I'm thinking along the lines of serial-quatech or quatech-serial:
   comments welcome.

Things I've currently only guessed (and which therefore could be completely
off-beam):
 - the compat_ioctl stuff
 - locking requirements within the ioctl handler

One issue which might raise eyebrows is the use of some new ioctls.  I'm
guessing that ideally this sort of thing would be done either via sysfs or
procfs now.  The problem is that Quatech supply some binary applications to
manipulate various non-standard features of these cards, and they expect
ioctls.  While all the information required to write replacements which use
a more acceptable interface is available in the driver source, I would
prefer to defer this until later if at all possible.

I would appreciate comments in relation to this driver so I can determine
whether or not it's feasible for me to get it into shape for formal
mainline submission.

Regards
  jonathan

----

SerQT_PCI.c

/*
 * Driver for Quatech PCI serial cards
 *
 * Copyright (C) 2004, 2005, 2007 Tim Gobeli <tgobeli@quatech.com>
 * Copyright (C) 2012 Jonathan Woithe <jwoithe@just42.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.
 *
 * You 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/version.h>
#include <linux/module.h>
#include "serialqt_pci.h"

// Uncomment for debugging
//#define DEBUG_ON

#ifdef DEBUG_ON
    #define  dbgprint(const...)    printk(const)

#else
    #define  dbgprint(const...)
#endif

MODULE_AUTHOR("Tim Gobeli <tgobeli@quatech.com>");
MODULE_DESCRIPTION("Driver for PCI Quatech serial port adapters, v1.28");
MODULE_LICENSE("GPL");

static struct pci_dev_link_t *pPci_portlist = NULL;
static int major_number;

static long int ioctl_serial_pci (struct file *filp, unsigned int cmd,
                                     unsigned long arg);

#ifdef CONFIG_COMPAT
static long int compat_ioctl_serial_pci (struct file *filp, unsigned int cmd,
                                       unsigned long arg);
#endif

static void multi_config(struct pci_dev *pDev, multi_id_t multi_info, int index);

static unsigned SerialReadQOPR_Reg(unsigned Base_Address);

static void SerialWriteQOPR_Reg(unsigned Base_Address, bool internal, unsigned Arg);

static void SerialWriteQMCR_Reg(unsigned Base_Address, unsigned Arg);

static unsigned SerialReadQMCR_Reg(unsigned Base_Address);

static int SerialDoes422_Exist(unsigned Base_Address);

static int SerialDoesQMCR_Exist(unsigned Base_Address);

static bool SerialLockOutOverride(unsigned Base_Address);

static bool SerialClkCtrlOverride(unsigned Base_Address, unsigned long *pClock_Rate);

struct file_operations serialqt_pci_fops = {
    unlocked_ioctl:  ioctl_serial_pci,
#ifdef CONFIG_COMPAT
    compat_ioctl:    compat_ioctl_serial_pci,
#endif

};


long int ioctl_serial_pci (struct file *filp, unsigned int cmd, 
                              unsigned long arg)
{
    unsigned long irqflags;
    unsigned ud_QT_Baseaddress, err;
    unsigned ucOPR_OrgValue, ucOPR_NewValue, uc_Value;
    int *p_Num_of_adapters, counts, index, *p_QMCR_Value;
    struct pci_dev *dev = NULL;
    Identity_struct *p_Identity_of;
    Identity_struct  Identity_of;

    struct pci_dev_link_t *pTemp_dev_link ;
    dbgprint(KERN_DEBUG"ioctl_serial_pci  cmd =\n");
     if (_IOC_TYPE(cmd) != SERIALQT_PCI_IOC_MAGIC)
        return -ENOTTY;
    if (_IOC_NR(cmd) > SERIALQT_IOC_MAXNR)
        return -ENOTTY;
    dbgprint(KERN_DEBUG"ioctl_serial_pci  cmd = 0x%x\n", cmd);
        err = 0;

    switch (cmd)
    {
    case SERIALQT_WRITE_QOPR: 
        pTemp_dev_link = pPci_portlist;
        index = arg >> 16;
        counts = 0;
        // Go through the whole list and find the  adapter
        while (pTemp_dev_link)
        {
            dbgprint(KERN_DEBUG"ioctl_serial_pci  arg = 0x%lx\n", arg);
            // Setting one port QOPR on multiport adapter sets them all  
            if (pTemp_dev_link->Adapter_Index == index)
            {
                // We've found the adapter we want, lets change the QOPR time 
                // setting accordingly
                spin_lock_irqsave(pTemp_dev_link->lock, irqflags);
                ud_QT_Baseaddress = pTemp_dev_link->ulPortStart;
                ucOPR_OrgValue =  SerialReadQOPR_Reg(ud_QT_Baseaddress);
                // Just mask off the last two bits of the QOPR register 
                // because thats all that needs changed there for clock 
                // rate control
                ucOPR_NewValue = ucOPR_OrgValue & 0xfc;
                ucOPR_NewValue = ucOPR_NewValue | arg;
                SerialWriteQOPR_Reg(ud_QT_Baseaddress, FALSE, ucOPR_NewValue);
                // We're done
                spin_unlock_irqrestore(pTemp_dev_link->lock, irqflags);
                break;
            } else
            {
                pTemp_dev_link = pTemp_dev_link->next;
            }

        }  // end while
        break;

    case SERIALQT_WRITE_QMCR:
        // initialize as error so if we don't find this one we give
        err = -ENOTTY; 
        pTemp_dev_link = pPci_portlist;
        index = arg >> 16;

        // Go through the whole list and find the  adapter
        while (pTemp_dev_link != NULL)
        {
            dbgprint(KERN_DEBUG"ioctl_serial_pci  arg = 0x%lx\n", arg);
            if (pTemp_dev_link->Adapter_Index == index)
            {
                // must set each ports QMCR individually
                while ((pTemp_dev_link != NULL) && 
                       (pTemp_dev_link->Adapter_Index == index))
                {
                    spin_lock_irqsave(pTemp_dev_link->lock, irqflags);
                    // We've found the adapter we want, lets change the 
                    // QMCR time setting accordingly
                    ud_QT_Baseaddress = pTemp_dev_link->ulPortStart;
                    ucOPR_NewValue = arg;

                    dbgprint(KERN_DEBUG"ioctl SERIALQT_WRITE_QMCR:  Base Addrss = 0x%x,  value = 0x%x\n", ud_QT_Baseaddress, ucOPR_NewValue);
                    SerialWriteQMCR_Reg(ud_QT_Baseaddress, ucOPR_NewValue );
                    err = 0;
                    spin_unlock_irqrestore(pTemp_dev_link->lock, irqflags);
                    pTemp_dev_link = pTemp_dev_link->next;
                }
                // We're done
                break;
            } else
            {
                pTemp_dev_link = pTemp_dev_link->next;
            }
        }  // end while
        break;

    case SERIALQT_READ_QMCR:
        // initialize as error so if we don't find this one we give an error.
        err = -ENOTTY;
        p_QMCR_Value =  (int *)arg;
        pTemp_dev_link = pPci_portlist;
        index = arg >> 16;
        counts = 0;
        while (pTemp_dev_link != NULL)
        {
            if (pTemp_dev_link->Adapter_Index == index)
            {
                spin_lock_irqsave(pTemp_dev_link->lock, irqflags);
                ud_QT_Baseaddress = pTemp_dev_link->ulPortStart;
                uc_Value =  SerialReadQMCR_Reg(ud_QT_Baseaddress);
                err = put_user(uc_Value, p_QMCR_Value);
                spin_unlock_irqrestore(pTemp_dev_link->lock, irqflags);
                break;   // We're done
            } else
            {
                pTemp_dev_link = pTemp_dev_link->next;
            }
        }
        break;

    case SERIALQT_GET_NUMOF_UNITS: 
        p_Num_of_adapters = (int *)arg;
        counts = 0;
        dbgprint(KERN_DEBUG"SERIALQT_GET_NUMOF_UNITS \n");

        for (index = 0; index < (sizeof(multi_id)/sizeof( multi_id_t)); index++)
        {
            dev = pci_get_subsys(QUATECH_VID, multi_id[index].prodid, 
                                    PCI_ANY_ID, PCI_ANY_ID, NULL);
            while (dev != NULL) // find multiple devices of same signature
            {
                counts++;
                dev = pci_get_subsys(QUATECH_VID, multi_id[index].prodid, 
                                        PCI_ANY_ID, PCI_ANY_ID, dev);
            }
        }
        dbgprint(KERN_DEBUG"ioctl_serial_pci writting counts = %d", counts);
        err = put_user(counts, p_Num_of_adapters);
        break;

    case SERIALQT_GET_THIS_UNIT:
        counts = 0;
        p_Identity_of = (Identity_struct *)arg;
        // copy user structure to local variable

        get_user(Identity_of.index, &p_Identity_of->index);
        dbgprint(KERN_DEBUG"SERIALQT_GET_THIS_UNIT Identity_of.index= 0x%x\n", Identity_of.index);

        // initialize as error so if we don't find this one we give an error.
        err = -ENOTTY; 

        pTemp_dev_link = pPci_portlist;
        while (pTemp_dev_link != NULL)
        {
            if (pTemp_dev_link->Adapter_Index == Identity_of.index)
            {
                err = put_user(pTemp_dev_link->prodid, &p_Identity_of->n_identity);
                break;   // We're done

            } else
            {
                pTemp_dev_link = pTemp_dev_link->next;
            }
        }
        break;

    case SERIALQT_IS422_EXTENDED:
        err = -ENOTTY; // initialize as error so if we don't find this one we give
        dbgprint(KERN_DEBUG"SERIALQT_IS422_EXTENDED \n");
        pTemp_dev_link = pPci_portlist;
        index = arg >> 16;

        while (pTemp_dev_link != NULL)
        {
            if (pTemp_dev_link->Adapter_Index == index)
            {
                dbgprint(KERN_DEBUG"Found Adapter \n");

                ud_QT_Baseaddress = pTemp_dev_link->ulPortStart;
                if (SerialDoesQMCR_Exist(ud_QT_Baseaddress))
                {
                    err = 1;
                    break;   // no MCR so return -ENOTTY;
                }
                if (SerialDoes422_Exist(ud_QT_Baseaddress))
                {
                    err = 2;
                    break;
                }
                dbgprint(KERN_DEBUG"SERIALQT_IS422_EXTENDED err = 0\n");

                // If we got this far then it is a 422/485 extended feature 
                // device
                err = 0; 
                break; 
            } else
            {
                pTemp_dev_link = pTemp_dev_link->next;
            }
        }
        break;

    default:
        err = -ENOTTY;

    } // End switch

    return err;
}

#ifdef CONFIG_COMPAT
long int compat_ioctl_serial_pci (struct file *filp, unsigned int cmd,
                                     unsigned long arg)
{
    long int err = 0;
    unsigned long compat_arg = 0;
    switch (cmd)
    {
    case SERIALQT_WRITE_QOPR: 
    case SERIALQT_WRITE_QMCR:
    case SERIALQT_IS422_EXTENDED:
        /* arg is an integer value */
        compat_arg = arg;
        break;
    case SERIALQT_READ_QMCR:
    case SERIALQT_GET_NUMOF_UNITS:
    case SERIALQT_GET_THIS_UNIT:
        /* arg is a pointer */
        compat_arg = (unsigned long)compat_ptr(arg);
        break;
    default:
        err = -ENOTTY;
    }
    if (err == 0)
        err = ioctl_serial_pci(filp, cmd, compat_arg);

    return err;
}
#endif

void SerialWriteQOPR_Reg(unsigned Base_Address, bool internal, unsigned Arg)
{
    unsigned    ulQT_ORaddress, ulQT_LCRaddress;
    unsigned    ucLCR_OrgValue, ucOPR_OrgValue, ucOPR_NewValue;

    // We only allow the last two bits to change on the QOR register
    ulQT_ORaddress = Base_Address + 7;
    ulQT_LCRaddress = Base_Address + 3;
    // Set DLAB to access options register
    ucLCR_OrgValue = inb(ulQT_LCRaddress);
    dbgprint(KERN_DEBUG"Read LCR at = 0x%x value = 0x%x\n", ulQT_LCRaddress, ucLCR_OrgValue);

    outb(0xbf, ulQT_LCRaddress);
    dbgprint(KERN_DEBUG" Wrote LCR at = 0x%x value = 0x%x\n", ulQT_LCRaddress, 0xbf);

    ucOPR_OrgValue = inb(ulQT_ORaddress);
    // If this comes from user -Just mask off the last two bits of the QOPR register cause thats all 
    // that needs changed there for clock rate control
    if(internal == FALSE)
    {
         ucOPR_NewValue = ucOPR_OrgValue & 0xfc;
         ucOPR_NewValue = ucOPR_NewValue | Arg; 
         dbgprint(KERN_DEBUG"Read qopr at = 0x%x value = 0x%x\n", ulQT_ORaddress, ucOPR_OrgValue);
    }
    else
        ucOPR_NewValue = Arg;

    //Write new value to options register
    outb(ucOPR_NewValue, ulQT_ORaddress );

    dbgprint(KERN_DEBUG"SerialWriteQOPR_Reg: Wrote qopr at = 0x%x value = 0x%x\n", ulQT_ORaddress, ucOPR_NewValue);

    // Read the value back for testing purposes
    // ucLCR_OrgValue = 0;
    // ucLCR_OrgValue = inb(ulQT_ORaddress);
    // dbgprint(KERN_DEBUG"Read qopr at = 0x%x value = 0x%x", ulQT_ORaddress, ucLCR_OrgValue);

    // Restore origninal lCR value
    outb(ucLCR_OrgValue, ulQT_LCRaddress ); 
}

unsigned SerialReadQOPR_Reg(unsigned Base_Address)
{
    unsigned    ulQT_ORaddress, ulQT_LCRaddress;
    unsigned    ucLCR_OrgValue, ucOPR_Value;

    //We only allow the last two bits to change on the QOR register
    ulQT_ORaddress = Base_Address + 7;
    ulQT_LCRaddress = Base_Address + 3;
    // Set DLAB to access options register
    ucLCR_OrgValue = inb(ulQT_LCRaddress);

    outb(0xbf, ulQT_LCRaddress);
     ucOPR_Value = inb(ulQT_ORaddress);
    // Restore origninal lCR value
    outb(ucLCR_OrgValue, ulQT_LCRaddress ); 
    dbgprint(KERN_DEBUG" SerialReadQOPR_Reg:Read QOR at Base = 0x%x value = 0x%x\n", Base_Address, ucOPR_Value);

    return ucOPR_Value;  
}

void SerialWriteQMCR_Reg(unsigned Base_Address, unsigned Arg)
{
    unsigned    ulQT_ORaddress, ulQT_LCRaddress, ulQT_MCRaddress;
    unsigned    ucLCR_OrgValue, ucOPR_OrgValue;

    // We only allow the last two bits to change on the QOR register
    ulQT_ORaddress = Base_Address + 7;
    ulQT_LCRaddress = Base_Address + 3;
    ulQT_MCRaddress = Base_Address + 4;
    // Set DLAB to access options register
    ucLCR_OrgValue = inb(ulQT_LCRaddress);

    outb(0xbf, ulQT_LCRaddress);

    ucOPR_OrgValue = inb(ulQT_ORaddress);
    // Set options register QLAB bit to access QMCR register
    outb(ucOPR_OrgValue | 0x10, ulQT_ORaddress);
    // Now write to the QMCR register
    outb(Arg, ulQT_MCRaddress);

    // Restore original contents of QOPR register
    outb(ucLCR_OrgValue, ulQT_LCRaddress );

    // Restore origninal lCR value
    outb(ucLCR_OrgValue, ulQT_LCRaddress ); 
}

unsigned SerialReadQMCR_Reg(unsigned Base_Address){

    unsigned    ulQT_ORaddress, ulQT_LCRaddress, ulQT_MCRaddress;
    unsigned    ucLCR_OrgValue, ucOPR_OrgValue, ucMCR_Value;

    // We only allow the last two bits to change on the QOR register
    ulQT_ORaddress = Base_Address + 7;
    ulQT_LCRaddress = Base_Address + 3;
    ulQT_MCRaddress = Base_Address + 4;
    // Set DLAB to access options register
    ucLCR_OrgValue = inb(ulQT_LCRaddress);

    outb(0xbf, ulQT_LCRaddress);

    ucOPR_OrgValue = inb(ulQT_ORaddress);
    // Set options register QLAB bit to access QMCR register
    outb(ucOPR_OrgValue | 0x10, ulQT_ORaddress);
    // Now read the QMCR register
    ucMCR_Value = inb(ulQT_MCRaddress);

    // Restore original contents of QOPR register
    outb(ucOPR_OrgValue, ulQT_ORaddress );

    // Restore origninal lCR value
    outb(ucLCR_OrgValue, ulQT_LCRaddress ); 

    return ucMCR_Value;
}

// SerialDoesQMCR_Exist tests for existence of QMCR register and returns 0
// valueif it does and nonzero if it doesn't 
int SerialDoesQMCR_Exist(unsigned Base_Address)
{
    unsigned    ulQT_ORaddress, ulQT_LCRaddress, ulQT_MCRaddress;
    unsigned    ucLCR_OrgValue, uc_Value;
    int        Status = 0;

    // We only allow the last two bits to change on the QOR register
    ulQT_ORaddress = Base_Address + 7;
    ulQT_LCRaddress = Base_Address + 3;
    ulQT_MCRaddress = Base_Address + 4;
    // Set DLAB to access options register
    ucLCR_OrgValue = inb(ulQT_LCRaddress);
    // Writting a 0xbf to lcr will make bet 5 of QOR go high if QOR and QMCR exist
    outb(0xbf, ulQT_LCRaddress);
    uc_Value = inb(ulQT_ORaddress);
    if (!(uc_Value & 0x20))//Is bit 5 (IDX) set
    {
        dbgprint(KERN_DEBUG"QMCR bit b5 not set \n");
        Status = -ENOTTY;
    }

    if (Status == 0)
    {
        // So far so good now write something other than 0xbf to the LCR to set IDX to 0
        // and test
        outb(0x80, ulQT_LCRaddress);
        // IDX should be clear
        uc_Value = inb(ulQT_ORaddress);
        if (uc_Value & 0x20)
            Status = -ENOTTY;

    }
    outb(ucLCR_OrgValue, ulQT_LCRaddress); // Restore original value
    return Status;
}

int SerialDoes422_Exist(unsigned Base_Address)
{
    int Status;
    unsigned ucMCR_OrgValue, uc_Value;
    ucMCR_OrgValue = SerialReadQMCR_Reg(Base_Address);
    // Essentially we're ging to write a value of 0xff to the QMCR register and 
    // if we read back a nonzero value, then its a 422 device otherwize its 422
    uc_Value = 0xff;
    SerialWriteQMCR_Reg(Base_Address, uc_Value);
    uc_Value = 0;
    uc_Value = SerialReadQMCR_Reg(Base_Address);
    if (uc_Value)
        Status = 0;
    else
        Status = -ENOTTY;

    SerialWriteQMCR_Reg(Base_Address, ucMCR_OrgValue);

    return Status;
}

int init_serial_pci(void){

    struct pci_dev *dev;

    int index;
    int status = 0;
    int Adapter_Index = 0;

    dev = NULL;

    dbgprint(KERN_DEBUG"Entering SerialQT_PCI \n\n");

    // Find the given adapters and register them accordingly with the serial 
    // core
    for (index = 0; index < (sizeof(multi_id)/sizeof( multi_id_t)); index++)
    {
        dev = pci_get_subsys(QUATECH_VID, multi_id[index].prodid, PCI_ANY_ID, 
                                PCI_ANY_ID, NULL);
        if (dev != NULL)
        {
            while (dev != NULL) // find multiple devices of same signature
            {
                status++;
                status = pci_enable_device(dev);
                if(status < 0)
                {
                    dbgprint(KERN_DEBUG"SerialQT_PCI - Unable to enable device \n\n");
                    return -EBUSY;
                }
                multi_config(dev, multi_id[index], Adapter_Index);
                Adapter_Index++; // index for next one
                dev = pci_get_subsys(QUATECH_VID, multi_id[index].prodid, 
                                        PCI_ANY_ID, PCI_ANY_ID, dev);
            }
        }
    }

    if (Adapter_Index == 0)
        dbgprint(KERN_DEBUG"No devices found \n\n");

    status = 0; // Dynamic assignment of major number
    major_number = register_chrdev(status, "SerialQT_PCI", &serialqt_pci_fops);
    if (major_number < 0)
    {
        dbgprint(KERN_DEBUG"No devices found \n\n");
        return -EBUSY;
    } else
       dbgprint(KERN_DEBUG"SerQT_PCI major number assignment = %d \n\n", major_number);
    return 0;
}

// multi_config()
// Inputs:
//  * Struct pci_dev * passed from successful pci_get_subsys()
//  * multi_id_t structure of device found
//
// Description:
//   Determines resources used by an adapter and uses them to register the
//   ports.  It then saves pertinent information for each port in a linked 
//   list for later use to configure the adapter per users request.

static void multi_config(struct pci_dev *pDev, multi_id_t multi_info, int Adapter_Index){

    int index;
    unsigned int ulAddressStart, ulPortAddress;
    unsigned int Irq;
    int nFound = 0;
    struct pci_dev_link_t *pTemp_dev_link;
    #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
    struct uart_port serial;
    #else
    struct serial_struct serial;
    #endif
    int line;
    int nNum_of_Ports = multi_info.multi;
    unsigned long Clock_Rate;
    u32 tmp;
   
    Irq = pDev->irq;
    dbgprint(KERN_DEBUG"Device = 0x%x, Irq = %d\n", pDev->device, Irq);

    // Set byte in the Amcc dchiep in the extremly unlikely event that the
    // system bios didn't run the ROM bios extension on our pci card
    if(multi_info.AMCC == TRUE)
    {
        ulPortAddress = pci_resource_start(pDev, 0);
        ulAddressStart = ulPortAddress + 0x38;
        tmp = inl(ulAddressStart);
        tmp = tmp | 0x00002000;
        dbgprint(KERN_DEBUG"Bios work around, writing value = 0x%x Device = 0x%x, at 0x%x\n",tmp, pDev->device, ulAddressStart);

        outl(tmp, ulAddressStart);

        ulAddressStart = ulPortAddress + 0x3c;
		
        tmp = inl(ulAddressStart);
        tmp = tmp |  0x01000000;
        outl(tmp, ulAddressStart);
        tmp &= ~0x01000000;
        outl(tmp, ulAddressStart);
    }

    ulAddressStart = pci_resource_start(pDev, multi_info.IO_Region);

    dbgprint(KERN_DEBUG"Device = 0x%x, Address = 0x%x", pDev->device, ulAddressStart);

    // SerialClkCtrlOverride will set the adapter clock rate to highest
    // available rate and return that rate in the varible
    SerialClkCtrlOverride((unsigned)ulAddressStart, &Clock_Rate);

    if (ulAddressStart != 0)
        nFound = 1;
    else
        nFound = 0;

    if (nFound)
    {
        for (index = 0; index < nNum_of_Ports; index++)
        {
            ulPortAddress = ulAddressStart + (index * 8);
            memset(&serial, 0, sizeof(serial));
            serial.irq = Irq;
            #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
            serial.iobase = ulPortAddress;
            serial.flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ;
            serial.uartclk = Clock_Rate;
            line = serial8250_register_port(&serial);
            #else
            serial.port = ulPortAddress;
            serial.flags = ASYNC_SKIP_TEST | ASYNC_SHARE_IRQ;
            serial.baud_base = Clock_Rate;
            line = register_serial(&serial);
            #endif
                       
            if (line < 0)
            {
                dbgprint(KERN_DEBUG"<1>SerQT_PCI:  register_serial() at 0x%04lx,irq %d failed\n",(u_long)ulPortAddress, serial.irq); 
                return;
            } else
            {
                dbgprint(KERN_DEBUG"SerQT_PCI:  register_serial() at 0x%04lx,irq %d, ttyS%d succeded\n",(u_long)ulPortAddress, serial.irq, line); 
            }
            pTemp_dev_link = kmalloc(sizeof(struct pci_dev_link_t), GFP_KERNEL);
            if (!pTemp_dev_link)
            {
                dbgprint(KERN_DEBUG"IN %s insufficient resources\n", __FUNCTION__);
                #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
                serial8250_unregister_port(line);
                #else
                unregister_serial(line);
                #endif
                
                return;
            } else
            {
                memset(pTemp_dev_link, 0, sizeof(struct pci_dev_link_t));
                serial.line = line;
            }
            // Create a list of ports with information we'll use to custom 
            // configure it
            pTemp_dev_link->line = serial.line;
            pTemp_dev_link->prodid =  multi_info.prodid;
            pTemp_dev_link->type = multi_info.type;
            pTemp_dev_link->Port_Number = index;
            pTemp_dev_link->ulPortStart = ulPortAddress;
            pTemp_dev_link->Adapter_Index = Adapter_Index;
            pTemp_dev_link->lock = &serial.lock;
            pTemp_dev_link->next = (void *)pPci_portlist;
            pPci_portlist = pTemp_dev_link;
        }   // end for
    } else
    {
        dbgprint(KERN_DEBUG"Couldn't find device\n");
    }// end if
}

// SerialClkCtrlOverride()
//
// Called from muli_config() to to determine if overrides to the Clock mode
// of the hardware feature have been set via jumper configurations
// also determines max clock multiplier and sets the adapter accordingly
// (some cards have a max multiplier of X4 some of X8
//
// Inputs: 
//  * Base_Address     Adapter base address
//
// Outputs: None
//
// Returns:
//  * True if no QOPR or locked out, also appropriate clocke rate is
//    returned in the pClock_Rate parameter

bool SerialClkCtrlOverride(unsigned Base_Address, unsigned long *pClock_Rate)
{
    unsigned QOPR_OrgValue, QOPR_Value, TestOutValue, Auto_QOPR_Setting;
    bool status;

    status =  SerialLockOutOverride(Base_Address);
    // If no QOR we disable all these selections
    if (status == TRUE)
    {
        dbgprint(KERN_DEBUG"SerialClkCrtlOverride, no QOR\n"); 

        *pClock_Rate =  CLOCK_X1; // No Qor so use standard rate and return
        return status;
    }

    QOPR_OrgValue = SerialReadQOPR_Reg(Base_Address);
    QOPR_Value = QOPR_OrgValue & (~QOPR_CLOCK_X8); // Clear clk rate bits

    SerialWriteQOPR_Reg(Base_Address, FALSE, QOPR_Value);

    TestOutValue = SerialReadQOPR_Reg(Base_Address);
    // Those bits shoud be 0, if not we're overridden
    if (TestOutValue &= QOPR_CLOCK_X8)
    {
        *pClock_Rate =  CLOCK_X1; // Set to standard rate if overridden
        status =  TRUE;
    }
    else
    {
        // Now try setting them to "1" and see if any of it sticks
        // some cards only go X4 so only hi bit sticks
        QOPR_Value = QOPR_Value | QOPR_CLOCK_X8; // Set clk rate bits


        SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_Value);

        TestOutValue = SerialReadQOPR_Reg(Base_Address);
        if (TestOutValue &=QOPR_CLOCK_X8)
            status =  FALSE;  // It stuck, we're not overridden
        else
        {
            *pClock_Rate =  CLOCK_X1; // Set to standard rate if overridden
            status =  TRUE; // It didn't stick, we are overridden
        }

    }

    // Ok were not forced but the max rate is not the same for all boards
    // lets find out what the max is and  
    if (status == FALSE)
    {
        TestOutValue = SerialReadQOPR_Reg(Base_Address);
        TestOutValue &=QOPR_CLOCK_X8;
        switch (TestOutValue)
        {
        case QOPR_CLOCK_X2:
            *pClock_Rate =  CLOCK_X2;
            Auto_QOPR_Setting = QOPR_CLOCK_X2;
            break;

        case QOPR_CLOCK_X4:
            *pClock_Rate =  CLOCK_X4;
            Auto_QOPR_Setting = QOPR_CLOCK_X4;
            break;

        case QOPR_CLOCK_X8:
            *pClock_Rate =  CLOCK_X8;
            Auto_QOPR_Setting = QOPR_CLOCK_X8;
            break;
            break;
        default:
            *pClock_Rate =  CLOCK_X1;
             Auto_QOPR_Setting = QOPR_CLOCK_X1;
        }

        QOPR_OrgValue = QOPR_OrgValue & (~QOPR_CLOCK_MASK); // clear clock bits
        QOPR_OrgValue = QOPR_OrgValue | Auto_QOPR_Setting; // Set clock bits
        // set adapter clock
        SerialWriteQOPR_Reg(Base_Address, TRUE,  QOPR_OrgValue);  
    }
    // set adapter clock
    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_OrgValue);

    dbgprint(KERN_DEBUG"<1>SerialClkCtrlOverride:  Writing to QOPR value =  0x%04lx\n",(u_long)QOPR_OrgValue); 

    return status;
}

// SerialLockOutOverride()
//
// Check on the presences of QOR and return true if we don't get the 
// desired test pattern resuls
//
// Inputs: base address
//
// Outputs: none
//
// Returns:
//  * False if QOPR exist
//  * TRUE if jumper overridden on Qopr isn't there

bool SerialLockOutOverride(unsigned Base_Address)
{
    unsigned QOPR_OrgValue, QOPR_Value, TestOutValue;

    QOPR_OrgValue =  SerialReadQOPR_Reg( Base_Address);

    QOPR_Value = QOPR_OrgValue & QPCR_TEST_FOR1; // Clear ID0, ID1 bits
    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_Value);


    TestOutValue = SerialReadQOPR_Reg(Base_Address);
    TestOutValue &= 0xc0;  // Mask ID0 & ID1
    if (TestOutValue != QPCR_TEST_GET1)
        return TRUE;

    QOPR_Value = QOPR_OrgValue & QPCR_TEST_FOR1; // Clear ID0, ID1 bits
    QOPR_Value = QOPR_Value | QPCR_TEST_FOR2; // set ID0, ID1 bits

    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_Value);
    TestOutValue = SerialReadQOPR_Reg(Base_Address);

    TestOutValue &= 0xc0;    // mask off ID0 and ID1 bits

    if (TestOutValue != QPCR_TEST_GET2)
        return TRUE;

    QOPR_Value = QOPR_OrgValue & QPCR_TEST_FOR1; // Clear ID0, ID1 bits
    QOPR_Value = QOPR_Value | QPCR_TEST_FOR3; // set ID0, ID1 bits

    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_Value);
    TestOutValue = SerialReadQOPR_Reg(Base_Address);

    TestOutValue &= 0xc0;    // mask off ID0 and ID1 bits

    if (TestOutValue != QPCR_TEST_GET3)
        return TRUE;

    QOPR_Value = QOPR_OrgValue & QPCR_TEST_FOR1; // Clear ID0, ID1 bits
    QOPR_Value = QOPR_Value | QPCR_TEST_FOR4; // set ID0, ID1 bits

    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_Value);
    TestOutValue = SerialReadQOPR_Reg(Base_Address);

    TestOutValue &= 0xc0;    // mask off ID0 and ID1 bits

    SerialWriteQOPR_Reg(Base_Address, TRUE, QOPR_OrgValue); // Restore value


    if (TestOutValue != QPCR_TEST_GET4)
        return TRUE;

    return FALSE;
}

void exit_serial_pci(void){
    struct pci_dev_link_t *pTemp_dev_link;
    while (pPci_portlist != NULL)
    {              
        #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
        serial8250_unregister_port(pPci_portlist->line);
        #else
        unregister_serial(pPci_portlist->line);
        #endif
        
        pTemp_dev_link = pPci_portlist->next;
        kfree(pPci_portlist);
        pPci_portlist = pTemp_dev_link;
    }

    unregister_chrdev(major_number, "SerialQT_PCI");
}

module_init(init_serial_pci);
module_exit(exit_serial_pci);

----

serialqt_pci.h

/*
 * serialqt_pci.h
 *
 * Copyright (C) 2004, 2005, 2007 Tim Gobeli <tgobeli@quatech.com>
 * Copyright (C) 2012 Jonathan Woithe <jwoithe@just42.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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __KERNEL__
#define __KERNEL__
#endif

#ifndef MODULE
#define MODULE
#endif

#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif

#include <config/modules.h>

#include <linux/errno.h>
#include <asm/io.h>
#include <linux/pci.h>

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
#include <linux/serial_8250.h>
#else
#include <linux/serial.h>
#endif

#include <asm/uaccess.h>
#include <linux/init.h>

#define TRUE  1
#define FALSE 0
#define bool  int

#define QSC100          0x0010
#define DSC100          0x0020	
#define DSC100E         0x0181
#define DSC200          0x0030
#define DSC200E         0x01B1
#define QSC200          0x0040
#define ESC100          0x0050
#define ESC100_1        0x0060
#define QSCP100         0x0120
#define DSCP100         0x0130
#define QSCP200         0x0140
#define DSCP200         0x0150
#define QSCLP100        0x0170
#define DSCLP100        0x0180
#define SSCLP100        0x0190
#define QSCLP200        0x01A0
#define DSCLP200        0x01B0
#define SSCLP200        0x01C0
#define ESCLP100        0x01E0

#define QUATECH_VID     0x135C

#define RS232 0
#define RS422 1

#define BUFFER_LENGTH   50

typedef struct
{
    int     prodid;
    int     multi;           /* 1 = multifunction, > 1 = # ports */
    int     extended;        /* 1 = extended feature, 1 = no extended feature */
    int     type;            /* RS232 or RS422 */
    int     IO_Region;       /* I/O region of funcitonal I/O range*/ 
    bool    AMCC;
} multi_id_t;

// This is used to create a linked list of ports and to associate them with 
// a given adapter
struct pci_dev_link_t
{
    struct          pci_dev_link_t *next;
    int             prodid;
    unsigned int    ulPortStart;
    int             type;
    int             line;
    int             Port_Number;
    int             Adapter_Index;
    spinlock_t      *lock;    /* Pointer to the mutex used by serial core */
};

static multi_id_t multi_id[] =
{  
    {QSC100,    4,  1,  RS232,  1, TRUE}, 
    {DSC100,    2,  1,  RS232,  1, TRUE},
    {DSC100E,   2,  1,  RS232,  2, FALSE},
    {DSC200,    2,  1,  RS422,  1, TRUE},
    {DSC200E,    2,  1,  RS422,  2, FALSE},
    {QSC200,    4,  1,  RS422,  1, TRUE},
    {ESC100,    8,  1,  RS232,  1, TRUE},
    {ESC100_1,  8,  1,  RS232,  1, TRUE},
    {QSCP100,   4,  1,  RS232,  1, TRUE},
    {DSCP100,   2,  1,  RS232,  1, TRUE},
    {QSCP200,   4,  1,  RS422,  1, TRUE},
    {DSCP200,   2,  1,  RS422,  1, TRUE},
    {ESCLP100,  8,  1,  RS232,  0, FALSE},
    {QSCLP100,  4,  1,  RS232,  2, FALSE},
    {DSCLP100,  2,  1,  RS232,  2, FALSE},
    {SSCLP100,  1,  1,  RS232,  2, FALSE},
    {QSCLP200,  4,  1,  RS422,  2, FALSE},
    {DSCLP200,  2,  1,  RS422,  2, FALSE}, 
    {SSCLP200,  1,  1,  RS422,  2, FALSE},
};

typedef struct
{
    int     index;
    int     n_identity;           
            
} Identity_struct;

#define SERIALQT_PCI_IOC_MAGIC 'k'
#define SERIALQT_WRITE_QOPR _IOW(SERIALQT_PCI_IOC_MAGIC, 0, int)
#define SERIALQT_WRITE_QMCR _IOW(SERIALQT_PCI_IOC_MAGIC, 1, int)
#define SERIALQT_GET_NUMOF_UNITS _IOR(SERIALQT_PCI_IOC_MAGIC, 2, void *)
#define SERIALQT_GET_THIS_UNIT _IOR(SERIALQT_PCI_IOC_MAGIC, 3, void *)
#define SERIALQT_READ_QOPR _IOR(SERIALQT_PCI_IOC_MAGIC, 4, int)
#define SERIALQT_READ_QMCR _IOR(SERIALQT_PCI_IOC_MAGIC, 5, int)
#define SERIALQT_IS422_EXTENDED _IOR(SERIALQT_PCI_IOC_MAGIC, 6, int)    //returns successful if 422 extended

#define SERIALQT_IOC_MAXNR 6

// Constants to Check for presence for QOR register
#define QPCR_TEST_FOR1    0x3f  // 
#define QPCR_TEST_GET1    0x00  // (0 0 x x x x x x) in gives (0 0 x x x x x x) out
#define QPCR_TEST_FOR2    0x40  // 
#define QPCR_TEST_GET2    0x040  // (0 1 x x x x x x) in gives (0 1 x x x x x x) out
#define QPCR_TEST_FOR3    0x80  // 
#define QPCR_TEST_GET3    0x40  // (1 0 x x x x x x) in gives (0 1 x x x x x x) out
#define QPCR_TEST_FOR4    0xc0  // 
#define QPCR_TEST_GET4    0x80  // (1 1 x x x x x x) in gives (1 0 x x x x x x) out

// Quatech Options Register DWORD "QOPR" (Hardware Register)
// Lower 8 bits defined by hardware registers, upper 8 bits custom flags
#define QOPR_CLOCK_AUTO    0x0100  // (bit 8) custom flag for "Auto" data rate multiplier mode
#define QOPR_CLOCK_X1      0x0000  // (x x x x x x 0 0) force X1 clock mode   
#define QOPR_CLOCK_X2      0x0001  // (x x x x x x 0 1) force X2 clock mode
#define QOPR_CLOCK_X4      0x0002  // (x x x x x x 1 0) force X4 clock mode
#define QOPR_CLOCK_X8      0x0003  // (x x x x x x 1 1) force X8 clock mode
#define QOPR_CLOCK_MASK    0x0103  // (1)(x x x x x x 1 1) used by software to mask off bits
#define QOPR_CLOCK_RATE_MASK    0x0003  // (1)(x x x x x x 1 1) used by software to mask off bits

// Quatech Hardware Options Enable Register DWORD "HWOR" (Software Register)
// low word, low byte in HWOR defines clock multiplier options available 
#define HWOR_CLOCK_X1      0x00000001  // (x x x x x x x 1) X1 clock mode available
#define HWOR_CLOCK_X2      0x00000002  // (x x x x x x 1 x) X2 clock mode available
#define HWOR_CLOCK_X4      0x00000004  // (x x x x x 1 x x) X4 clock mode available
#define HWOR_CLOCK_X8      0x00000008  // (x x x x 1 x x x) X8 clock mode available
#define HWOR_CLOCK_AUTO    0x00000080  // (1 x x x x x x x) auto clock mode available
#define HWOR_CLOCK_CLR     0xffffff00  // (0 0 0 0 0 0 0 0) auto clock mode available
#define HWOR_CLOCK_MASK    0x000000FF  // Bit mask used to determine whether to display Advanced property page

// low word, low byte in HWOR defines clock multiplier options available 
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
    #define CLOCK_X1           1843200  // X1 clock RATE
    #define CLOCK_X2           3685400  // X2 clock RATE
    #define CLOCK_X4           7372800  // X4 clock RATE
    #define CLOCK_X8           14745600 // (X8 clock RATE
#else
    #define CLOCK_X1           115200  // X1 clock RATE
    #define CLOCK_X2           230400  // X2 clock RATE
    #define CLOCK_X4           460800  // X4 clock RATE
    #define CLOCK_X8           921600  // (X8 clock RATE
#endif

// low word, high byte in HWOR defines RS-485 transmit and receive control 
// options available
#define HWOR_DUPLEX_FULL   0x00000100  // (x x x x x x x 1) transmitters always on available
#define HWOR_DUPLEX_DTR    0x00000200  // (x x x x x x 1 x) transmitters controlled by DTR available
#define HWOR_DUPLEX_RTS    0x00000400  // (x x x x x 1 x x) transmitters controlled by RTS available
#define HWOR_DUPLEX_AUTO   0x00000800  // (x x x x 1 x x x) Auto Toggle mode available
#define HWOR_RECEIVE_NXMT  0x00008000  // (1 x x x x x x x) receivers enabled when not transmit enabled feature available
#define HWOR_XMTRVCTRL_CLR 0xfffff00ff  //(0 0 0 0 0 0 0 0) used to clear all transmit and receive control options available 

// high word, low byte in HWOR defines RS-485 signal line configuration 
// options available
#define HWOR_SIGNAL_MODEM  0x00010000  // (x x x x x x x 1) RTS to AUXOUT - CTS from AUXIN - RCLK loopback to XCLK
#define HWOR_SIGNAL_CLOCK  0x00020000  // (x x x x x x 1 x) XCLK to AUXOUT - RCLK from AUXIN - RTS loopback to CTS
#define HWOR_SIGNAL_LOOP   0x00040000  // (x x x x x 1 x x) RTS loopback to CTS - RCLK loopback to XCLK - AUXIN loopback to AUXOUT
#define HWOR_SIGNAL_CLR    0xfff0ffff  // (0 0 0 0 0 0 0 0) used to clear all transmit and receive control options available 

#define HWOR_RS485_MASK    0x000FFF00  // Bit mask used to determine whether to display RS-422/485 property page

#define HWOR_PORT_MASK    0x00100000  // Bit mask used to determine whether to Port property page

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-14  6:07 Comments requested: driver for Quatech PCI serial cards Jonathan Woithe
@ 2012-11-14 12:12 ` Alan Cox
  2012-11-14 23:10   ` Jonathan Woithe
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Cox @ 2012-11-14 12:12 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: linux-serial

> The driver supports a wide range of Quatech DSC/ESC/QSC cards, including the
> custom functionality which allow various aspects of the signal routing to be
> altered under software control.

This has actually been kicked around with another couple of cards. We
have a draft but never test implemented API for binding gpio numbers on
a device to signals for the tty layer.

>  - the name of the module.  Anything with "qt" in it will confuse people.
>    I'm thinking along the lines of serial-quatech or quatech-serial:
>    comments welcome.

qt is fine in kernel - just be careful of the qt usb module.

I think in the same place I'd start by just adding the needed identifiers
to the 8250_pci driver along with any needed init helper.

For the ioctls a lot appear to be just exposing values which as you say
would fit sysfs. The tty layer now (as of 3.7rc) usefully supports sysfs
nodes on a tty itself too.

The basic ones are already handled by 8250_pci.c (QSC100, DSC100,
ESC100D, ESC100M).

As far as I can see the issues are

1.	Clock multiplier feature

Supportable by flags in 8250.c and worst case by providing a custom
set_termios. No API needed.

2.	RS485/RS422 options

Supportable by adding TIOCRS485 handling/callout to 8250.c (needs doing
anyway)

The RS485 ioctl might need some extending but we've been gradually doing
this as we hit chips with more features in hardware so that's not a
problem beyond synchronizing with other platforms who can use the extra
flags.

Alan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-14 12:12 ` Alan Cox
@ 2012-11-14 23:10   ` Jonathan Woithe
  2012-11-14 23:57     ` Alan Cox
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-14 23:10 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial, jwoithe

On Wed, Nov 14, 2012 at 12:12:22PM +0000, Alan Cox wrote:
> >  - the name of the module.  Anything with "qt" in it will confuse people.
> >    I'm thinking along the lines of serial-quatech or quatech-serial:
> >    comments welcome.
> 
> qt is fine in kernel - just be careful of the qt usb module.

Ok.

> I think in the same place I'd start by just adding the needed identifiers
> to the 8250_pci driver along with any needed init helper.

Is this in relation to the proposed signal routing API?

> For the ioctls a lot appear to be just exposing values which as you say
> would fit sysfs. The tty layer now (as of 3.7rc) usefully supports sysfs
> nodes on a tty itself too.

Do I take it from this that the sysfs route is the only way this driver is
likely to get merged?  If that's the case the configuration binaries
supplied by Quatech (which are not opensource unfortunately) will not be
compatible with the new driver, necessitating the writing of new replacement
userspace utilities.

> As far as I can see the issues are
> 
> 1.	Clock multiplier feature
> 
> Supportable by flags in 8250.c and worst case by providing a custom
> set_termios. No API needed.
> 
> 2.	RS485/RS422 options
> 
> Supportable by adding TIOCRS485 handling/callout to 8250.c (needs doing
> anyway)

Based on this, is it fair to say that the driver in its current basic form
(clean-ups notwithstanding) cannot be considered for inclusion in mainline?
While time allows me to do cleanups to the existing code, porting the
functionality to totally different mechanisms (ie: away from ioctl) is
unfortunately not something I would have the time for now (mostly because I
would first have to learn those other interfaces).  My employer would simply
say to find another card, unless there were existing examples of this sort
of usage which I can use as a template.

The DSC-200/300 card (which is what I have) was initially preferred for a
new design because we've used it in other non-Linux systems in the past.  We
don't want to be stuck with maintaining an out-of-tree driver to facilitate
this though, so mainlining it is the only feasible route to us using it in
our upcoming system.  I'm guessing that the difficulties with the driver as
it's currently structured may have been the reason it was never pushed to
mainline by the original author in 2007.

> The RS485 ioctl might need some extending but we've been gradually doing
> this as we hit chips with more features in hardware ...

As far as I know the Quatech cards in question do RS422 but not explicitly
RS485 (that is, their specification doesn't mention RS485).  But I take it
that "RS485 ioctl" is what would be used to control additional RS422
features as well.

Regards
  jonathan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-14 23:10   ` Jonathan Woithe
@ 2012-11-14 23:57     ` Alan Cox
  2012-11-15  0:11       ` Jonathan Woithe
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Cox @ 2012-11-14 23:57 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: linux-serial

On Thu, 15 Nov 2012 09:40:54 +1030
Jonathan Woithe <jwoithe@just42.net> wrote:

> On Wed, Nov 14, 2012 at 12:12:22PM +0000, Alan Cox wrote:
> > >  - the name of the module.  Anything with "qt" in it will confuse people.
> > >    I'm thinking along the lines of serial-quatech or quatech-serial:
> > >    comments welcome.
> > 
> > qt is fine in kernel - just be careful of the qt usb module.
> 
> Ok.
> 
> > I think in the same place I'd start by just adding the needed identifiers
> > to the 8250_pci driver along with any needed init helper.
> 
> Is this in relation to the proposed signal routing API?

No in relation to throwing that driver away and doing it right.

> supplied by Quatech (which are not opensource unfortunately) will not be
> compatible with the new driver, necessitating the writing of new replacement
> userspace utilities.

tragedy 8)

> > As far as I can see the issues are
> > 
> > 1.	Clock multiplier feature
> > 
> > Supportable by flags in 8250.c and worst case by providing a custom
> > set_termios. No API needed.
> > 
> > 2.	RS485/RS422 options
> > 
> > Supportable by adding TIOCRS485 handling/callout to 8250.c (needs doing
> > anyway)
> 
> Based on this, is it fair to say that the driver in its current basic form
> (clean-ups notwithstanding) cannot be considered for inclusion in mainline?

It is.

> our upcoming system.  I'm guessing that the difficulties with the driver as
> it's currently structured may have been the reason it was never pushed to
> mainline by the original author in 2007.

There are not many difficulties I can see. You just add the identifiers
to the 8250_pci tables and a couple of small bits of code and everything
but the RS422/485 stuff just works.

If you need the RS422 stuff then yes its a bit more work.

Alan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-14 23:57     ` Alan Cox
@ 2012-11-15  0:11       ` Jonathan Woithe
  2012-11-15 15:53         ` Alan Cox
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-15  0:11 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial, jwoithe

On Wed, Nov 14, 2012 at 11:57:19PM +0000, Alan Cox wrote:
> > our upcoming system.  I'm guessing that the difficulties with the driver as
> > it's currently structured may have been the reason it was never pushed to
> > mainline by the original author in 2007.
> 
> There are not many difficulties I can see. You just add the identifiers
> to the 8250_pci tables and a couple of small bits of code and everything
> but the RS422/485 stuff just works.
> 
> If you need the RS422 stuff then yes its a bit more work.

Unfortunately we need the RS422 stuff - that's the reason for using the
DSC-200/300 card.

Thanks for the comments and feedback - I'll take another look at things in
the light of the information.  If the functionality we require requires a
significant amount of new code to be written, I suspect that the work will
be deemed too time consuming by those who call the shots.  We'll see.

Regards
  jonathan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-15  0:11       ` Jonathan Woithe
@ 2012-11-15 15:53         ` Alan Cox
  2012-11-15 22:23           ` Jonathan Woithe
  2012-11-28  2:29           ` Jonathan Woithe
  0 siblings, 2 replies; 14+ messages in thread
From: Alan Cox @ 2012-11-15 15:53 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: linux-serial

On Thu, 15 Nov 2012 10:41:10 +1030
Jonathan Woithe <jwoithe@just42.net> wrote:

> On Wed, Nov 14, 2012 at 11:57:19PM +0000, Alan Cox wrote:
> > > our upcoming system.  I'm guessing that the difficulties with the driver as
> > > it's currently structured may have been the reason it was never pushed to
> > > mainline by the original author in 2007.
> > 
> > There are not many difficulties I can see. You just add the identifiers
> > to the 8250_pci tables and a couple of small bits of code and everything
> > but the RS422/485 stuff just works.
> > 
> > If you need the RS422 stuff then yes its a bit more work.
> 
> Unfortunately we need the RS422 stuff - that's the reason for using the
> DSC-200/300 card.
> 
> Thanks for the comments and feedback - I'll take another look at things in
> the light of the information.  If the functionality we require requires a
> significant amount of new code to be written, I suspect that the work will
> be deemed too time consuming by those who call the shots.  We'll see.

First guess at what you need except for the RS422 registers is attached. To
get the RS422 features working needs the tool tracing (or driver printks adding)
to see what QMCR and QOPR bits map to which RS422 features.

Given that if you just force the bits you want in with the RS422 warning I expect
it'll just work for your case and should then be trivial to add the ioctl.

Alan



commit b6175d06d8b101e2ae523b92a91dad0150f677fa
Author: Alan Cox <alan@linux.intel.com>
Date:   Thu Nov 15 13:42:37 2012 +0000

    quatech: add the other serial identifiers
    
    Jonathan Woithe posted an out of tree enabler/control module for these cards. Lift
    the relevant identifiers and put them in the 8250_pci driver.
    
    Signed-off-by: Alan Cox <alan@linux.intel.com>

diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c
index 508063b..9a27e92 100644
--- a/drivers/tty/serial/8250/8250_pci.c
+++ b/drivers/tty/serial/8250/8250_pci.c
@@ -1040,6 +1040,251 @@ static int pci_asix_setup(struct serial_private *priv,
 	return pci_default_setup(priv, board, port, idx);
 }
 
+/* Quatech devices have their own extra interface features */
+
+struct quatech_feature {
+	u16 devid;
+	bool amcc;
+};
+
+#define QPCR_TEST_FOR1		0x3F
+#define QPCR_TEST_GET1		0x00
+#define QPCR_TEST_FOR2		0x40
+#define QPCR_TEST_GET2		0x40
+#define QPCR_TEST_FOR3		0x80
+#define QPCR_TEST_GET3		0x40
+#define QPCR_TEST_FOR4		0xC0
+#define QPCR_TEST_GET4		0x80
+
+#define QOPR_CLOCK_X1		0x0000
+#define QOPR_CLOCK_X2		0x0001
+#define QOPR_CLOCK_X4		0x0002
+#define QOPR_CLOCK_X8		0x0003
+#define QOPR_CLOCK_RATE_MASK	0x0003
+
+
+static struct quatech_feature quatech_cards[] = {
+	{ PCI_DEVICE_ID_QUATECH_QSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100D,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100M,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SPPXP_100, 0 },
+	{ 0, }
+};
+
+static int pci_quatech_amcc(u16 devid)
+{
+	struct quatech_feature *qf = &quatech_cards[0];
+	while (qf->devid) {
+		if (qf->devid == devid)
+			return qf->amcc;
+		qf++;
+	}
+	pr_err("quatech: unknown port type '0x%04X'.\n", devid);
+	return 0;
+};
+
+static int pci_quatech_rqopr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+	return val;
+}
+
+static void pci_quatech_wqopr(struct uart_8250_port *port, u8 qopr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(qopr, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_rqmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val, qmcr;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	qmcr = inb(base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+
+	return qmcr;
+}
+
+static void pci_quatech_wqmcr(struct uart_8250_port *port, u8 qmcr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	outb(qmcr, base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_has_qmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	if (val & 0x20) {
+		outb(0x80, UART_LCR);
+		if (!(inb(UART_SCR) & 0x20)) {
+			outb(LCR, base + UART_LCR);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_test(struct uart_8250_port *port)
+{
+	u8 reg;
+	u8 qopr = pci_quatech_rqopr(port);
+	pci_quatech_wqopr(port, qopr & QPCR_TEST_FOR1);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET1)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR2);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET2)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR3);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET3)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR4);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET4)
+		return -EINVAL;
+
+	pci_quatech_wqopr(port, qopr);
+	return 0;	
+}
+
+static int pci_quatech_clock(struct uart_8250_port *port)
+{
+	u8 qopr, reg, set;
+	unsigned long clock;
+
+	if (pci_quatech_test(port) < 0)
+		return 1843200;
+
+	qopr = pci_quatech_rqopr(port);
+
+	pci_quatech_wqopr(port, qopr & ~QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (reg & QOPR_CLOCK_X8) {
+		clock = 1843200;
+		goto out;
+	}
+	pci_quatech_wqopr(port, qopr | QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (!(reg & QOPR_CLOCK_X8)) {
+		clock = 1843200;
+		goto out;
+	}
+	reg &= QOPR_CLOCK_X8;
+	if (reg == QOPR_CLOCK_X2) {
+		clock =  3685400;
+		set = QOPR_CLOCK_X2;
+	} else if (reg == QOPR_CLOCK_X4) {
+		clock = 7372800;
+		set = QOPR_CLOCK_X4;
+	} else if (reg == QOPR_CLOCK_X8) {
+		clock = 14745600;
+		set = QOPR_CLOCK_X8;
+	} else {
+		clock = 1843200;
+		set = QOPR_CLOCK_X1;
+	}
+	qopr &= ~QOPR_CLOCK_RATE_MASK;
+	qopr |= set;
+
+out:
+	pci_quatech_wqopr(port, qopr);
+	return clock;
+}
+
+static int pci_quatech_rs422(struct uart_8250_port *port)
+{
+	u8 qmcr;
+	int rs422 = 0;
+
+	if (!pci_quatech_has_qmcr(port))
+		return 0;
+	qmcr = pci_quatech_rqmcr(port);
+	pci_quatech_wqmcr(port, 0xFF);
+	if (pci_quatech_rqmcr(port))
+		rs422 = 1;
+	pci_quatech_wqmcr(port, qmcr);
+	return rs422;
+}
+
+static int pci_quatech_init(struct pci_dev *dev)
+{
+	if (pci_quatech_amcc(dev->device)) {
+		unsigned long base = pci_resource_start(dev, 0);
+		if (base) {
+			u32 tmp;
+			outl(inl(base + 0x38), base + 0x38);
+			tmp = inl(base + 0x3c);
+			outl(tmp | 0x01000000, base + 0x3c);
+			outl(tmp, base + 0x3c);
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_setup(struct serial_private *priv,
+		  const struct pciserial_board *board,
+		  struct uart_8250_port *port, int idx)
+{
+	/* Set up the clocking */
+	port->port.uartclk = pci_quatech_clock(port);
+	/* For now just warn about RS422 */
+	if (pci_quatech_rs422(port)) 
+		pr_warn( "quatech: RS422 feature set not currently supported.\n");
+	return pci_default_setup(priv, board, port, idx);
+}
+
+static void __devexit pci_quatech_exit(struct pci_dev *dev)
+{
+}
+
 static int pci_default_setup(struct serial_private *priv,
 		  const struct pciserial_board *board,
 		  struct uart_8250_port *port, int idx)
@@ -1503,6 +1748,16 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = {
 		.setup		= pci_default_setup,
 		.exit		= __devexit_p(pci_plx9050_exit),
 	},
+	/* Quatech */
+	{
+		.vendor		= PCI_VENDOR_ID_QUATECH,
+		.device		= PCI_ANY_ID,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.init		= pci_quatech_init,
+		.setup		= pci_quatech_setup,
+		.exit		= __devexit_p(pci_quatech_exit),
+	},
 	/*
 	 * SBS Technologies, Inc., PMC-OCTALPRO 232
 	 */
@@ -3257,18 +3512,70 @@ static struct pci_device_id serial_pci_tbl[] = {
 	{	PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_ROMULUS,
 		0x10b5, 0x106a, 0, 0,
 		pbn_plx_romulus },
+	/*
+	 * Quatech cards. These actually have configurable clocks but for
+	 * now we just use the default.
+	 *
+	 * 100 series are RS232, 200 series RS422, 
+	 */
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100D,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100M,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b0_8_115200 },
+
 	{	PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_OXSEMI_16PCI954,
 		PCI_VENDOR_ID_SPECIALIX, PCI_SUBDEVICE_ID_SPECIALIX_SPEED4,
 		0, 0,
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9d36b82..ce45006 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -1867,8 +1867,23 @@
 #define PCI_VENDOR_ID_QUATECH		0x135C
 #define PCI_DEVICE_ID_QUATECH_QSC100	0x0010
 #define PCI_DEVICE_ID_QUATECH_DSC100	0x0020
+#define PCI_DEVICE_ID_QUATECH_DSC200	0x0030
+#define PCI_DEVICE_ID_QUATECH_QSC200	0x0040
 #define PCI_DEVICE_ID_QUATECH_ESC100D	0x0050
 #define PCI_DEVICE_ID_QUATECH_ESC100M	0x0060
+#define PCI_DEVICE_ID_QUATECH_QSCP100	0x0120
+#define PCI_DEVICE_ID_QUATECH_DSCP100	0x0130
+#define PCI_DEVICE_ID_QUATECH_QSCP200	0x0140
+#define PCI_DEVICE_ID_QUATECH_DSCP200	0x0150
+#define PCI_DEVICE_ID_QUATECH_QSCLP100	0x0170
+#define PCI_DEVICE_ID_QUATECH_DSCLP100	0x0180
+#define PCI_DEVICE_ID_QUATECH_DSC100E	0x0181
+#define PCI_DEVICE_ID_QUATECH_SSCLP100	0x0190
+#define PCI_DEVICE_ID_QUATECH_QSCLP200	0x01A0
+#define PCI_DEVICE_ID_QUATECH_DSCLP200	0x01B0
+#define PCI_DEVICE_ID_QUATECH_DSC200E	0x01B1
+#define PCI_DEVICE_ID_QUATECH_SSCLP200	0x01C0
+#define PCI_DEVICE_ID_QUATECH_ESCLP100	0x01E0
 #define PCI_DEVICE_ID_QUATECH_SPPXP_100 0x0278
 
 #define PCI_VENDOR_ID_SEALEVEL		0x135e

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-15 15:53         ` Alan Cox
@ 2012-11-15 22:23           ` Jonathan Woithe
  2012-11-28  2:29           ` Jonathan Woithe
  1 sibling, 0 replies; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-15 22:23 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial, jwoithe

On Thu, Nov 15, 2012 at 03:53:47PM +0000, Alan Cox wrote:
> On Thu, 15 Nov 2012 10:41:10 +1030
> Jonathan Woithe <jwoithe@just42.net> wrote:
> 
> > On Wed, Nov 14, 2012 at 11:57:19PM +0000, Alan Cox wrote:
> > > > our upcoming system.  I'm guessing that the difficulties with the driver as
> > > > it's currently structured may have been the reason it was never pushed to
> > > > mainline by the original author in 2007.
> > > 
> > > There are not many difficulties I can see. You just add the identifiers
> > > to the 8250_pci tables and a couple of small bits of code and everything
> > > but the RS422/485 stuff just works.
> > > 
> > > If you need the RS422 stuff then yes its a bit more work.
> > 
> > Unfortunately we need the RS422 stuff - that's the reason for using the
> > DSC-200/300 card.
> > 
> > Thanks for the comments and feedback - I'll take another look at things in
> > the light of the information.  If the functionality we require requires a
> > significant amount of new code to be written, I suspect that the work will
> > be deemed too time consuming by those who call the shots.  We'll see.
> 
> First guess at what you need except for the RS422 registers is attached. To
> get the RS422 features working needs the tool tracing (or driver printks adding)
> to see what QMCR and QOPR bits map to which RS422 features.
> 
> Given that if you just force the bits you want in with the RS422 warning I expect
> it'll just work for your case and should then be trivial to add the ioctl.

Thanks very much for taking the time to do this.  I'm away from the office
today but will test it on Monday.  I have been given the go-ahead to spend a
small amount of time to see if I can get the DSC card working for our case;
with this patch as a starting point I think progress will be made.

jonathan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-15 15:53         ` Alan Cox
  2012-11-15 22:23           ` Jonathan Woithe
@ 2012-11-28  2:29           ` Jonathan Woithe
  2012-11-28 13:24             ` Alan Cox
  1 sibling, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-28  2:29 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial, jwoithe

On Thu, Nov 15, 2012 at 03:53:47PM +0000, Alan Cox wrote:
> > Unfortunately we need the RS422 stuff - that's the reason for using the
> > DSC-200/300 card.
> > 
> > Thanks for the comments and feedback - I'll take another look at things in
> > the light of the information.  If the functionality we require requires a
> > significant amount of new code to be written, I suspect that the work will
> > be deemed too time consuming by those who call the shots.  We'll see.
> 
> First guess at what you need except for the RS422 registers is attached.
> To get the RS422 features working needs the tool tracing (or driver
> printks adding) to see what QMCR and QOPR bits map to which RS422
> features.
> 
> Given that if you just force the bits you want in with the RS422 warning I
> expect it'll just work for your case and should then be trivial to add the
> ioctl.

Apologies for the delay in getting back to this.

With a minor change (the port IO base must be set prior to calling
pci_quatech_clock() and pci_quatech_rs422() in pci_quatech_setup()) this
seems to do the job for us (with a DSC-200/300 card).  As it turns out, the
card configuration we need happens to match the power-on default state of
the card when the jumpers are not in use, so no further manipulation is
required at this stage.  Revised patch is below, against the tty git tree.

I also tweaked the warning a little to make it clearer that it is the
software control of RS422 features that aren't supported.  If the defaults
are fine (or the hardware jumpers are used to control the features) then
RS422 will work just fine.

So far as getting something working for our case, the patch below is all
that is required.  I do know the QMCR and QOPR bit map details for at least
the DSC-200/300 card as it's more or less described in the manual.  I will
try to prepare a second patch which at least creates the necessary defines
for these in the next few days.

With regard to the ioctl, should this be an implementation of the ioctl
provided by the old out-of-tree driver, or is there an established
standard/example which I should write against instead?

jonathan


diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c
index 17b7d26..43c6b4f 100644
--- a/drivers/tty/serial/8250/8250_pci.c
+++ b/drivers/tty/serial/8250/8250_pci.c
@@ -1040,6 +1040,253 @@ static int pci_asix_setup(struct serial_private *priv,
 	return pci_default_setup(priv, board, port, idx);
 }
 
+/* Quatech devices have their own extra interface features */
+
+struct quatech_feature {
+	u16 devid;
+	bool amcc;
+};
+
+#define QPCR_TEST_FOR1		0x3F
+#define QPCR_TEST_GET1		0x00
+#define QPCR_TEST_FOR2		0x40
+#define QPCR_TEST_GET2		0x40
+#define QPCR_TEST_FOR3		0x80
+#define QPCR_TEST_GET3		0x40
+#define QPCR_TEST_FOR4		0xC0
+#define QPCR_TEST_GET4		0x80
+
+#define QOPR_CLOCK_X1		0x0000
+#define QOPR_CLOCK_X2		0x0001
+#define QOPR_CLOCK_X4		0x0002
+#define QOPR_CLOCK_X8		0x0003
+#define QOPR_CLOCK_RATE_MASK	0x0003
+
+
+static struct quatech_feature quatech_cards[] = {
+	{ PCI_DEVICE_ID_QUATECH_QSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100D,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100M,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SPPXP_100, 0 },
+	{ 0, }
+};
+
+static int pci_quatech_amcc(u16 devid)
+{
+	struct quatech_feature *qf = &quatech_cards[0];
+	while (qf->devid) {
+		if (qf->devid == devid)
+			return qf->amcc;
+		qf++;
+	}
+	pr_err("quatech: unknown port type '0x%04X'.\n", devid);
+	return 0;
+};
+
+static int pci_quatech_rqopr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+	return val;
+}
+
+static void pci_quatech_wqopr(struct uart_8250_port *port, u8 qopr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(qopr, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_rqmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val, qmcr;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	qmcr = inb(base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+
+	return qmcr;
+}
+
+static void pci_quatech_wqmcr(struct uart_8250_port *port, u8 qmcr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	outb(qmcr, base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_has_qmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	if (val & 0x20) {
+		outb(0x80, UART_LCR);
+		if (!(inb(UART_SCR) & 0x20)) {
+			outb(LCR, base + UART_LCR);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_test(struct uart_8250_port *port)
+{
+	u8 reg;
+	u8 qopr = pci_quatech_rqopr(port);
+	pci_quatech_wqopr(port, qopr & QPCR_TEST_FOR1);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET1)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR2);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET2)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR3);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET3)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR4);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET4)
+		return -EINVAL;
+
+	pci_quatech_wqopr(port, qopr);
+	return 0;	
+}
+
+static int pci_quatech_clock(struct uart_8250_port *port)
+{
+	u8 qopr, reg, set;
+	unsigned long clock;
+
+	if (pci_quatech_test(port) < 0)
+		return 1843200;
+
+	qopr = pci_quatech_rqopr(port);
+
+	pci_quatech_wqopr(port, qopr & ~QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (reg & QOPR_CLOCK_X8) {
+		clock = 1843200;
+		goto out;
+	}
+	pci_quatech_wqopr(port, qopr | QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (!(reg & QOPR_CLOCK_X8)) {
+		clock = 1843200;
+		goto out;
+	}
+	reg &= QOPR_CLOCK_X8;
+	if (reg == QOPR_CLOCK_X2) {
+		clock =  3685400;
+		set = QOPR_CLOCK_X2;
+	} else if (reg == QOPR_CLOCK_X4) {
+		clock = 7372800;
+		set = QOPR_CLOCK_X4;
+	} else if (reg == QOPR_CLOCK_X8) {
+		clock = 14745600;
+		set = QOPR_CLOCK_X8;
+	} else {
+		clock = 1843200;
+		set = QOPR_CLOCK_X1;
+	}
+	qopr &= ~QOPR_CLOCK_RATE_MASK;
+	qopr |= set;
+
+out:
+	pci_quatech_wqopr(port, qopr);
+	return clock;
+}
+
+static int pci_quatech_rs422(struct uart_8250_port *port)
+{
+	u8 qmcr;
+	int rs422 = 0;
+
+	if (!pci_quatech_has_qmcr(port))
+		return 0;
+	qmcr = pci_quatech_rqmcr(port);
+	pci_quatech_wqmcr(port, 0xFF);
+	if (pci_quatech_rqmcr(port))
+		rs422 = 1;
+	pci_quatech_wqmcr(port, qmcr);
+	return rs422;
+}
+
+static int pci_quatech_init(struct pci_dev *dev)
+{
+	if (pci_quatech_amcc(dev->device)) {
+		unsigned long base = pci_resource_start(dev, 0);
+		if (base) {
+			u32 tmp;
+			outl(inl(base + 0x38), base + 0x38);
+			tmp = inl(base + 0x3c);
+			outl(tmp | 0x01000000, base + 0x3c);
+			outl(tmp, base + 0x3c);
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_setup(struct serial_private *priv,
+		  const struct pciserial_board *board,
+		  struct uart_8250_port *port, int idx)
+{
+	/* Needed by pci_quatech calls below */
+	port->port.iobase = pci_resource_start(priv->dev, FL_GET_BASE(board->flags));
+	/* Set up the clocking */
+	port->port.uartclk = pci_quatech_clock(port);
+	/* For now just warn about RS422 */
+	if (pci_quatech_rs422(port)) 
+		pr_warn( "quatech: software control of RS422 features not currently supported.\n");
+	return pci_default_setup(priv, board, port, idx);
+}
+
+static void __devexit pci_quatech_exit(struct pci_dev *dev)
+{
+}
+
 static int pci_default_setup(struct serial_private *priv,
 		  const struct pciserial_board *board,
 		  struct uart_8250_port *port, int idx)
@@ -1503,6 +1750,16 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = {
 		.setup		= pci_default_setup,
 		.exit		= __devexit_p(pci_plx9050_exit),
 	},
+	/* Quatech */
+	{
+		.vendor		= PCI_VENDOR_ID_QUATECH,
+		.device		= PCI_ANY_ID,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.init		= pci_quatech_init,
+		.setup		= pci_quatech_setup,
+		.exit		= __devexit_p(pci_quatech_exit),
+	},
 	/*
 	 * SBS Technologies, Inc., PMC-OCTALPRO 232
 	 */
@@ -3257,18 +3514,70 @@ static struct pci_device_id serial_pci_tbl[] = {
 	{	PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_ROMULUS,
 		0x10b5, 0x106a, 0, 0,
 		pbn_plx_romulus },
+	/*
+	 * Quatech cards. These actually have configurable clocks but for
+	 * now we just use the default.
+	 *
+	 * 100 series are RS232, 200 series RS422, 
+	 */
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100D,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100M,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b0_8_115200 },
+
 	{	PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_OXSEMI_16PCI954,
 		PCI_VENDOR_ID_SPECIALIX, PCI_SUBDEVICE_ID_SPECIALIX_SPEED4,
 		0, 0,
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9d36b82..ce45006 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -1867,8 +1867,23 @@
 #define PCI_VENDOR_ID_QUATECH		0x135C
 #define PCI_DEVICE_ID_QUATECH_QSC100	0x0010
 #define PCI_DEVICE_ID_QUATECH_DSC100	0x0020
+#define PCI_DEVICE_ID_QUATECH_DSC200	0x0030
+#define PCI_DEVICE_ID_QUATECH_QSC200	0x0040
 #define PCI_DEVICE_ID_QUATECH_ESC100D	0x0050
 #define PCI_DEVICE_ID_QUATECH_ESC100M	0x0060
+#define PCI_DEVICE_ID_QUATECH_QSCP100	0x0120
+#define PCI_DEVICE_ID_QUATECH_DSCP100	0x0130
+#define PCI_DEVICE_ID_QUATECH_QSCP200	0x0140
+#define PCI_DEVICE_ID_QUATECH_DSCP200	0x0150
+#define PCI_DEVICE_ID_QUATECH_QSCLP100	0x0170
+#define PCI_DEVICE_ID_QUATECH_DSCLP100	0x0180
+#define PCI_DEVICE_ID_QUATECH_DSC100E	0x0181
+#define PCI_DEVICE_ID_QUATECH_SSCLP100	0x0190
+#define PCI_DEVICE_ID_QUATECH_QSCLP200	0x01A0
+#define PCI_DEVICE_ID_QUATECH_DSCLP200	0x01B0
+#define PCI_DEVICE_ID_QUATECH_DSC200E	0x01B1
+#define PCI_DEVICE_ID_QUATECH_SSCLP200	0x01C0
+#define PCI_DEVICE_ID_QUATECH_ESCLP100	0x01E0
 #define PCI_DEVICE_ID_QUATECH_SPPXP_100 0x0278
 
 #define PCI_VENDOR_ID_SEALEVEL		0x135e


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-28  2:29           ` Jonathan Woithe
@ 2012-11-28 13:24             ` Alan Cox
  2012-11-28 13:41               ` Jonathan Woithe
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Cox @ 2012-11-28 13:24 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: linux-serial

> So far as getting something working for our case, the patch below is all
> that is required.  I do know the QMCR and QOPR bit map details for at least
> the DSC-200/300 card as it's more or less described in the manual.  I will
> try to prepare a second patch which at least creates the necessary defines
> for these in the next few days.

Thanks.

> With regard to the ioctl, should this be an implementation of the ioctl
> provided by the old out-of-tree driver, or is there an established
> standard/example which I should write against instead?

We have a standard (if possibly needing a few bits adding) ioctl for
basic RS422/485 features. That's TIOCG/SRS485 (which for all the naming
is also kind of 422 and other related bits as well)

Can we get a Signed-off-by: for the first patch so we can try and get it
into 3.8 ?

Alan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-28 13:24             ` Alan Cox
@ 2012-11-28 13:41               ` Jonathan Woithe
  2012-11-28 17:30                 ` Greg KH
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-28 13:41 UTC (permalink / raw)
  To: Alan Cox; +Cc: linux-serial, jwoithe

On Wed, Nov 28, 2012 at 01:24:19PM +0000, Alan Cox wrote:
> > With regard to the ioctl, should this be an implementation of the ioctl
> > provided by the old out-of-tree driver, or is there an established
> > standard/example which I should write against instead?
> 
> We have a standard (if possibly needing a few bits adding) ioctl for
> basic RS422/485 features. That's TIOCG/SRS485 (which for all the naming
> is also kind of 422 and other related bits as well)

Ok, thanks for the pointer.  I'll take a look at this in due course.

> Can we get a Signed-off-by: for the first patch so we can try and get it
> into 3.8 ?

Sure.  See below.  The rest of the previous commit message was fine so I
haven't replicated it here.

jonathan



Signed-off-by: Jonathan Woithe <jwoithe@just42.net>

diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c
index 17b7d26..43c6b4f 100644
--- a/drivers/tty/serial/8250/8250_pci.c
+++ b/drivers/tty/serial/8250/8250_pci.c
@@ -1040,6 +1040,253 @@ static int pci_asix_setup(struct serial_private *priv,
 	return pci_default_setup(priv, board, port, idx);
 }
 
+/* Quatech devices have their own extra interface features */
+
+struct quatech_feature {
+	u16 devid;
+	bool amcc;
+};
+
+#define QPCR_TEST_FOR1		0x3F
+#define QPCR_TEST_GET1		0x00
+#define QPCR_TEST_FOR2		0x40
+#define QPCR_TEST_GET2		0x40
+#define QPCR_TEST_FOR3		0x80
+#define QPCR_TEST_GET3		0x40
+#define QPCR_TEST_FOR4		0xC0
+#define QPCR_TEST_GET4		0x80
+
+#define QOPR_CLOCK_X1		0x0000
+#define QOPR_CLOCK_X2		0x0001
+#define QOPR_CLOCK_X4		0x0002
+#define QOPR_CLOCK_X8		0x0003
+#define QOPR_CLOCK_RATE_MASK	0x0003
+
+
+static struct quatech_feature quatech_cards[] = {
+	{ PCI_DEVICE_ID_QUATECH_QSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100D,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100M,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SPPXP_100, 0 },
+	{ 0, }
+};
+
+static int pci_quatech_amcc(u16 devid)
+{
+	struct quatech_feature *qf = &quatech_cards[0];
+	while (qf->devid) {
+		if (qf->devid == devid)
+			return qf->amcc;
+		qf++;
+	}
+	pr_err("quatech: unknown port type '0x%04X'.\n", devid);
+	return 0;
+};
+
+static int pci_quatech_rqopr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+	return val;
+}
+
+static void pci_quatech_wqopr(struct uart_8250_port *port, u8 qopr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(qopr, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_rqmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val, qmcr;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	qmcr = inb(base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+
+	return qmcr;
+}
+
+static void pci_quatech_wqmcr(struct uart_8250_port *port, u8 qmcr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	outb(qmcr, base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_has_qmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	if (val & 0x20) {
+		outb(0x80, UART_LCR);
+		if (!(inb(UART_SCR) & 0x20)) {
+			outb(LCR, base + UART_LCR);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_test(struct uart_8250_port *port)
+{
+	u8 reg;
+	u8 qopr = pci_quatech_rqopr(port);
+	pci_quatech_wqopr(port, qopr & QPCR_TEST_FOR1);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET1)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR2);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET2)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR3);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET3)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR4);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET4)
+		return -EINVAL;
+
+	pci_quatech_wqopr(port, qopr);
+	return 0;	
+}
+
+static int pci_quatech_clock(struct uart_8250_port *port)
+{
+	u8 qopr, reg, set;
+	unsigned long clock;
+
+	if (pci_quatech_test(port) < 0)
+		return 1843200;
+
+	qopr = pci_quatech_rqopr(port);
+
+	pci_quatech_wqopr(port, qopr & ~QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (reg & QOPR_CLOCK_X8) {
+		clock = 1843200;
+		goto out;
+	}
+	pci_quatech_wqopr(port, qopr | QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (!(reg & QOPR_CLOCK_X8)) {
+		clock = 1843200;
+		goto out;
+	}
+	reg &= QOPR_CLOCK_X8;
+	if (reg == QOPR_CLOCK_X2) {
+		clock =  3685400;
+		set = QOPR_CLOCK_X2;
+	} else if (reg == QOPR_CLOCK_X4) {
+		clock = 7372800;
+		set = QOPR_CLOCK_X4;
+	} else if (reg == QOPR_CLOCK_X8) {
+		clock = 14745600;
+		set = QOPR_CLOCK_X8;
+	} else {
+		clock = 1843200;
+		set = QOPR_CLOCK_X1;
+	}
+	qopr &= ~QOPR_CLOCK_RATE_MASK;
+	qopr |= set;
+
+out:
+	pci_quatech_wqopr(port, qopr);
+	return clock;
+}
+
+static int pci_quatech_rs422(struct uart_8250_port *port)
+{
+	u8 qmcr;
+	int rs422 = 0;
+
+	if (!pci_quatech_has_qmcr(port))
+		return 0;
+	qmcr = pci_quatech_rqmcr(port);
+	pci_quatech_wqmcr(port, 0xFF);
+	if (pci_quatech_rqmcr(port))
+		rs422 = 1;
+	pci_quatech_wqmcr(port, qmcr);
+	return rs422;
+}
+
+static int pci_quatech_init(struct pci_dev *dev)
+{
+	if (pci_quatech_amcc(dev->device)) {
+		unsigned long base = pci_resource_start(dev, 0);
+		if (base) {
+			u32 tmp;
+			outl(inl(base + 0x38), base + 0x38);
+			tmp = inl(base + 0x3c);
+			outl(tmp | 0x01000000, base + 0x3c);
+			outl(tmp, base + 0x3c);
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_setup(struct serial_private *priv,
+		  const struct pciserial_board *board,
+		  struct uart_8250_port *port, int idx)
+{
+	/* Needed by pci_quatech calls below */
+	port->port.iobase = pci_resource_start(priv->dev, FL_GET_BASE(board->flags));
+	/* Set up the clocking */
+	port->port.uartclk = pci_quatech_clock(port);
+	/* For now just warn about RS422 */
+	if (pci_quatech_rs422(port)) 
+		pr_warn( "quatech: software control of RS422 features not currently supported.\n");
+	return pci_default_setup(priv, board, port, idx);
+}
+
+static void __devexit pci_quatech_exit(struct pci_dev *dev)
+{
+}
+
 static int pci_default_setup(struct serial_private *priv,
 		  const struct pciserial_board *board,
 		  struct uart_8250_port *port, int idx)
@@ -1503,6 +1750,16 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = {
 		.setup		= pci_default_setup,
 		.exit		= __devexit_p(pci_plx9050_exit),
 	},
+	/* Quatech */
+	{
+		.vendor		= PCI_VENDOR_ID_QUATECH,
+		.device		= PCI_ANY_ID,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.init		= pci_quatech_init,
+		.setup		= pci_quatech_setup,
+		.exit		= __devexit_p(pci_quatech_exit),
+	},
 	/*
 	 * SBS Technologies, Inc., PMC-OCTALPRO 232
 	 */
@@ -3257,18 +3514,70 @@ static struct pci_device_id serial_pci_tbl[] = {
 	{	PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_ROMULUS,
 		0x10b5, 0x106a, 0, 0,
 		pbn_plx_romulus },
+	/*
+	 * Quatech cards. These actually have configurable clocks but for
+	 * now we just use the default.
+	 *
+	 * 100 series are RS232, 200 series RS422, 
+	 */
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100D,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100M,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b0_8_115200 },
+
 	{	PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_OXSEMI_16PCI954,
 		PCI_VENDOR_ID_SPECIALIX, PCI_SUBDEVICE_ID_SPECIALIX_SPEED4,
 		0, 0,
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9d36b82..ce45006 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -1867,8 +1867,23 @@
 #define PCI_VENDOR_ID_QUATECH		0x135C
 #define PCI_DEVICE_ID_QUATECH_QSC100	0x0010
 #define PCI_DEVICE_ID_QUATECH_DSC100	0x0020
+#define PCI_DEVICE_ID_QUATECH_DSC200	0x0030
+#define PCI_DEVICE_ID_QUATECH_QSC200	0x0040
 #define PCI_DEVICE_ID_QUATECH_ESC100D	0x0050
 #define PCI_DEVICE_ID_QUATECH_ESC100M	0x0060
+#define PCI_DEVICE_ID_QUATECH_QSCP100	0x0120
+#define PCI_DEVICE_ID_QUATECH_DSCP100	0x0130
+#define PCI_DEVICE_ID_QUATECH_QSCP200	0x0140
+#define PCI_DEVICE_ID_QUATECH_DSCP200	0x0150
+#define PCI_DEVICE_ID_QUATECH_QSCLP100	0x0170
+#define PCI_DEVICE_ID_QUATECH_DSCLP100	0x0180
+#define PCI_DEVICE_ID_QUATECH_DSC100E	0x0181
+#define PCI_DEVICE_ID_QUATECH_SSCLP100	0x0190
+#define PCI_DEVICE_ID_QUATECH_QSCLP200	0x01A0
+#define PCI_DEVICE_ID_QUATECH_DSCLP200	0x01B0
+#define PCI_DEVICE_ID_QUATECH_DSC200E	0x01B1
+#define PCI_DEVICE_ID_QUATECH_SSCLP200	0x01C0
+#define PCI_DEVICE_ID_QUATECH_ESCLP100	0x01E0
 #define PCI_DEVICE_ID_QUATECH_SPPXP_100 0x0278
 
 #define PCI_VENDOR_ID_SEALEVEL		0x135e

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-28 13:41               ` Jonathan Woithe
@ 2012-11-28 17:30                 ` Greg KH
  2012-11-28 22:33                   ` Jonathan Woithe
  0 siblings, 1 reply; 14+ messages in thread
From: Greg KH @ 2012-11-28 17:30 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: Alan Cox, linux-serial

On Thu, Nov 29, 2012 at 12:11:40AM +1030, Jonathan Woithe wrote:
> On Wed, Nov 28, 2012 at 01:24:19PM +0000, Alan Cox wrote:
> > > With regard to the ioctl, should this be an implementation of the ioctl
> > > provided by the old out-of-tree driver, or is there an established
> > > standard/example which I should write against instead?
> > 
> > We have a standard (if possibly needing a few bits adding) ioctl for
> > basic RS422/485 features. That's TIOCG/SRS485 (which for all the naming
> > is also kind of 422 and other related bits as well)
> 
> Ok, thanks for the pointer.  I'll take a look at this in due course.
> 
> > Can we get a Signed-off-by: for the first patch so we can try and get it
> > into 3.8 ?
> 
> Sure.  See below.  The rest of the previous commit message was fine so I
> haven't replicated it here.

Please do, otherwise I have to hand-edit the patch to add it back, and
odds are, I will get it wrong...

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Comments requested: driver for Quatech PCI serial cards
  2012-11-28 17:30                 ` Greg KH
@ 2012-11-28 22:33                   ` Jonathan Woithe
  2012-12-10  0:13                     ` Did quatech changes make 3.8? (was: Re: Comments requested: driver for Quatech PCI serial cards) Jonathan Woithe
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-11-28 22:33 UTC (permalink / raw)
  To: Greg KH; +Cc: Alan Cox, linux-serial, Jonathan Woithe

On Wed, Nov 28, 2012 at 09:30:10AM -0800, Greg KH wrote:
> On Thu, Nov 29, 2012 at 12:11:40AM +1030, Jonathan Woithe wrote:
> > On Wed, Nov 28, 2012 at 01:24:19PM +0000, Alan Cox wrote:
> > > Can we get a Signed-off-by: for the first patch so we can try and get it
> > > into 3.8 ?
> > 
> > Sure.  See below.  The rest of the previous commit message was fine so I
> > haven't replicated it here.
> 
> Please do, otherwise I have to hand-edit the patch to add it back, and
> odds are, I will get it wrong...

No problem.  See below.  I took the opportunity to tweak the commit message
a touch.

jonathan


From: Alan Cox <alan@linux.intel.com>

quatech: add the other serial identifiers and preliminary control code
    
Jonathan Woithe posted an out of tree enabler/control module for these
cards.  Lift the relevant identifiers and put them in the 8250_pci driver
along with code used to control custom registers on these cards.
    
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Jonathan Woithe <jwoithe@just42.net>

---
diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c
index 17b7d26..43c6b4f 100644
--- a/drivers/tty/serial/8250/8250_pci.c
+++ b/drivers/tty/serial/8250/8250_pci.c
@@ -1040,6 +1040,253 @@ static int pci_asix_setup(struct serial_private *priv,
 	return pci_default_setup(priv, board, port, idx);
 }
 
+/* Quatech devices have their own extra interface features */
+
+struct quatech_feature {
+	u16 devid;
+	bool amcc;
+};
+
+#define QPCR_TEST_FOR1		0x3F
+#define QPCR_TEST_GET1		0x00
+#define QPCR_TEST_FOR2		0x40
+#define QPCR_TEST_GET2		0x40
+#define QPCR_TEST_FOR3		0x80
+#define QPCR_TEST_GET3		0x40
+#define QPCR_TEST_FOR4		0xC0
+#define QPCR_TEST_GET4		0x80
+
+#define QOPR_CLOCK_X1		0x0000
+#define QOPR_CLOCK_X2		0x0001
+#define QOPR_CLOCK_X4		0x0002
+#define QOPR_CLOCK_X8		0x0003
+#define QOPR_CLOCK_RATE_MASK	0x0003
+
+
+static struct quatech_feature quatech_cards[] = {
+	{ PCI_DEVICE_ID_QUATECH_QSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC100E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200,   1 },
+	{ PCI_DEVICE_ID_QUATECH_DSC200E,  0 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100D,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESC100M,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP100,  1 },
+	{ PCI_DEVICE_ID_QUATECH_QSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_DSCP200,  1 },
+	{ PCI_DEVICE_ID_QUATECH_ESCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP100, 0 },
+	{ PCI_DEVICE_ID_QUATECH_QSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_DSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SSCLP200, 0 },
+	{ PCI_DEVICE_ID_QUATECH_SPPXP_100, 0 },
+	{ 0, }
+};
+
+static int pci_quatech_amcc(u16 devid)
+{
+	struct quatech_feature *qf = &quatech_cards[0];
+	while (qf->devid) {
+		if (qf->devid == devid)
+			return qf->amcc;
+		qf++;
+	}
+	pr_err("quatech: unknown port type '0x%04X'.\n", devid);
+	return 0;
+};
+
+static int pci_quatech_rqopr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+	return val;
+}
+
+static void pci_quatech_wqopr(struct uart_8250_port *port, u8 qopr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(qopr, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_rqmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val, qmcr;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	qmcr = inb(base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+
+	return qmcr;
+}
+
+static void pci_quatech_wqmcr(struct uart_8250_port *port, u8 qmcr)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	outb(val | 0x10, base + UART_SCR);
+	outb(qmcr, base + UART_MCR);
+	outb(val, base + UART_SCR);
+	outb(LCR, base + UART_LCR);
+}
+
+static int pci_quatech_has_qmcr(struct uart_8250_port *port)
+{
+	unsigned long base = port->port.iobase;
+	u8 LCR, val;
+
+	LCR = inb(base + UART_LCR);
+	outb(0xBF, base + UART_LCR);
+	val = inb(base + UART_SCR);
+	if (val & 0x20) {
+		outb(0x80, UART_LCR);
+		if (!(inb(UART_SCR) & 0x20)) {
+			outb(LCR, base + UART_LCR);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_test(struct uart_8250_port *port)
+{
+	u8 reg;
+	u8 qopr = pci_quatech_rqopr(port);
+	pci_quatech_wqopr(port, qopr & QPCR_TEST_FOR1);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET1)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR2);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET2)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR3);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET3)
+		return -EINVAL;
+	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR4);
+	reg = pci_quatech_rqopr(port) & 0xC0;
+	if (reg != QPCR_TEST_GET4)
+		return -EINVAL;
+
+	pci_quatech_wqopr(port, qopr);
+	return 0;	
+}
+
+static int pci_quatech_clock(struct uart_8250_port *port)
+{
+	u8 qopr, reg, set;
+	unsigned long clock;
+
+	if (pci_quatech_test(port) < 0)
+		return 1843200;
+
+	qopr = pci_quatech_rqopr(port);
+
+	pci_quatech_wqopr(port, qopr & ~QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (reg & QOPR_CLOCK_X8) {
+		clock = 1843200;
+		goto out;
+	}
+	pci_quatech_wqopr(port, qopr | QOPR_CLOCK_X8);
+	reg = pci_quatech_rqopr(port);
+	if (!(reg & QOPR_CLOCK_X8)) {
+		clock = 1843200;
+		goto out;
+	}
+	reg &= QOPR_CLOCK_X8;
+	if (reg == QOPR_CLOCK_X2) {
+		clock =  3685400;
+		set = QOPR_CLOCK_X2;
+	} else if (reg == QOPR_CLOCK_X4) {
+		clock = 7372800;
+		set = QOPR_CLOCK_X4;
+	} else if (reg == QOPR_CLOCK_X8) {
+		clock = 14745600;
+		set = QOPR_CLOCK_X8;
+	} else {
+		clock = 1843200;
+		set = QOPR_CLOCK_X1;
+	}
+	qopr &= ~QOPR_CLOCK_RATE_MASK;
+	qopr |= set;
+
+out:
+	pci_quatech_wqopr(port, qopr);
+	return clock;
+}
+
+static int pci_quatech_rs422(struct uart_8250_port *port)
+{
+	u8 qmcr;
+	int rs422 = 0;
+
+	if (!pci_quatech_has_qmcr(port))
+		return 0;
+	qmcr = pci_quatech_rqmcr(port);
+	pci_quatech_wqmcr(port, 0xFF);
+	if (pci_quatech_rqmcr(port))
+		rs422 = 1;
+	pci_quatech_wqmcr(port, qmcr);
+	return rs422;
+}
+
+static int pci_quatech_init(struct pci_dev *dev)
+{
+	if (pci_quatech_amcc(dev->device)) {
+		unsigned long base = pci_resource_start(dev, 0);
+		if (base) {
+			u32 tmp;
+			outl(inl(base + 0x38), base + 0x38);
+			tmp = inl(base + 0x3c);
+			outl(tmp | 0x01000000, base + 0x3c);
+			outl(tmp, base + 0x3c);
+		}
+	}
+	return 0;
+}
+
+static int pci_quatech_setup(struct serial_private *priv,
+		  const struct pciserial_board *board,
+		  struct uart_8250_port *port, int idx)
+{
+	/* Needed by pci_quatech calls below */
+	port->port.iobase = pci_resource_start(priv->dev, FL_GET_BASE(board->flags));
+	/* Set up the clocking */
+	port->port.uartclk = pci_quatech_clock(port);
+	/* For now just warn about RS422 */
+	if (pci_quatech_rs422(port)) 
+		pr_warn( "quatech: software control of RS422 features not currently supported.\n");
+	return pci_default_setup(priv, board, port, idx);
+}
+
+static void __devexit pci_quatech_exit(struct pci_dev *dev)
+{
+}
+
 static int pci_default_setup(struct serial_private *priv,
 		  const struct pciserial_board *board,
 		  struct uart_8250_port *port, int idx)
@@ -1503,6 +1750,16 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = {
 		.setup		= pci_default_setup,
 		.exit		= __devexit_p(pci_plx9050_exit),
 	},
+	/* Quatech */
+	{
+		.vendor		= PCI_VENDOR_ID_QUATECH,
+		.device		= PCI_ANY_ID,
+		.subvendor	= PCI_ANY_ID,
+		.subdevice	= PCI_ANY_ID,
+		.init		= pci_quatech_init,
+		.setup		= pci_quatech_setup,
+		.exit		= __devexit_p(pci_quatech_exit),
+	},
 	/*
 	 * SBS Technologies, Inc., PMC-OCTALPRO 232
 	 */
@@ -3257,18 +3514,70 @@ static struct pci_device_id serial_pci_tbl[] = {
 	{	PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_ROMULUS,
 		0x10b5, 0x106a, 0, 0,
 		pbn_plx_romulus },
+	/*
+	 * Quatech cards. These actually have configurable clocks but for
+	 * now we just use the default.
+	 *
+	 * 100 series are RS232, 200 series RS422, 
+	 */
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200E,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100D,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
 	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100M,
 		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
 		pbn_b1_8_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b1_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_4_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_2_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP200,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b2_1_115200 },
+	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESCLP100,
+		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
+		pbn_b0_8_115200 },
+
 	{	PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_OXSEMI_16PCI954,
 		PCI_VENDOR_ID_SPECIALIX, PCI_SUBDEVICE_ID_SPECIALIX_SPEED4,
 		0, 0,
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index 9d36b82..ce45006 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -1867,8 +1867,23 @@
 #define PCI_VENDOR_ID_QUATECH		0x135C
 #define PCI_DEVICE_ID_QUATECH_QSC100	0x0010
 #define PCI_DEVICE_ID_QUATECH_DSC100	0x0020
+#define PCI_DEVICE_ID_QUATECH_DSC200	0x0030
+#define PCI_DEVICE_ID_QUATECH_QSC200	0x0040
 #define PCI_DEVICE_ID_QUATECH_ESC100D	0x0050
 #define PCI_DEVICE_ID_QUATECH_ESC100M	0x0060
+#define PCI_DEVICE_ID_QUATECH_QSCP100	0x0120
+#define PCI_DEVICE_ID_QUATECH_DSCP100	0x0130
+#define PCI_DEVICE_ID_QUATECH_QSCP200	0x0140
+#define PCI_DEVICE_ID_QUATECH_DSCP200	0x0150
+#define PCI_DEVICE_ID_QUATECH_QSCLP100	0x0170
+#define PCI_DEVICE_ID_QUATECH_DSCLP100	0x0180
+#define PCI_DEVICE_ID_QUATECH_DSC100E	0x0181
+#define PCI_DEVICE_ID_QUATECH_SSCLP100	0x0190
+#define PCI_DEVICE_ID_QUATECH_QSCLP200	0x01A0
+#define PCI_DEVICE_ID_QUATECH_DSCLP200	0x01B0
+#define PCI_DEVICE_ID_QUATECH_DSC200E	0x01B1
+#define PCI_DEVICE_ID_QUATECH_SSCLP200	0x01C0
+#define PCI_DEVICE_ID_QUATECH_ESCLP100	0x01E0
 #define PCI_DEVICE_ID_QUATECH_SPPXP_100 0x0278
 
 #define PCI_VENDOR_ID_SEALEVEL		0x135e

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Did quatech changes make 3.8? (was: Re: Comments requested: driver for Quatech PCI serial cards)
  2012-11-28 22:33                   ` Jonathan Woithe
@ 2012-12-10  0:13                     ` Jonathan Woithe
  2012-12-10  2:19                       ` Greg KH
  0 siblings, 1 reply; 14+ messages in thread
From: Jonathan Woithe @ 2012-12-10  0:13 UTC (permalink / raw)
  To: Greg KH; +Cc: Alan Cox, linux-serial, jwoithe

On Thu, Nov 29, 2012 at 09:03:00AM +1030, Jonathan Woithe wrote:
> On Wed, Nov 28, 2012 at 09:30:10AM -0800, Greg KH wrote:
> > On Thu, Nov 29, 2012 at 12:11:40AM +1030, Jonathan Woithe wrote:
> > > On Wed, Nov 28, 2012 at 01:24:19PM +0000, Alan Cox wrote:
> > > > Can we get a Signed-off-by: for the first patch so we can try and get it
> > > > into 3.8 ?
> > > 
> > > Sure.  See below.  The rest of the previous commit message was fine so I
> > > haven't replicated it here.
> > 
> > Please do, otherwise I have to hand-edit the patch to add it back, and
> > odds are, I will get it wrong...
> 
> No problem.  See below.  I took the opportunity to tweak the commit message
> a touch.

I am wondering (for documentation purposes) whether the quatech changes
below made 3.8 or whether they'll appear in 3.9.  I don't see them in
linux-next or tty-next, but I'm still learning git and might have done
something wrong while searching.

jonathan

> From: Alan Cox <alan@linux.intel.com>
> 
> quatech: add the other serial identifiers and preliminary control code
>     
> Jonathan Woithe posted an out of tree enabler/control module for these
> cards.  Lift the relevant identifiers and put them in the 8250_pci driver
> along with code used to control custom registers on these cards.
>     
> Signed-off-by: Alan Cox <alan@linux.intel.com>
> Signed-off-by: Jonathan Woithe <jwoithe@just42.net>
> 
> ---
> diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c
> index 17b7d26..43c6b4f 100644
> --- a/drivers/tty/serial/8250/8250_pci.c
> +++ b/drivers/tty/serial/8250/8250_pci.c
> @@ -1040,6 +1040,253 @@ static int pci_asix_setup(struct serial_private *priv,
>  	return pci_default_setup(priv, board, port, idx);
>  }
>  
> +/* Quatech devices have their own extra interface features */
> +
> +struct quatech_feature {
> +	u16 devid;
> +	bool amcc;
> +};
> +
> +#define QPCR_TEST_FOR1		0x3F
> +#define QPCR_TEST_GET1		0x00
> +#define QPCR_TEST_FOR2		0x40
> +#define QPCR_TEST_GET2		0x40
> +#define QPCR_TEST_FOR3		0x80
> +#define QPCR_TEST_GET3		0x40
> +#define QPCR_TEST_FOR4		0xC0
> +#define QPCR_TEST_GET4		0x80
> +
> +#define QOPR_CLOCK_X1		0x0000
> +#define QOPR_CLOCK_X2		0x0001
> +#define QOPR_CLOCK_X4		0x0002
> +#define QOPR_CLOCK_X8		0x0003
> +#define QOPR_CLOCK_RATE_MASK	0x0003
> +
> +
> +static struct quatech_feature quatech_cards[] = {
> +	{ PCI_DEVICE_ID_QUATECH_QSC100,   1 },
> +	{ PCI_DEVICE_ID_QUATECH_DSC100,   1 },
> +	{ PCI_DEVICE_ID_QUATECH_DSC100E,  0 },
> +	{ PCI_DEVICE_ID_QUATECH_DSC200,   1 },
> +	{ PCI_DEVICE_ID_QUATECH_DSC200E,  0 },
> +	{ PCI_DEVICE_ID_QUATECH_ESC100D,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_ESC100M,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_QSCP100,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_DSCP100,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_QSCP200,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_DSCP200,  1 },
> +	{ PCI_DEVICE_ID_QUATECH_ESCLP100, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_QSCLP100, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_DSCLP100, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_SSCLP100, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_QSCLP200, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_DSCLP200, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_SSCLP200, 0 },
> +	{ PCI_DEVICE_ID_QUATECH_SPPXP_100, 0 },
> +	{ 0, }
> +};
> +
> +static int pci_quatech_amcc(u16 devid)
> +{
> +	struct quatech_feature *qf = &quatech_cards[0];
> +	while (qf->devid) {
> +		if (qf->devid == devid)
> +			return qf->amcc;
> +		qf++;
> +	}
> +	pr_err("quatech: unknown port type '0x%04X'.\n", devid);
> +	return 0;
> +};
> +
> +static int pci_quatech_rqopr(struct uart_8250_port *port)
> +{
> +	unsigned long base = port->port.iobase;
> +	u8 LCR, val;
> +
> +	LCR = inb(base + UART_LCR);
> +	outb(0xBF, base + UART_LCR);
> +	val = inb(base + UART_SCR);
> +	outb(LCR, base + UART_LCR);
> +	return val;
> +}
> +
> +static void pci_quatech_wqopr(struct uart_8250_port *port, u8 qopr)
> +{
> +	unsigned long base = port->port.iobase;
> +	u8 LCR, val;
> +
> +	LCR = inb(base + UART_LCR);
> +	outb(0xBF, base + UART_LCR);
> +	val = inb(base + UART_SCR);
> +	outb(qopr, base + UART_SCR);
> +	outb(LCR, base + UART_LCR);
> +}
> +
> +static int pci_quatech_rqmcr(struct uart_8250_port *port)
> +{
> +	unsigned long base = port->port.iobase;
> +	u8 LCR, val, qmcr;
> +
> +	LCR = inb(base + UART_LCR);
> +	outb(0xBF, base + UART_LCR);
> +	val = inb(base + UART_SCR);
> +	outb(val | 0x10, base + UART_SCR);
> +	qmcr = inb(base + UART_MCR);
> +	outb(val, base + UART_SCR);
> +	outb(LCR, base + UART_LCR);
> +
> +	return qmcr;
> +}
> +
> +static void pci_quatech_wqmcr(struct uart_8250_port *port, u8 qmcr)
> +{
> +	unsigned long base = port->port.iobase;
> +	u8 LCR, val;
> +
> +	LCR = inb(base + UART_LCR);
> +	outb(0xBF, base + UART_LCR);
> +	val = inb(base + UART_SCR);
> +	outb(val | 0x10, base + UART_SCR);
> +	outb(qmcr, base + UART_MCR);
> +	outb(val, base + UART_SCR);
> +	outb(LCR, base + UART_LCR);
> +}
> +
> +static int pci_quatech_has_qmcr(struct uart_8250_port *port)
> +{
> +	unsigned long base = port->port.iobase;
> +	u8 LCR, val;
> +
> +	LCR = inb(base + UART_LCR);
> +	outb(0xBF, base + UART_LCR);
> +	val = inb(base + UART_SCR);
> +	if (val & 0x20) {
> +		outb(0x80, UART_LCR);
> +		if (!(inb(UART_SCR) & 0x20)) {
> +			outb(LCR, base + UART_LCR);
> +			return 1;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int pci_quatech_test(struct uart_8250_port *port)
> +{
> +	u8 reg;
> +	u8 qopr = pci_quatech_rqopr(port);
> +	pci_quatech_wqopr(port, qopr & QPCR_TEST_FOR1);
> +	reg = pci_quatech_rqopr(port) & 0xC0;
> +	if (reg != QPCR_TEST_GET1)
> +		return -EINVAL;
> +	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR2);
> +	reg = pci_quatech_rqopr(port) & 0xC0;
> +	if (reg != QPCR_TEST_GET2)
> +		return -EINVAL;
> +	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR3);
> +	reg = pci_quatech_rqopr(port) & 0xC0;
> +	if (reg != QPCR_TEST_GET3)
> +		return -EINVAL;
> +	pci_quatech_wqopr(port, (qopr & QPCR_TEST_FOR1)|QPCR_TEST_FOR4);
> +	reg = pci_quatech_rqopr(port) & 0xC0;
> +	if (reg != QPCR_TEST_GET4)
> +		return -EINVAL;
> +
> +	pci_quatech_wqopr(port, qopr);
> +	return 0;	
> +}
> +
> +static int pci_quatech_clock(struct uart_8250_port *port)
> +{
> +	u8 qopr, reg, set;
> +	unsigned long clock;
> +
> +	if (pci_quatech_test(port) < 0)
> +		return 1843200;
> +
> +	qopr = pci_quatech_rqopr(port);
> +
> +	pci_quatech_wqopr(port, qopr & ~QOPR_CLOCK_X8);
> +	reg = pci_quatech_rqopr(port);
> +	if (reg & QOPR_CLOCK_X8) {
> +		clock = 1843200;
> +		goto out;
> +	}
> +	pci_quatech_wqopr(port, qopr | QOPR_CLOCK_X8);
> +	reg = pci_quatech_rqopr(port);
> +	if (!(reg & QOPR_CLOCK_X8)) {
> +		clock = 1843200;
> +		goto out;
> +	}
> +	reg &= QOPR_CLOCK_X8;
> +	if (reg == QOPR_CLOCK_X2) {
> +		clock =  3685400;
> +		set = QOPR_CLOCK_X2;
> +	} else if (reg == QOPR_CLOCK_X4) {
> +		clock = 7372800;
> +		set = QOPR_CLOCK_X4;
> +	} else if (reg == QOPR_CLOCK_X8) {
> +		clock = 14745600;
> +		set = QOPR_CLOCK_X8;
> +	} else {
> +		clock = 1843200;
> +		set = QOPR_CLOCK_X1;
> +	}
> +	qopr &= ~QOPR_CLOCK_RATE_MASK;
> +	qopr |= set;
> +
> +out:
> +	pci_quatech_wqopr(port, qopr);
> +	return clock;
> +}
> +
> +static int pci_quatech_rs422(struct uart_8250_port *port)
> +{
> +	u8 qmcr;
> +	int rs422 = 0;
> +
> +	if (!pci_quatech_has_qmcr(port))
> +		return 0;
> +	qmcr = pci_quatech_rqmcr(port);
> +	pci_quatech_wqmcr(port, 0xFF);
> +	if (pci_quatech_rqmcr(port))
> +		rs422 = 1;
> +	pci_quatech_wqmcr(port, qmcr);
> +	return rs422;
> +}
> +
> +static int pci_quatech_init(struct pci_dev *dev)
> +{
> +	if (pci_quatech_amcc(dev->device)) {
> +		unsigned long base = pci_resource_start(dev, 0);
> +		if (base) {
> +			u32 tmp;
> +			outl(inl(base + 0x38), base + 0x38);
> +			tmp = inl(base + 0x3c);
> +			outl(tmp | 0x01000000, base + 0x3c);
> +			outl(tmp, base + 0x3c);
> +		}
> +	}
> +	return 0;
> +}
> +
> +static int pci_quatech_setup(struct serial_private *priv,
> +		  const struct pciserial_board *board,
> +		  struct uart_8250_port *port, int idx)
> +{
> +	/* Needed by pci_quatech calls below */
> +	port->port.iobase = pci_resource_start(priv->dev, FL_GET_BASE(board->flags));
> +	/* Set up the clocking */
> +	port->port.uartclk = pci_quatech_clock(port);
> +	/* For now just warn about RS422 */
> +	if (pci_quatech_rs422(port)) 
> +		pr_warn( "quatech: software control of RS422 features not currently supported.\n");
> +	return pci_default_setup(priv, board, port, idx);
> +}
> +
> +static void __devexit pci_quatech_exit(struct pci_dev *dev)
> +{
> +}
> +
>  static int pci_default_setup(struct serial_private *priv,
>  		  const struct pciserial_board *board,
>  		  struct uart_8250_port *port, int idx)
> @@ -1503,6 +1750,16 @@ static struct pci_serial_quirk pci_serial_quirks[] __refdata = {
>  		.setup		= pci_default_setup,
>  		.exit		= __devexit_p(pci_plx9050_exit),
>  	},
> +	/* Quatech */
> +	{
> +		.vendor		= PCI_VENDOR_ID_QUATECH,
> +		.device		= PCI_ANY_ID,
> +		.subvendor	= PCI_ANY_ID,
> +		.subdevice	= PCI_ANY_ID,
> +		.init		= pci_quatech_init,
> +		.setup		= pci_quatech_setup,
> +		.exit		= __devexit_p(pci_quatech_exit),
> +	},
>  	/*
>  	 * SBS Technologies, Inc., PMC-OCTALPRO 232
>  	 */
> @@ -3257,18 +3514,70 @@ static struct pci_device_id serial_pci_tbl[] = {
>  	{	PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_ROMULUS,
>  		0x10b5, 0x106a, 0, 0,
>  		pbn_plx_romulus },
> +	/*
> +	 * Quatech cards. These actually have configurable clocks but for
> +	 * now we just use the default.
> +	 *
> +	 * 100 series are RS232, 200 series RS422, 
> +	 */
>  	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC100,
>  		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
>  		pbn_b1_4_115200 },
>  	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100,
>  		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
>  		pbn_b1_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC100E,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSC200E,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSC200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_4_115200 },
>  	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100D,
>  		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
>  		pbn_b1_8_115200 },
>  	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESC100M,
>  		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
>  		pbn_b1_8_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_4_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCP200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_4_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCP200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b1_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_4_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_1_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_QSCLP200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_4_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_DSCLP200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_2_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_SSCLP200,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b2_1_115200 },
> +	{	PCI_VENDOR_ID_QUATECH, PCI_DEVICE_ID_QUATECH_ESCLP100,
> +		PCI_ANY_ID, PCI_ANY_ID, 0, 0,
> +		pbn_b0_8_115200 },
> +
>  	{	PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_OXSEMI_16PCI954,
>  		PCI_VENDOR_ID_SPECIALIX, PCI_SUBDEVICE_ID_SPECIALIX_SPEED4,
>  		0, 0,
> diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
> index 9d36b82..ce45006 100644
> --- a/include/linux/pci_ids.h
> +++ b/include/linux/pci_ids.h
> @@ -1867,8 +1867,23 @@
>  #define PCI_VENDOR_ID_QUATECH		0x135C
>  #define PCI_DEVICE_ID_QUATECH_QSC100	0x0010
>  #define PCI_DEVICE_ID_QUATECH_DSC100	0x0020
> +#define PCI_DEVICE_ID_QUATECH_DSC200	0x0030
> +#define PCI_DEVICE_ID_QUATECH_QSC200	0x0040
>  #define PCI_DEVICE_ID_QUATECH_ESC100D	0x0050
>  #define PCI_DEVICE_ID_QUATECH_ESC100M	0x0060
> +#define PCI_DEVICE_ID_QUATECH_QSCP100	0x0120
> +#define PCI_DEVICE_ID_QUATECH_DSCP100	0x0130
> +#define PCI_DEVICE_ID_QUATECH_QSCP200	0x0140
> +#define PCI_DEVICE_ID_QUATECH_DSCP200	0x0150
> +#define PCI_DEVICE_ID_QUATECH_QSCLP100	0x0170
> +#define PCI_DEVICE_ID_QUATECH_DSCLP100	0x0180
> +#define PCI_DEVICE_ID_QUATECH_DSC100E	0x0181
> +#define PCI_DEVICE_ID_QUATECH_SSCLP100	0x0190
> +#define PCI_DEVICE_ID_QUATECH_QSCLP200	0x01A0
> +#define PCI_DEVICE_ID_QUATECH_DSCLP200	0x01B0
> +#define PCI_DEVICE_ID_QUATECH_DSC200E	0x01B1
> +#define PCI_DEVICE_ID_QUATECH_SSCLP200	0x01C0
> +#define PCI_DEVICE_ID_QUATECH_ESCLP100	0x01E0
>  #define PCI_DEVICE_ID_QUATECH_SPPXP_100 0x0278
>  
>  #define PCI_VENDOR_ID_SEALEVEL		0x135e

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Did quatech changes make 3.8? (was: Re: Comments requested: driver for Quatech PCI serial cards)
  2012-12-10  0:13                     ` Did quatech changes make 3.8? (was: Re: Comments requested: driver for Quatech PCI serial cards) Jonathan Woithe
@ 2012-12-10  2:19                       ` Greg KH
  0 siblings, 0 replies; 14+ messages in thread
From: Greg KH @ 2012-12-10  2:19 UTC (permalink / raw)
  To: Jonathan Woithe; +Cc: Alan Cox, linux-serial

On Mon, Dec 10, 2012 at 10:43:47AM +1030, Jonathan Woithe wrote:
> On Thu, Nov 29, 2012 at 09:03:00AM +1030, Jonathan Woithe wrote:
> > On Wed, Nov 28, 2012 at 09:30:10AM -0800, Greg KH wrote:
> > > On Thu, Nov 29, 2012 at 12:11:40AM +1030, Jonathan Woithe wrote:
> > > > On Wed, Nov 28, 2012 at 01:24:19PM +0000, Alan Cox wrote:
> > > > > Can we get a Signed-off-by: for the first patch so we can try and get it
> > > > > into 3.8 ?
> > > > 
> > > > Sure.  See below.  The rest of the previous commit message was fine so I
> > > > haven't replicated it here.
> > > 
> > > Please do, otherwise I have to hand-edit the patch to add it back, and
> > > odds are, I will get it wrong...
> > 
> > No problem.  See below.  I took the opportunity to tweak the commit message
> > a touch.
> 
> I am wondering (for documentation purposes) whether the quatech changes
> below made 3.8 or whether they'll appear in 3.9.  I don't see them in
> linux-next or tty-next, but I'm still learning git and might have done
> something wrong while searching.

Odd, I somehow missed this, thinking Alan would resend it later, I'll
queue it up next week and it should make 3.8-final.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2012-12-10  2:17 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-11-14  6:07 Comments requested: driver for Quatech PCI serial cards Jonathan Woithe
2012-11-14 12:12 ` Alan Cox
2012-11-14 23:10   ` Jonathan Woithe
2012-11-14 23:57     ` Alan Cox
2012-11-15  0:11       ` Jonathan Woithe
2012-11-15 15:53         ` Alan Cox
2012-11-15 22:23           ` Jonathan Woithe
2012-11-28  2:29           ` Jonathan Woithe
2012-11-28 13:24             ` Alan Cox
2012-11-28 13:41               ` Jonathan Woithe
2012-11-28 17:30                 ` Greg KH
2012-11-28 22:33                   ` Jonathan Woithe
2012-12-10  0:13                     ` Did quatech changes make 3.8? (was: Re: Comments requested: driver for Quatech PCI serial cards) Jonathan Woithe
2012-12-10  2:19                       ` Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.