linux/drivers/gpio/gpio-ts5500.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Digital I/O driver for Technologic Systems TS-5500
   4 *
   5 * Copyright (c) 2012 Savoir-faire Linux Inc.
   6 *      Vivien Didelot <vivien.didelot@savoirfairelinux.com>
   7 *
   8 * Technologic Systems platforms have pin blocks, exposing several Digital
   9 * Input/Output lines (DIO). This driver aims to support single pin blocks.
  10 * In that sense, the support is not limited to the TS-5500 blocks.
  11 * Actually, the following platforms have DIO support:
  12 *
  13 * TS-5500:
  14 *   Documentation: http://wiki.embeddedarm.com/wiki/TS-5500
  15 *   Blocks: DIO1, DIO2 and LCD port.
  16 *
  17 * TS-5600:
  18 *   Documentation: http://wiki.embeddedarm.com/wiki/TS-5600
  19 *   Blocks: LCD port (identical to TS-5500 LCD).
  20 */
  21
  22#include <linux/bitops.h>
  23#include <linux/gpio/driver.h>
  24#include <linux/io.h>
  25#include <linux/module.h>
  26#include <linux/platform_device.h>
  27#include <linux/slab.h>
  28
  29/* List of supported Technologic Systems platforms DIO blocks */
  30enum ts5500_blocks { TS5500_DIO1, TS5500_DIO2, TS5500_LCD, TS5600_LCD };
  31
  32struct ts5500_priv {
  33        const struct ts5500_dio *pinout;
  34        struct gpio_chip gpio_chip;
  35        spinlock_t lock;
  36        bool strap;
  37        u8 hwirq;
  38};
  39
  40/*
  41 * Hex 7D is used to control several blocks (e.g. DIO2 and LCD port).
  42 * This flag ensures that the region has been requested by this driver.
  43 */
  44static bool hex7d_reserved;
  45
  46/*
  47 * This structure is used to describe capabilities of DIO lines,
  48 * such as available directions and connected interrupt (if any).
  49 */
  50struct ts5500_dio {
  51        const u8 value_addr;
  52        const u8 value_mask;
  53        const u8 control_addr;
  54        const u8 control_mask;
  55        const bool no_input;
  56        const bool no_output;
  57        const u8 irq;
  58};
  59
  60#define TS5500_DIO_IN_OUT(vaddr, vbit, caddr, cbit)     \
  61        {                                               \
  62                .value_addr = vaddr,                    \
  63                .value_mask = BIT(vbit),                \
  64                .control_addr = caddr,                  \
  65                .control_mask = BIT(cbit),              \
  66        }
  67
  68#define TS5500_DIO_IN(addr, bit)                \
  69        {                                       \
  70                .value_addr = addr,             \
  71                .value_mask = BIT(bit),         \
  72                .no_output = true,              \
  73        }
  74
  75#define TS5500_DIO_IN_IRQ(addr, bit, _irq)      \
  76        {                                       \
  77                .value_addr = addr,             \
  78                .value_mask = BIT(bit),         \
  79                .no_output = true,              \
  80                .irq = _irq,                    \
  81        }
  82
  83#define TS5500_DIO_OUT(addr, bit)               \
  84        {                                       \
  85                .value_addr = addr,             \
  86                .value_mask = BIT(bit),         \
  87                .no_input = true,               \
  88        }
  89
  90/*
  91 * Input/Output DIO lines are programmed in groups of 4. Their values are
  92 * available through 4 consecutive bits in a value port, whereas the direction
  93 * of these 4 lines is driven by only 1 bit in a control port.
  94 */
  95#define TS5500_DIO_GROUP(vaddr, vbitfrom, caddr, cbit)          \
  96        TS5500_DIO_IN_OUT(vaddr, vbitfrom + 0, caddr, cbit),    \
  97        TS5500_DIO_IN_OUT(vaddr, vbitfrom + 1, caddr, cbit),    \
  98        TS5500_DIO_IN_OUT(vaddr, vbitfrom + 2, caddr, cbit),    \
  99        TS5500_DIO_IN_OUT(vaddr, vbitfrom + 3, caddr, cbit)
 100
 101/*
 102 * TS-5500 DIO1 block
 103 *
 104 *  value    control  dir    hw
 105 *  addr bit addr bit in out irq name     pin offset
 106 *
 107 *  0x7b  0  0x7a  0  x   x      DIO1_0   1   0
 108 *  0x7b  1  0x7a  0  x   x      DIO1_1   3   1
 109 *  0x7b  2  0x7a  0  x   x      DIO1_2   5   2
 110 *  0x7b  3  0x7a  0  x   x      DIO1_3   7   3
 111 *  0x7b  4  0x7a  1  x   x      DIO1_4   9   4
 112 *  0x7b  5  0x7a  1  x   x      DIO1_5   11  5
 113 *  0x7b  6  0x7a  1  x   x      DIO1_6   13  6
 114 *  0x7b  7  0x7a  1  x   x      DIO1_7   15  7
 115 *  0x7c  0  0x7a  5  x   x      DIO1_8   4   8
 116 *  0x7c  1  0x7a  5  x   x      DIO1_9   6   9
 117 *  0x7c  2  0x7a  5  x   x      DIO1_10  8   10
 118 *  0x7c  3  0x7a  5  x   x      DIO1_11  10  11
 119 *  0x7c  4           x          DIO1_12  12  12
 120 *  0x7c  5           x      7   DIO1_13  14  13
 121 */
 122static const struct ts5500_dio ts5500_dio1[] = {
 123        TS5500_DIO_GROUP(0x7b, 0, 0x7a, 0),
 124        TS5500_DIO_GROUP(0x7b, 4, 0x7a, 1),
 125        TS5500_DIO_GROUP(0x7c, 0, 0x7a, 5),
 126        TS5500_DIO_IN(0x7c, 4),
 127        TS5500_DIO_IN_IRQ(0x7c, 5, 7),
 128};
 129
 130/*
 131 * TS-5500 DIO2 block
 132 *
 133 *  value    control  dir    hw
 134 *  addr bit addr bit in out irq name     pin offset
 135 *
 136 *  0x7e  0  0x7d  0  x   x      DIO2_0   1   0
 137 *  0x7e  1  0x7d  0  x   x      DIO2_1   3   1
 138 *  0x7e  2  0x7d  0  x   x      DIO2_2   5   2
 139 *  0x7e  3  0x7d  0  x   x      DIO2_3   7   3
 140 *  0x7e  4  0x7d  1  x   x      DIO2_4   9   4
 141 *  0x7e  5  0x7d  1  x   x      DIO2_5   11  5
 142 *  0x7e  6  0x7d  1  x   x      DIO2_6   13  6
 143 *  0x7e  7  0x7d  1  x   x      DIO2_7   15  7
 144 *  0x7f  0  0x7d  5  x   x      DIO2_8   4   8
 145 *  0x7f  1  0x7d  5  x   x      DIO2_9   6   9
 146 *  0x7f  2  0x7d  5  x   x      DIO2_10  8   10
 147 *  0x7f  3  0x7d  5  x   x      DIO2_11  10  11
 148 *  0x7f  4           x      6   DIO2_13  14  12
 149 */
 150static const struct ts5500_dio ts5500_dio2[] = {
 151        TS5500_DIO_GROUP(0x7e, 0, 0x7d, 0),
 152        TS5500_DIO_GROUP(0x7e, 4, 0x7d, 1),
 153        TS5500_DIO_GROUP(0x7f, 0, 0x7d, 5),
 154        TS5500_DIO_IN_IRQ(0x7f, 4, 6),
 155};
 156
 157/*
 158 * TS-5500 LCD port used as DIO block
 159 * TS-5600 LCD port is identical
 160 *
 161 *  value    control  dir    hw
 162 *  addr bit addr bit in out irq name    pin offset
 163 *
 164 *  0x72  0  0x7d  2  x   x      LCD_0   8   0
 165 *  0x72  1  0x7d  2  x   x      LCD_1   7   1
 166 *  0x72  2  0x7d  2  x   x      LCD_2   10  2
 167 *  0x72  3  0x7d  2  x   x      LCD_3   9   3
 168 *  0x72  4  0x7d  3  x   x      LCD_4   12  4
 169 *  0x72  5  0x7d  3  x   x      LCD_5   11  5
 170 *  0x72  6  0x7d  3  x   x      LCD_6   14  6
 171 *  0x72  7  0x7d  3  x   x      LCD_7   13  7
 172 *  0x73  0               x      LCD_EN  5   8
 173 *  0x73  6           x          LCD_WR  6   9
 174 *  0x73  7           x      1   LCD_RS  3   10
 175 */
 176static const struct ts5500_dio ts5500_lcd[] = {
 177        TS5500_DIO_GROUP(0x72, 0, 0x7d, 2),
 178        TS5500_DIO_GROUP(0x72, 4, 0x7d, 3),
 179        TS5500_DIO_OUT(0x73, 0),
 180        TS5500_DIO_IN(0x73, 6),
 181        TS5500_DIO_IN_IRQ(0x73, 7, 1),
 182};
 183
 184static inline void ts5500_set_mask(u8 mask, u8 addr)
 185{
 186        u8 val = inb(addr);
 187        val |= mask;
 188        outb(val, addr);
 189}
 190
 191static inline void ts5500_clear_mask(u8 mask, u8 addr)
 192{
 193        u8 val = inb(addr);
 194        val &= ~mask;
 195        outb(val, addr);
 196}
 197
 198static int ts5500_gpio_input(struct gpio_chip *chip, unsigned offset)
 199{
 200        struct ts5500_priv *priv = gpiochip_get_data(chip);
 201        const struct ts5500_dio line = priv->pinout[offset];
 202        unsigned long flags;
 203
 204        if (line.no_input)
 205                return -ENXIO;
 206
 207        if (line.no_output)
 208                return 0;
 209
 210        spin_lock_irqsave(&priv->lock, flags);
 211        ts5500_clear_mask(line.control_mask, line.control_addr);
 212        spin_unlock_irqrestore(&priv->lock, flags);
 213
 214        return 0;
 215}
 216
 217static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset)
 218{
 219        struct ts5500_priv *priv = gpiochip_get_data(chip);
 220        const struct ts5500_dio line = priv->pinout[offset];
 221
 222        return !!(inb(line.value_addr) & line.value_mask);
 223}
 224
 225static int ts5500_gpio_output(struct gpio_chip *chip, unsigned offset, int val)
 226{
 227        struct ts5500_priv *priv = gpiochip_get_data(chip);
 228        const struct ts5500_dio line = priv->pinout[offset];
 229        unsigned long flags;
 230
 231        if (line.no_output)
 232                return -ENXIO;
 233
 234        spin_lock_irqsave(&priv->lock, flags);
 235        if (!line.no_input)
 236                ts5500_set_mask(line.control_mask, line.control_addr);
 237
 238        if (val)
 239                ts5500_set_mask(line.value_mask, line.value_addr);
 240        else
 241                ts5500_clear_mask(line.value_mask, line.value_addr);
 242        spin_unlock_irqrestore(&priv->lock, flags);
 243
 244        return 0;
 245}
 246
 247static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
 248{
 249        struct ts5500_priv *priv = gpiochip_get_data(chip);
 250        const struct ts5500_dio line = priv->pinout[offset];
 251        unsigned long flags;
 252
 253        spin_lock_irqsave(&priv->lock, flags);
 254        if (val)
 255                ts5500_set_mask(line.value_mask, line.value_addr);
 256        else
 257                ts5500_clear_mask(line.value_mask, line.value_addr);
 258        spin_unlock_irqrestore(&priv->lock, flags);
 259}
 260
 261static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
 262{
 263        struct ts5500_priv *priv = gpiochip_get_data(chip);
 264        const struct ts5500_dio *block = priv->pinout;
 265        const struct ts5500_dio line = block[offset];
 266
 267        /* Only one pin is connected to an interrupt */
 268        if (line.irq)
 269                return line.irq;
 270
 271        /* As this pin is input-only, we may strap it to another in/out pin */
 272        if (priv->strap)
 273                return priv->hwirq;
 274
 275        return -ENXIO;
 276}
 277
 278static int ts5500_enable_irq(struct ts5500_priv *priv)
 279{
 280        int ret = 0;
 281        unsigned long flags;
 282
 283        spin_lock_irqsave(&priv->lock, flags);
 284        if (priv->hwirq == 7)
 285                ts5500_set_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
 286        else if (priv->hwirq == 6)
 287                ts5500_set_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
 288        else if (priv->hwirq == 1)
 289                ts5500_set_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
 290        else
 291                ret = -EINVAL;
 292        spin_unlock_irqrestore(&priv->lock, flags);
 293
 294        return ret;
 295}
 296
 297static void ts5500_disable_irq(struct ts5500_priv *priv)
 298{
 299        unsigned long flags;
 300
 301        spin_lock_irqsave(&priv->lock, flags);
 302        if (priv->hwirq == 7)
 303                ts5500_clear_mask(BIT(7), 0x7a); /* DIO1_13 on IRQ7 */
 304        else if (priv->hwirq == 6)
 305                ts5500_clear_mask(BIT(7), 0x7d); /* DIO2_13 on IRQ6 */
 306        else if (priv->hwirq == 1)
 307                ts5500_clear_mask(BIT(6), 0x7d); /* LCD_RS on IRQ1 */
 308        else
 309                dev_err(priv->gpio_chip.parent, "invalid hwirq %d\n",
 310                        priv->hwirq);
 311        spin_unlock_irqrestore(&priv->lock, flags);
 312}
 313
 314static int ts5500_dio_probe(struct platform_device *pdev)
 315{
 316        enum ts5500_blocks block = platform_get_device_id(pdev)->driver_data;
 317        struct device *dev = &pdev->dev;
 318        const char *name = dev_name(dev);
 319        struct ts5500_priv *priv;
 320        struct resource *res;
 321        unsigned long flags;
 322        int ret;
 323
 324        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 325        if (!res) {
 326                dev_err(dev, "missing IRQ resource\n");
 327                return -EINVAL;
 328        }
 329
 330        priv = devm_kzalloc(dev, sizeof(struct ts5500_priv), GFP_KERNEL);
 331        if (!priv)
 332                return -ENOMEM;
 333
 334        platform_set_drvdata(pdev, priv);
 335        priv->hwirq = res->start;
 336        spin_lock_init(&priv->lock);
 337
 338        priv->gpio_chip.owner = THIS_MODULE;
 339        priv->gpio_chip.label = name;
 340        priv->gpio_chip.parent = dev;
 341        priv->gpio_chip.direction_input = ts5500_gpio_input;
 342        priv->gpio_chip.direction_output = ts5500_gpio_output;
 343        priv->gpio_chip.get = ts5500_gpio_get;
 344        priv->gpio_chip.set = ts5500_gpio_set;
 345        priv->gpio_chip.to_irq = ts5500_gpio_to_irq;
 346        priv->gpio_chip.base = -1;
 347
 348        switch (block) {
 349        case TS5500_DIO1:
 350                priv->pinout = ts5500_dio1;
 351                priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio1);
 352
 353                if (!devm_request_region(dev, 0x7a, 3, name)) {
 354                        dev_err(dev, "failed to request %s ports\n", name);
 355                        return -EBUSY;
 356                }
 357                break;
 358        case TS5500_DIO2:
 359                priv->pinout = ts5500_dio2;
 360                priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_dio2);
 361
 362                if (!devm_request_region(dev, 0x7e, 2, name)) {
 363                        dev_err(dev, "failed to request %s ports\n", name);
 364                        return -EBUSY;
 365                }
 366
 367                if (hex7d_reserved)
 368                        break;
 369
 370                if (!devm_request_region(dev, 0x7d, 1, name)) {
 371                        dev_err(dev, "failed to request %s 7D\n", name);
 372                        return -EBUSY;
 373                }
 374
 375                hex7d_reserved = true;
 376                break;
 377        case TS5500_LCD:
 378        case TS5600_LCD:
 379                priv->pinout = ts5500_lcd;
 380                priv->gpio_chip.ngpio = ARRAY_SIZE(ts5500_lcd);
 381
 382                if (!devm_request_region(dev, 0x72, 2, name)) {
 383                        dev_err(dev, "failed to request %s ports\n", name);
 384                        return -EBUSY;
 385                }
 386
 387                if (!hex7d_reserved) {
 388                        if (!devm_request_region(dev, 0x7d, 1, name)) {
 389                                dev_err(dev, "failed to request %s 7D\n", name);
 390                                return -EBUSY;
 391                        }
 392
 393                        hex7d_reserved = true;
 394                }
 395
 396                /* Ensure usage of LCD port as DIO */
 397                spin_lock_irqsave(&priv->lock, flags);
 398                ts5500_clear_mask(BIT(4), 0x7d);
 399                spin_unlock_irqrestore(&priv->lock, flags);
 400                break;
 401        }
 402
 403        ret = devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);
 404        if (ret) {
 405                dev_err(dev, "failed to register the gpio chip\n");
 406                return ret;
 407        }
 408
 409        ret = ts5500_enable_irq(priv);
 410        if (ret) {
 411                dev_err(dev, "invalid interrupt %d\n", priv->hwirq);
 412                return ret;
 413        }
 414
 415        return 0;
 416}
 417
 418static int ts5500_dio_remove(struct platform_device *pdev)
 419{
 420        struct ts5500_priv *priv = platform_get_drvdata(pdev);
 421
 422        ts5500_disable_irq(priv);
 423
 424        return 0;
 425}
 426
 427static const struct platform_device_id ts5500_dio_ids[] = {
 428        { "ts5500-dio1", TS5500_DIO1 },
 429        { "ts5500-dio2", TS5500_DIO2 },
 430        { "ts5500-dio-lcd", TS5500_LCD },
 431        { "ts5600-dio-lcd", TS5600_LCD },
 432        { }
 433};
 434MODULE_DEVICE_TABLE(platform, ts5500_dio_ids);
 435
 436static struct platform_driver ts5500_dio_driver = {
 437        .driver = {
 438                .name = "ts5500-dio",
 439        },
 440        .probe = ts5500_dio_probe,
 441        .remove = ts5500_dio_remove,
 442        .id_table = ts5500_dio_ids,
 443};
 444
 445module_platform_driver(ts5500_dio_driver);
 446
 447MODULE_LICENSE("GPL");
 448MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
 449MODULE_DESCRIPTION("Technologic Systems TS-5500 Digital I/O driver");
 450