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 int syscon_gpio_get(struct gpio_chip *chip, unsigned offset)
  63{
  64        struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
  65        unsigned int val, offs;
  66        int ret;
  67
  68        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
  69
  70        ret = regmap_read(priv->syscon,
  71                          (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val);
  72        if (ret)
  73                return ret;
  74
  75        return !!(val & BIT(offs % SYSCON_REG_BITS));
  76}
  77
  78static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
  79{
  80        struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
  81        unsigned int offs;
  82
  83        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
  84
  85        regmap_update_bits(priv->syscon,
  86                           (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
  87                           BIT(offs % SYSCON_REG_BITS),
  88                           val ? BIT(offs % SYSCON_REG_BITS) : 0);
  89}
  90
  91static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset)
  92{
  93        struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
  94
  95        if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
  96                unsigned int offs;
  97
  98                offs = priv->dir_reg_offset +
  99                       priv->data->dir_bit_offset + offset;
 100
 101                regmap_update_bits(priv->syscon,
 102                                   (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 103                                   BIT(offs % SYSCON_REG_BITS), 0);
 104        }
 105
 106        return 0;
 107}
 108
 109static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val)
 110{
 111        struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
 112
 113        if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) {
 114                unsigned int offs;
 115
 116                offs = priv->dir_reg_offset +
 117                       priv->data->dir_bit_offset + offset;
 118
 119                regmap_update_bits(priv->syscon,
 120                                   (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 121                                   BIT(offs % SYSCON_REG_BITS),
 122                                   BIT(offs % SYSCON_REG_BITS));
 123        }
 124
 125        priv->data->set(chip, offset, val);
 126
 127        return 0;
 128}
 129
 130static const struct syscon_gpio_data clps711x_mctrl_gpio = {
 131        /* ARM CLPS711X SYSFLG1 Bits 8-10 */
 132        .compatible     = "cirrus,ep7209-syscon1",
 133        .flags          = GPIO_SYSCON_FEAT_IN,
 134        .bit_count      = 3,
 135        .dat_bit_offset = 0x40 * 8 + 8,
 136};
 137
 138#define KEYSTONE_LOCK_BIT BIT(0)
 139
 140static void keystone_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
 141{
 142        struct syscon_gpio_priv *priv = gpiochip_get_data(chip);
 143        unsigned int offs;
 144        int ret;
 145
 146        offs = priv->dreg_offset + priv->data->dat_bit_offset + offset;
 147
 148        if (!val)
 149                return;
 150
 151        ret = regmap_update_bits(
 152                        priv->syscon,
 153                        (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE,
 154                        BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT,
 155                        BIT(offs % SYSCON_REG_BITS) | KEYSTONE_LOCK_BIT);
 156        if (ret < 0)
 157                dev_err(chip->parent, "gpio write failed ret(%d)\n", ret);
 158}
 159
 160static const struct syscon_gpio_data keystone_dsp_gpio = {
 161        /* ARM Keystone 2 */
 162        .compatible     = NULL,
 163        .flags          = GPIO_SYSCON_FEAT_OUT,
 164        .bit_count      = 28,
 165        .dat_bit_offset = 4,
 166        .set            = keystone_gpio_set,
 167};
 168
 169static const struct of_device_id syscon_gpio_ids[] = {
 170        {
 171                .compatible     = "cirrus,ep7209-mctrl-gpio",
 172                .data           = &clps711x_mctrl_gpio,
 173        },
 174        {
 175                .compatible     = "ti,keystone-dsp-gpio",
 176                .data           = &keystone_dsp_gpio,
 177        },
 178        { }
 179};
 180MODULE_DEVICE_TABLE(of, syscon_gpio_ids);
 181
 182static int syscon_gpio_probe(struct platform_device *pdev)
 183{
 184        struct device *dev = &pdev->dev;
 185        const struct of_device_id *of_id;
 186        struct syscon_gpio_priv *priv;
 187        struct device_node *np = dev->of_node;
 188        int ret;
 189
 190        of_id = of_match_device(syscon_gpio_ids, dev);
 191        if (!of_id)
 192                return -ENODEV;
 193
 194        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 195        if (!priv)
 196                return -ENOMEM;
 197
 198        priv->data = of_id->data;
 199
 200        if (priv->data->compatible) {
 201                priv->syscon = syscon_regmap_lookup_by_compatible(
 202                                        priv->data->compatible);
 203                if (IS_ERR(priv->syscon))
 204                        return PTR_ERR(priv->syscon);
 205        } else {
 206                priv->syscon =
 207                        syscon_regmap_lookup_by_phandle(np, "gpio,syscon-dev");
 208                if (IS_ERR(priv->syscon))
 209                        return PTR_ERR(priv->syscon);
 210
 211                ret = of_property_read_u32_index(np, "gpio,syscon-dev", 1,
 212                                                 &priv->dreg_offset);
 213                if (ret)
 214                        dev_err(dev, "can't read the data register offset!\n");
 215
 216                priv->dreg_offset <<= 3;
 217
 218                ret = of_property_read_u32_index(np, "gpio,syscon-dev", 2,
 219                                                 &priv->dir_reg_offset);
 220                if (ret)
 221                        dev_dbg(dev, "can't read the dir register offset!\n");
 222
 223                priv->dir_reg_offset <<= 3;
 224        }
 225
 226        priv->chip.parent = dev;
 227        priv->chip.owner = THIS_MODULE;
 228        priv->chip.label = dev_name(dev);
 229        priv->chip.base = -1;
 230        priv->chip.ngpio = priv->data->bit_count;
 231        priv->chip.get = syscon_gpio_get;
 232        if (priv->data->flags & GPIO_SYSCON_FEAT_IN)
 233                priv->chip.direction_input = syscon_gpio_dir_in;
 234        if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) {
 235                priv->chip.set = priv->data->set ? : syscon_gpio_set;
 236                priv->chip.direction_output = syscon_gpio_dir_out;
 237        }
 238
 239        platform_set_drvdata(pdev, priv);
 240
 241        return devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv);
 242}
 243
 244static struct platform_driver syscon_gpio_driver = {
 245        .driver = {
 246                .name           = "gpio-syscon",
 247                .of_match_table = syscon_gpio_ids,
 248        },
 249        .probe  = syscon_gpio_probe,
 250};
 251module_platform_driver(syscon_gpio_driver);
 252
 253MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
 254MODULE_DESCRIPTION("SYSCON GPIO driver");
 255MODULE_LICENSE("GPL");
 256