serial: mctrl_gpio: implement interrupt handling
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Wed, 30 Sep 2015 08:19:41 +0000 (10:19 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 4 Oct 2015 17:47:51 +0000 (18:47 +0100)
This allows to reduce the per-driver boiler plate considerably.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Reviewed-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/serial/driver
drivers/tty/serial/serial_mctrl_gpio.c
drivers/tty/serial/serial_mctrl_gpio.h

index c415b0ef449378db4698ca9552c54bdfb387a9d0..379468e12680dbfb1d4a3dd222fb617681ae1df7 100644 (file)
@@ -439,11 +439,13 @@ Modem control lines via GPIO
 
 Some helpers are provided in order to set/get modem control lines via GPIO.
 
-mctrl_gpio_init(dev, idx):
+mctrl_gpio_init(port, idx):
        This will get the {cts,rts,...}-gpios from device tree if they are
        present and request them, set direction etc, and return an
        allocated structure. devm_* functions are used, so there's no need
        to call mctrl_gpio_free().
+       As this sets up the irq handling make sure to not handle changes to the
+       gpio input lines in your driver, too.
 
 mctrl_gpio_free(dev, gpios):
        This will free the requested gpios in mctrl_gpio_init().
@@ -458,3 +460,9 @@ mctrl_gpio_set(gpios, mctrl):
 
 mctrl_gpio_get(gpios, mctrl):
        This will update mctrl with the gpios values.
+
+mctrl_gpio_enable_ms(gpios):
+       Enables irqs and handling of changes to the ms lines.
+
+mctrl_gpio_disable_ms(gpios):
+       Disables irqs and handling of changes to the ms lines.
index 1cdedd3bfb438686e1025b300b73324e66fc15e0..3eb57eb532f18a6af5fc169b044699a453e59975 100644 (file)
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- *
  */
 
 #include <linux/err.h>
 #include <linux/device.h>
+#include <linux/irq.h>
 #include <linux/gpio/consumer.h>
 #include <linux/termios.h>
+#include <linux/serial_core.h>
 
 #include "serial_mctrl_gpio.h"
 
 struct mctrl_gpios {
+       struct uart_port *port;
        struct gpio_desc *gpio[UART_GPIO_MAX];
+       int irq[UART_GPIO_MAX];
+       unsigned int mctrl_prev;
+       bool mctrl_on;
 };
 
 static const struct {
@@ -112,13 +117,133 @@ struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto);
 
+#define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
+static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context)
+{
+       struct mctrl_gpios *gpios = context;
+       struct uart_port *port = gpios->port;
+       u32 mctrl = gpios->mctrl_prev;
+       u32 mctrl_diff;
+
+       mctrl_gpio_get(gpios, &mctrl);
+
+       mctrl_diff = mctrl ^ gpios->mctrl_prev;
+       gpios->mctrl_prev = mctrl;
+
+       if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) {
+               if ((mctrl_diff & mctrl) & TIOCM_RI)
+                       port->icount.rng++;
+
+               if ((mctrl_diff & mctrl) & TIOCM_DSR)
+                       port->icount.dsr++;
+
+               if (mctrl_diff & TIOCM_CD)
+                       uart_handle_dcd_change(port, mctrl & TIOCM_CD);
+
+               if (mctrl_diff & TIOCM_CTS)
+                       uart_handle_cts_change(port, mctrl & TIOCM_CTS);
+
+               wake_up_interruptible(&port->state->port.delta_msr_wait);
+       }
+
+       return IRQ_HANDLED;
+}
+
+struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
+{
+       struct mctrl_gpios *gpios;
+       enum mctrl_gpio_idx i;
+
+       gpios = mctrl_gpio_init_noauto(port->dev, idx);
+       if (IS_ERR(gpios))
+               return gpios;
+
+       gpios->port = port;
+
+       for (i = 0; i < UART_GPIO_MAX; ++i) {
+               int ret;
+
+               if (!gpios->gpio[i] || mctrl_gpios_desc[i].dir_out)
+                       continue;
+
+               ret = gpiod_to_irq(gpios->gpio[i]);
+               if (ret <= 0) {
+                       dev_err(port->dev,
+                               "failed to find corresponding irq for %s (idx=%d, err=%d)\n",
+                               mctrl_gpios_desc[i].name, idx, ret);
+                       return ERR_PTR(ret);
+               }
+               gpios->irq[i] = ret;
+
+               /* irqs should only be enabled in .enable_ms */
+               irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN);
+
+               ret = devm_request_irq(port->dev, gpios->irq[i],
+                                      mctrl_gpio_irq_handle,
+                                      IRQ_TYPE_EDGE_BOTH, dev_name(port->dev),
+                                      gpios);
+               if (ret) {
+                       /* alternatively implement polling */
+                       dev_err(port->dev,
+                               "failed to request irq for %s (idx=%d, err=%d)\n",
+                               mctrl_gpios_desc[i].name, idx, ret);
+                       return ERR_PTR(ret);
+               }
+       }
+
+       return gpios;
+}
+
 void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)
 {
        enum mctrl_gpio_idx i;
 
-       for (i = 0; i < UART_GPIO_MAX; i++)
+       for (i = 0; i < UART_GPIO_MAX; i++) {
+               if (gpios->irq[i])
+                       devm_free_irq(gpios->port->dev, gpios->irq[i], gpios);
+
                if (gpios->gpio[i])
                        devm_gpiod_put(dev, gpios->gpio[i]);
+       }
        devm_kfree(dev, gpios);
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_free);
+
+void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
+{
+       enum mctrl_gpio_idx i;
+
+       /* .enable_ms may be called multiple times */
+       if (gpios->mctrl_on)
+               return;
+
+       gpios->mctrl_on = true;
+
+       /* get initial status of modem lines GPIOs */
+       mctrl_gpio_get(gpios, &gpios->mctrl_prev);
+
+       for (i = 0; i < UART_GPIO_MAX; ++i) {
+               if (!gpios->irq[i])
+                       continue;
+
+               enable_irq(gpios->irq[i]);
+       }
+}
+EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms);
+
+void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)
+{
+       enum mctrl_gpio_idx i;
+
+       if (!gpios->mctrl_on)
+               return;
+
+       gpios->mctrl_on = false;
+
+       for (i = 0; i < UART_GPIO_MAX; ++i) {
+               if (!gpios->irq[i])
+                       continue;
+
+               disable_irq(gpios->irq[i]);
+       }
+}
index 6d046fcf744e4fc851aa2d0f3acad5350128b407..3c4ac9ae41f92914d4cce0e398b06e94e9d02a93 100644 (file)
@@ -22,6 +22,8 @@
 #include <linux/device.h>
 #include <linux/gpio/consumer.h>
 
+struct uart_port;
+
 enum mctrl_gpio_idx {
        UART_GPIO_CTS,
        UART_GPIO_DSR,
@@ -59,6 +61,15 @@ unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl);
 struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
                                      enum mctrl_gpio_idx gidx);
 
+/*
+ * Request and set direction of modem control lines GPIOs and sets up irq
+ * handling.
+ * devm_* functions are used, so there's no need to call mctrl_gpio_free().
+ * Returns a pointer to the allocated mctrl structure if ok, -ENOMEM on
+ * allocation error.
+ */
+struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx);
+
 /*
  * Request and set direction of modem control lines GPIOs.
  * devm_* functions are used, so there's no need to call mctrl_gpio_free().
@@ -75,6 +86,16 @@ struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev,
  */
 void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios);
 
+/*
+ * Enable gpio interrupts to report status line changes.
+ */
+void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios);
+
+/*
+ * Disable gpio interrupts to report status line changes.
+ */
+void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios);
+
 #else /* GPIOLIB */
 
 static inline
@@ -95,6 +116,12 @@ struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
        return ERR_PTR(-ENOSYS);
 }
 
+static inline
+struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
+{
+       return ERR_PTR(-ENOSYS);
+}
+
 static inline
 struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
 {
@@ -106,6 +133,14 @@ void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)
 {
 }
 
+void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
+{
+}
+
+void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)
+{
+}
+
 #endif /* GPIOLIB */
 
 #endif