These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / tty / serial / serial_mctrl_gpio.c
index 0ec756c..3eb57eb 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 {
@@ -49,13 +54,12 @@ void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)
        unsigned int count = 0;
 
        for (i = 0; i < UART_GPIO_MAX; i++)
-               if (!IS_ERR_OR_NULL(gpios->gpio[i]) &&
-                   mctrl_gpios_desc[i].dir_out) {
+               if (gpios->gpio[i] && mctrl_gpios_desc[i].dir_out) {
                        desc_array[count] = gpios->gpio[i];
                        value_array[count] = !!(mctrl & mctrl_gpios_desc[i].mctrl);
                        count++;
                }
-       gpiod_set_array(count, desc_array, value_array);
+       gpiod_set_array_value(count, desc_array, value_array);
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_set);
 
@@ -83,7 +87,7 @@ unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_get);
 
-struct mctrl_gpios *mctrl_gpio_init(struct device *dev, unsigned int idx)
+struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
 {
        struct mctrl_gpios *gpios;
        enum mctrl_gpio_idx i;
@@ -111,15 +115,135 @@ struct mctrl_gpios *mctrl_gpio_init(struct device *dev, unsigned int idx)
 
        return gpios;
 }
-EXPORT_SYMBOL_GPL(mctrl_gpio_init);
+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++)
-               if (!IS_ERR_OR_NULL(gpios->gpio[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]);
+       }
+}