summaryrefslogtreecommitdiffstats
path: root/drivers/tty/serial/serial_mctrl_gpio.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-11-05 06:35:12 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2015-11-05 06:35:12 +0100
commitfd0d351de7bbd718bc2b34d5846854831aa2b88c (patch)
treeb4630c372215deb2e17235707189f06603bee3a3 /drivers/tty/serial/serial_mctrl_gpio.c
parentMerge tag 'usb-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/greg... (diff)
parenttty: Use unbound workqueue for all input workers (diff)
downloadlinux-fd0d351de7bbd718bc2b34d5846854831aa2b88c.tar.xz
linux-fd0d351de7bbd718bc2b34d5846854831aa2b88c.zip
Merge tag 'tty-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty
Pull tty/serial driver updates from Greg KH: "Here is the big tty and serial driver update for 4.4-rc1. Lots of serial driver updates and a few small tty core changes. Full details in the shortlog. All of these have been in linux-next for a while" * tag 'tty-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty: (148 commits) tty: Use unbound workqueue for all input workers tty: Abstract tty buffer work tty: Prevent tty teardown during tty_write_message() tty: core: Use correct spinlock flavor in tiocspgrp() tty: Combine SIGTTOU/SIGTTIN handling serial: amba-pl011: fix incorrect integer size in pl011_fifo_to_tty() ttyFDC: Fix build problems due to use of module_{init,exit} tty: remove unneeded return statement serial: 8250_mid: add support for DMA engine handling from UART MMIO dmaengine: hsu: remove platform data dmaengine: hsu: introduce stubs for the exported functions dmaengine: hsu: make the UART driver in control of selecting this driver serial: fix mctrl helper functions serial: 8250_pci: Intel MID UART support to its own driver serial: fsl_lpuart: add earlycon support tty: disable unbind for old 74xx based serial/mpsc console port serial: pl011: Spelling s/clocks-names/clock-names/ n_tty: Remove reader wakeups for TTY_BREAK/TTY_PARITY chars tty: synclink, fix indentation serial: at91, fix rs485 properties ...
Diffstat (limited to 'drivers/tty/serial/serial_mctrl_gpio.c')
-rw-r--r--drivers/tty/serial/serial_mctrl_gpio.c133
1 files changed, 129 insertions, 4 deletions
diff --git a/drivers/tty/serial/serial_mctrl_gpio.c b/drivers/tty/serial/serial_mctrl_gpio.c
index 402f7fb54133..3eb57eb532f1 100644
--- a/drivers/tty/serial/serial_mctrl_gpio.c
+++ b/drivers/tty/serial/serial_mctrl_gpio.c
@@ -12,18 +12,23 @@
* 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 {
@@ -82,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;
@@ -110,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++)
+ 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]);
+ }
+}