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