linux/drivers/tty/serial/serial_mctrl_gpio.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Helpers for controlling modem lines via GPIO
   4 *
   5 * Copyright (C) 2014 Paratronic S.A.
   6 */
   7
   8#include <linux/err.h>
   9#include <linux/device.h>
  10#include <linux/irq.h>
  11#include <linux/gpio/consumer.h>
  12#include <linux/termios.h>
  13#include <linux/serial_core.h>
  14#include <linux/module.h>
  15#include <linux/property.h>
  16
  17#include "serial_mctrl_gpio.h"
  18
  19struct mctrl_gpios {
  20        struct uart_port *port;
  21        struct gpio_desc *gpio[UART_GPIO_MAX];
  22        int irq[UART_GPIO_MAX];
  23        unsigned int mctrl_prev;
  24        bool mctrl_on;
  25};
  26
  27static const struct {
  28        const char *name;
  29        unsigned int mctrl;
  30        enum gpiod_flags flags;
  31} mctrl_gpios_desc[UART_GPIO_MAX] = {
  32        { "cts", TIOCM_CTS, GPIOD_IN, },
  33        { "dsr", TIOCM_DSR, GPIOD_IN, },
  34        { "dcd", TIOCM_CD,  GPIOD_IN, },
  35        { "rng", TIOCM_RNG, GPIOD_IN, },
  36        { "rts", TIOCM_RTS, GPIOD_OUT_LOW, },
  37        { "dtr", TIOCM_DTR, GPIOD_OUT_LOW, },
  38};
  39
  40static bool mctrl_gpio_flags_is_dir_out(unsigned int idx)
  41{
  42        return mctrl_gpios_desc[idx].flags & GPIOD_FLAGS_BIT_DIR_OUT;
  43}
  44
  45void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)
  46{
  47        enum mctrl_gpio_idx i;
  48        struct gpio_desc *desc_array[UART_GPIO_MAX];
  49        DECLARE_BITMAP(values, UART_GPIO_MAX);
  50        unsigned int count = 0;
  51
  52        if (gpios == NULL)
  53                return;
  54
  55        for (i = 0; i < UART_GPIO_MAX; i++)
  56                if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
  57                        desc_array[count] = gpios->gpio[i];
  58                        __assign_bit(count, values,
  59                                     mctrl & mctrl_gpios_desc[i].mctrl);
  60                        count++;
  61                }
  62        gpiod_set_array_value(count, desc_array, NULL, values);
  63}
  64EXPORT_SYMBOL_GPL(mctrl_gpio_set);
  65
  66struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
  67                                      enum mctrl_gpio_idx gidx)
  68{
  69        if (gpios == NULL)
  70                return NULL;
  71
  72        return gpios->gpio[gidx];
  73}
  74EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod);
  75
  76unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)
  77{
  78        enum mctrl_gpio_idx i;
  79
  80        if (gpios == NULL)
  81                return *mctrl;
  82
  83        for (i = 0; i < UART_GPIO_MAX; i++) {
  84                if (gpios->gpio[i] && !mctrl_gpio_flags_is_dir_out(i)) {
  85                        if (gpiod_get_value(gpios->gpio[i]))
  86                                *mctrl |= mctrl_gpios_desc[i].mctrl;
  87                        else
  88                                *mctrl &= ~mctrl_gpios_desc[i].mctrl;
  89                }
  90        }
  91
  92        return *mctrl;
  93}
  94EXPORT_SYMBOL_GPL(mctrl_gpio_get);
  95
  96unsigned int
  97mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl)
  98{
  99        enum mctrl_gpio_idx i;
 100
 101        if (gpios == NULL)
 102                return *mctrl;
 103
 104        for (i = 0; i < UART_GPIO_MAX; i++) {
 105                if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
 106                        if (gpiod_get_value(gpios->gpio[i]))
 107                                *mctrl |= mctrl_gpios_desc[i].mctrl;
 108                        else
 109                                *mctrl &= ~mctrl_gpios_desc[i].mctrl;
 110                }
 111        }
 112
 113        return *mctrl;
 114}
 115EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs);
 116
 117struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
 118{
 119        struct mctrl_gpios *gpios;
 120        enum mctrl_gpio_idx i;
 121
 122        gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL);
 123        if (!gpios)
 124                return ERR_PTR(-ENOMEM);
 125
 126        for (i = 0; i < UART_GPIO_MAX; i++) {
 127                char *gpio_str;
 128                bool present;
 129
 130                /* Check if GPIO property exists and continue if not */
 131                gpio_str = kasprintf(GFP_KERNEL, "%s-gpios",
 132                                     mctrl_gpios_desc[i].name);
 133                if (!gpio_str)
 134                        continue;
 135
 136                present = device_property_present(dev, gpio_str);
 137                kfree(gpio_str);
 138                if (!present)
 139                        continue;
 140
 141                gpios->gpio[i] =
 142                        devm_gpiod_get_index_optional(dev,
 143                                                      mctrl_gpios_desc[i].name,
 144                                                      idx,
 145                                                      mctrl_gpios_desc[i].flags);
 146
 147                if (IS_ERR(gpios->gpio[i]))
 148                        return ERR_CAST(gpios->gpio[i]);
 149        }
 150
 151        return gpios;
 152}
 153EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto);
 154
 155#define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
 156static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context)
 157{
 158        struct mctrl_gpios *gpios = context;
 159        struct uart_port *port = gpios->port;
 160        u32 mctrl = gpios->mctrl_prev;
 161        u32 mctrl_diff;
 162        unsigned long flags;
 163
 164        mctrl_gpio_get(gpios, &mctrl);
 165
 166        spin_lock_irqsave(&port->lock, flags);
 167
 168        mctrl_diff = mctrl ^ gpios->mctrl_prev;
 169        gpios->mctrl_prev = mctrl;
 170
 171        if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) {
 172                if ((mctrl_diff & mctrl) & TIOCM_RI)
 173                        port->icount.rng++;
 174
 175                if ((mctrl_diff & mctrl) & TIOCM_DSR)
 176                        port->icount.dsr++;
 177
 178                if (mctrl_diff & TIOCM_CD)
 179                        uart_handle_dcd_change(port, mctrl & TIOCM_CD);
 180
 181                if (mctrl_diff & TIOCM_CTS)
 182                        uart_handle_cts_change(port, mctrl & TIOCM_CTS);
 183
 184                wake_up_interruptible(&port->state->port.delta_msr_wait);
 185        }
 186
 187        spin_unlock_irqrestore(&port->lock, flags);
 188
 189        return IRQ_HANDLED;
 190}
 191
 192struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
 193{
 194        struct mctrl_gpios *gpios;
 195        enum mctrl_gpio_idx i;
 196
 197        gpios = mctrl_gpio_init_noauto(port->dev, idx);
 198        if (IS_ERR(gpios))
 199                return gpios;
 200
 201        gpios->port = port;
 202
 203        for (i = 0; i < UART_GPIO_MAX; ++i) {
 204                int ret;
 205
 206                if (!gpios->gpio[i] || mctrl_gpio_flags_is_dir_out(i))
 207                        continue;
 208
 209                ret = gpiod_to_irq(gpios->gpio[i]);
 210                if (ret < 0) {
 211                        dev_err(port->dev,
 212                                "failed to find corresponding irq for %s (idx=%d, err=%d)\n",
 213                                mctrl_gpios_desc[i].name, idx, ret);
 214                        return ERR_PTR(ret);
 215                }
 216                gpios->irq[i] = ret;
 217
 218                /* irqs should only be enabled in .enable_ms */
 219                irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN);
 220
 221                ret = devm_request_irq(port->dev, gpios->irq[i],
 222                                       mctrl_gpio_irq_handle,
 223                                       IRQ_TYPE_EDGE_BOTH, dev_name(port->dev),
 224                                       gpios);
 225                if (ret) {
 226                        /* alternatively implement polling */
 227                        dev_err(port->dev,
 228                                "failed to request irq for %s (idx=%d, err=%d)\n",
 229                                mctrl_gpios_desc[i].name, idx, ret);
 230                        return ERR_PTR(ret);
 231                }
 232        }
 233
 234        return gpios;
 235}
 236EXPORT_SYMBOL_GPL(mctrl_gpio_init);
 237
 238void mctrl_gpio_free(struct device *dev, struct mctrl_gpios *gpios)
 239{
 240        enum mctrl_gpio_idx i;
 241
 242        if (gpios == NULL)
 243                return;
 244
 245        for (i = 0; i < UART_GPIO_MAX; i++) {
 246                if (gpios->irq[i])
 247                        devm_free_irq(gpios->port->dev, gpios->irq[i], gpios);
 248
 249                if (gpios->gpio[i])
 250                        devm_gpiod_put(dev, gpios->gpio[i]);
 251        }
 252        devm_kfree(dev, gpios);
 253}
 254EXPORT_SYMBOL_GPL(mctrl_gpio_free);
 255
 256void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
 257{
 258        enum mctrl_gpio_idx i;
 259
 260        if (gpios == NULL)
 261                return;
 262
 263        /* .enable_ms may be called multiple times */
 264        if (gpios->mctrl_on)
 265                return;
 266
 267        gpios->mctrl_on = true;
 268
 269        /* get initial status of modem lines GPIOs */
 270        mctrl_gpio_get(gpios, &gpios->mctrl_prev);
 271
 272        for (i = 0; i < UART_GPIO_MAX; ++i) {
 273                if (!gpios->irq[i])
 274                        continue;
 275
 276                enable_irq(gpios->irq[i]);
 277        }
 278}
 279EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms);
 280
 281void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios)
 282{
 283        enum mctrl_gpio_idx i;
 284
 285        if (gpios == NULL)
 286                return;
 287
 288        if (!gpios->mctrl_on)
 289                return;
 290
 291        gpios->mctrl_on = false;
 292
 293        for (i = 0; i < UART_GPIO_MAX; ++i) {
 294                if (!gpios->irq[i])
 295                        continue;
 296
 297                disable_irq(gpios->irq[i]);
 298        }
 299}
 300EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms);
 301
 302MODULE_LICENSE("GPL");
 303