tty: serial: exar: Relocate sleep wake-up handling
authorAaron Sierra <asierra@xes-inc.com>
Thu, 25 Jan 2018 00:19:23 +0000 (18:19 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 26 Apr 2018 09:02:11 +0000 (11:02 +0200)
[ Upstream commit c7e1b4059075c9e8eed101d7cc5da43e95eb5e18 ]

Exar sleep wake-up handling has been done on a per-channel basis by
virtue of INT0 being accessible from each channel's address space. I
believe this was initially done out of necessity, but now that Exar
devices have their own driver, we can do things more efficiently by
registering a dedicated INT0 handler at the PCI device level.

I see this change providing the following benefits:

    1. If more than one port is active, eliminates the redundant bus
       cycles for reading INT0 on every interrupt.
    2. This note associated with hooking in the per-channel handler in
       8250_port.c is resolved:
        /* Fixme: probably not the best place for this */

Cc: Matt Schulte <matts@commtech-fastcom.com>
Signed-off-by: Aaron Sierra <asierra@xes-inc.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Sasha Levin <alexander.levin@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/8250/8250_exar.c
drivers/tty/serial/8250/8250_port.c

index c55624703fdf04a74fc167b702a50212204e4c62..e0aa5f03004cc6c1754684716835acbd1f7f19b9 100644 (file)
@@ -37,6 +37,7 @@
 #define PCI_DEVICE_ID_EXAR_XR17V4358           0x4358
 #define PCI_DEVICE_ID_EXAR_XR17V8358           0x8358
 
+#define UART_EXAR_INT0         0x80
 #define UART_EXAR_8XMODE       0x88    /* 8X sampling rate select */
 
 #define UART_EXAR_FCTR         0x08    /* Feature Control Register */
@@ -124,6 +125,7 @@ struct exar8250_board {
 struct exar8250 {
        unsigned int            nr;
        struct exar8250_board   *board;
+       void __iomem            *virt;
        int                     line[0];
 };
 
@@ -134,12 +136,9 @@ static int default_setup(struct exar8250 *priv, struct pci_dev *pcidev,
        const struct exar8250_board *board = priv->board;
        unsigned int bar = 0;
 
-       if (!pcim_iomap_table(pcidev)[bar] && !pcim_iomap(pcidev, bar, 0))
-               return -ENOMEM;
-
        port->port.iotype = UPIO_MEM;
        port->port.mapbase = pci_resource_start(pcidev, bar) + offset;
-       port->port.membase = pcim_iomap_table(pcidev)[bar] + offset;
+       port->port.membase = priv->virt + offset;
        port->port.regshift = board->reg_shift;
 
        return 0;
@@ -423,6 +422,25 @@ static void pci_xr17v35x_exit(struct pci_dev *pcidev)
        port->port.private_data = NULL;
 }
 
+/*
+ * These Exar UARTs have an extra interrupt indicator that could fire for a
+ * few interrupts that are not presented/cleared through IIR.  One of which is
+ * a wakeup interrupt when coming out of sleep.  These interrupts are only
+ * cleared by reading global INT0 or INT1 registers as interrupts are
+ * associated with channel 0. The INT[3:0] registers _are_ accessible from each
+ * channel's address space, but for the sake of bus efficiency we register a
+ * dedicated handler at the PCI device level to handle them.
+ */
+static irqreturn_t exar_misc_handler(int irq, void *data)
+{
+       struct exar8250 *priv = data;
+
+       /* Clear all PCI interrupts by reading INT0. No effect on IIR */
+       ioread8(priv->virt + UART_EXAR_INT0);
+
+       return IRQ_HANDLED;
+}
+
 static int
 exar_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *ent)
 {
@@ -451,6 +469,9 @@ exar_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *ent)
                return -ENOMEM;
 
        priv->board = board;
+       priv->virt = pcim_iomap(pcidev, bar, 0);
+       if (!priv->virt)
+               return -ENOMEM;
 
        pci_set_master(pcidev);
 
@@ -464,6 +485,11 @@ exar_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *ent)
        uart.port.irq = pci_irq_vector(pcidev, 0);
        uart.port.dev = &pcidev->dev;
 
+       rc = devm_request_irq(&pcidev->dev, uart.port.irq, exar_misc_handler,
+                        IRQF_SHARED, "exar_uart", priv);
+       if (rc)
+               return rc;
+
        for (i = 0; i < nr_ports && i < maxnr; i++) {
                rc = board->setup(priv, pcidev, &uart, i);
                if (rc) {
index fde34c84e707c493e2fb02b574677b5cfbb07828..e32c51d549c3d3a7e4edc9cbb3421d9ff1285b21 100644 (file)
@@ -458,7 +458,6 @@ static void io_serial_out(struct uart_port *p, int offset, int value)
 }
 
 static int serial8250_default_handle_irq(struct uart_port *port);
-static int exar_handle_irq(struct uart_port *port);
 
 static void set_io_from_upio(struct uart_port *p)
 {
@@ -1903,26 +1902,6 @@ static int serial8250_default_handle_irq(struct uart_port *port)
        return ret;
 }
 
-/*
- * These Exar UARTs have an extra interrupt indicator that could
- * fire for a few unimplemented interrupts.  One of which is a
- * wakeup event when coming out of sleep.  Put this here just
- * to be on the safe side that these interrupts don't go unhandled.
- */
-static int exar_handle_irq(struct uart_port *port)
-{
-       unsigned int iir = serial_port_in(port, UART_IIR);
-       int ret = 0;
-
-       if (((port->type == PORT_XR17V35X) || (port->type == PORT_XR17D15X)) &&
-           serial_port_in(port, UART_EXAR_INT0) != 0)
-               ret = 1;
-
-       ret |= serial8250_handle_irq(port, iir);
-
-       return ret;
-}
-
 /*
  * Newer 16550 compatible parts such as the SC16C650 & Altera 16550 Soft IP
  * have a programmable TX threshold that triggers the THRE interrupt in
@@ -3107,11 +3086,6 @@ static void serial8250_config_port(struct uart_port *port, int flags)
        if (port->type == PORT_UNKNOWN)
                serial8250_release_std_resource(up);
 
-       /* Fixme: probably not the best place for this */
-       if ((port->type == PORT_XR17V35X) ||
-          (port->type == PORT_XR17D15X))
-               port->handle_irq = exar_handle_irq;
-
        register_dev_spec_attr_grp(up);
        up->fcr = uart_config[up->port.type].fcr;
 }