Skip to content

Commit

Permalink
serial: mctrl_gpio: implement interrupt handling
Browse files Browse the repository at this point in the history
This allows to reduce the per-driver boiler plate considerably.

Signed-off-by: Uwe Kleine-König <[email protected]>
Reviewed-by: Nicolas Ferre <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
Uwe Kleine-König authored and gregkh committed Oct 4, 2015
1 parent 7d8c70d commit ce59e48
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 3 deletions.
10 changes: 9 additions & 1 deletion Documentation/serial/driver
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand All @@ -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.
129 changes: 127 additions & 2 deletions drivers/tty/serial/serial_mctrl_gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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]);
}
}
35 changes: 35 additions & 0 deletions drivers/tty/serial/serial_mctrl_gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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().
Expand All @@ -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
Expand All @@ -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)
{
Expand All @@ -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

0 comments on commit ce59e48

Please sign in to comment.