linux/drivers/gpio/gpio-syscon.c
<<
>>
Prefs
   1/*
   2 *  SYSCON GPIO driver
   3 *
   4 *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 */
  11
  12#include <linux/err.h>
  13#include <linux/gpio.h>
  14#include <linux/module.h>
  15#include <linux/of.h>
  16#include <linux/of_device.h>
  17#include <linux/platform_device.h>
  18#include <linux/regmap.h>
  19#include <linux/mfd/syscon.h>
  20
  21#define GPIO_SYSCON_FEAT_IN     BIT(0)
  22#define GPIO_SYSCON_FEAT_OUT    BIT(1)
  23#define GPIO_SYSCON_FEAT_DIR    BIT(2)
  24
  25/* SYSCON driver is designed to use 32-bit wide registers */
  26#define SYSCON_REG_SIZE         (4)
  27#define SYSCON_REG_BITS         (SYSCON_REG_SIZE * 8)
  28
  29/**
  30 * struct syscon_gpio_data - Configuration for the device.
  31 * compatible:          SYSCON driver compatible string.
  32 * flags:               Set of GPIO_SYSCON_FEAT_ flags:
  33 *                      GPIO_SYSCON_FEAT_IN:    GPIOs supports input,
  34 *                      GPIO_SYSCON_FEAT_OUT:   GPIOs supports output,
  35 *                      GPIO_SYSCON_FEAT_DIR:   GPIOs supports switch direction.
  36 * bit_count:           Number of bits used as GPIOs.
  37 * dat_bit_offset:      Offset (in bits) to the first GPIO bit.
  38 * dir_bit_offset:      Optional offset (in bits) to the first bit to switch
  39 *                      GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag).
  40 * set:         HW specific callback to assigns output value
  41 *                      for signal "offset"
  42 */
  43
  44struct syscon_gpio_data {
  45        const char      *compatible;
  46        unsigned int    flags;
  47        unsigned int    bit_count;
  48        unsigned int    dat_bit_offset;
  49        unsigned int    dir_bit_offset;
  50        void            (*set)(struct gpio_chip *chip,
  51                               unsigned offset, int value);
  52};
  53
  54struct syscon_gpio_priv {
  55        struct gpio_chip                chip;
  56        struct regmap                   *syscon;
  57        const struct syscon_gpio_data   *data;
  58        u32                             dreg_offset;
  59        u32                             dir_reg_offset;
  60};
  61
  62static inline struct syscon_gpio_priv *to_syscon_gpio(struct gpio_chip *chip)
  63{
  64        return container_of(chip, struct syscon_gpio_priv, chip);
  65}
  66
  67static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset)
  68{
  69        struct syscon_gpio_priv *priv = to_syscon_gpio(chip);
  70        unsigned int val, offs;
  71        int ret;
  72
  73        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
  74
  75        ret = regmap_read(priv->syscon,
  76                          (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val);
  77        if (ret)
  78                return ret;
  79
  80        return !!(val & BIT(offs % SYSCON_REG_BITS));
  81}
  82
  83static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
  84{
  85        struct syscon_gpio_priv *priv = to_syscon_gpio(chip);
  86        unsigned int offs;
  87
  88        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
  89
  90        regmap_update_bits(priv->syscon,
  91                           (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
  92                           BIT(offs % SYSCON_REG_BITS),
  93                           val ? BIT(offs % SYSCON_REG_BITS) : 0);
  94}
  95
  96static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset)
  97{
  98        struct syscon_gpio_priv *priv = to_syscon_gpio(chip);
  99
 100        if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
 101                unsigned int offs;
 102
 103                offs = priv->dir_reg_offset +
 104                       priv->data->dir_bit_offset + offset;
 105
 106                regmap_update_bits(priv->syscon,
 107                                   (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 108                                   BIT(offs % SYSCON_REG_BITS), 0);
 109        }
 110
 111        return 0;
 112}
 113
 114static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val)
 115{
 116        struct syscon_gpio_priv *priv = to_syscon_gpio(chip);
 117
 118        if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
 119                unsigned int offs;
 120
 121                offs = priv->dir_reg_offset +
 122                       priv->data->dir_bit_offset + offset;
 123
 124                regmap_update_bits(priv->syscon,
 125                                   (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 126                                   BIT(offs % SYSCON_REG_BITS),
 127                                   BIT(offs % SYSCON_REG_BITS));
 128        }
 129
 130        priv->data->set(chip, offset, val);
 131
 132        return 0;
 133}
 134
 135static const struct syscon_gpio_data clps711x_mctrl_gpio = {
 136        /* ARM CLPS711X SYSFLG1 Bits 8-10 */
 137        .compatible     = "cirrus,clps711x-syscon1",
 138        .flags          = GPIO_SYSCON_FEAT_IN,
 139        .bit_count      = 3,
 140        .dat_bit_offset = 0x40 * 8 + 8,
 141};
 142
 143#define KEYSTONE_LOCK_BIT BIT(0)
 144
 145static void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
 146{
 147        struct syscon_gpio_priv *priv = to_syscon_gpio(chip);
 148        unsigned int offs;
 149        int ret;
 150
 151        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
 152
 153        if (!val)
 154                return;
 155
 156        ret = regmap_update_bits(
 157                        priv->syscon,
 158                        (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 159                        BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT,
 160                        BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT);
 161        if (ret < 0)
 162                dev_err(chip->dev, "gpio write failed ret(%d)\n", ret);
 163}
 164
 165static const struct syscon_gpio_data keystone_dsp_gpio = {
 166        /* ARM Keystone 2 */
 167        .compatible     = NULL,
 168        .flags          = GPIO_SYSCON_FEAT_OUT,
 169        .bit_count      = 28,
 170        .dat_bit_offset = 4,
 171        .set            = keystone_gpio_set,
 172};
 173
 174static const struct of_device_id syscon_gpio_ids[] = {
 175        {
 176                .compatible     = "cirrus,clps711x-mctrl-gpio",
 177                .data           = &clps711x_mctrl_gpio,
 178        },
 179        {
 180                .compatible     = "ti,keystone-dsp-gpio",
 181                .data           = &keystone_dsp_gpio,
 182        },
 183        { }
 184};
 185MODULE_DEVICE_TABLE(of, syscon_gpio_ids);
 186
 187static int syscon_gpio_probe(struct platform_device *pdev)
 188{
 189        struct device *dev = &pdev->dev;
 190        const struct of_device_id *of_id = of_match_device(syscon_gpio_ids, dev);
 191        struct syscon_gpio_priv *priv;
 192        struct device_node *np = dev->of_node;
 193        int ret;
 194
 195        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 196        if (!priv)
 197                return -ENOMEM;
 198
 199        priv->data = of_id->data;
 200
 201        if (priv->data->compatible) {
 202                priv->syscon = syscon_regmap_lookup_by_compatible(
 203                                        priv->data->compatible);
 204                if (IS_ERR(priv->syscon))
 205                        return PTR_ERR(priv->syscon);
 206        } else {
 207                priv->syscon =
 208                        syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev");
 209                if (IS_ERR(priv->syscon))
 210                        return PTR_ERR(priv->syscon);
 211
 212                ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1,
 213                                                 &priv->dreg_offset);
 214                if (ret)
 215                        dev_err(dev, "can't read the data register offset!\n");
 216
 217                priv->dreg_offset <<= 3;
 218
 219                ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2,
 220                                                 &priv->dir_reg_offset);
 221                if (ret)
 222                        dev_dbg(dev, "can't read the dir register offset!\n");
 223
 224                priv->dir_reg_offset <<= 3;
 225        }
 226
 227        priv->chip.dev = dev;
 228        priv->chip.owner = THIS_MODULE;
 229        priv->chip.label = dev_name(dev);
 230        priv->chip.base = -1;
 231        priv->chip.ngpio = priv->data->bit_count;
 232        priv->chip.get = syscon_gpio_get;
 233        if (priv->data->flags & GPIO_SYSCON_FEAT_IN)
 234                priv->chip.direction_input = syscon_gpio_dir_in;
 235        if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) {
 236                priv->chip.set = priv->data->set ? : syscon_gpio_set;
 237                priv->chip.direction_output = syscon_gpio_dir_out;
 238        }
 239
 240        platform_set_drvdata(pdev, priv);
 241
 242        return gpiochip_add(&priv->chip);
 243}
 244
 245static int syscon_gpio_remove(struct platform_device *pdev)
 246{
 247        struct syscon_gpio_priv *priv = platform_get_drvdata(pdev);
 248
 249        gpiochip_remove(&priv->chip);
 250        return 0;
 251}
 252
 253static struct platform_driver syscon_gpio_driver = {
 254        .driver = {
 255                .name           = "gpio-syscon",
 256                .of_match_table = syscon_gpio_ids,
 257        },
 258        .probe  = syscon_gpio_probe,
 259        .remove = syscon_gpio_remove,
 260};
 261module_platform_driver(syscon_gpio_driver);
 262
 263MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
 264MODULE_DESCRIPTION("SYSCON GPIO driver");
 265MODULE_LICENSE("GPL");
 266