linux/drivers/gpio/gpio-logicvc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2019 Bootlin
   4 * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
   5 */
   6
   7#include <linux/err.h>
   8#include <linux/gpio/driver.h>
   9#include <linux/module.h>
  10#include <linux/of.h>
  11#include <linux/of_device.h>
  12#include <linux/of_address.h>
  13#include <linux/platform_device.h>
  14#include <linux/regmap.h>
  15#include <linux/mfd/syscon.h>
  16
  17#define LOGICVC_CTRL_REG                0x40
  18#define LOGICVC_CTRL_GPIO_SHIFT         11
  19#define LOGICVC_CTRL_GPIO_BITS          5
  20
  21#define LOGICVC_POWER_CTRL_REG          0x78
  22#define LOGICVC_POWER_CTRL_GPIO_SHIFT   0
  23#define LOGICVC_POWER_CTRL_GPIO_BITS    4
  24
  25struct logicvc_gpio {
  26        struct gpio_chip chip;
  27        struct regmap *regmap;
  28};
  29
  30static void logicvc_gpio_offset(struct logicvc_gpio *logicvc, unsigned offset,
  31                                unsigned int *reg, unsigned int *bit)
  32{
  33        if (offset >= LOGICVC_CTRL_GPIO_BITS) {
  34                *reg = LOGICVC_POWER_CTRL_REG;
  35
  36                /* To the (virtual) power ctrl offset. */
  37                offset -= LOGICVC_CTRL_GPIO_BITS;
  38                /* To the actual bit offset in reg. */
  39                offset += LOGICVC_POWER_CTRL_GPIO_SHIFT;
  40        } else {
  41                *reg = LOGICVC_CTRL_REG;
  42
  43                /* To the actual bit offset in reg. */
  44                offset += LOGICVC_CTRL_GPIO_SHIFT;
  45        }
  46
  47        *bit = BIT(offset);
  48}
  49
  50static int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset)
  51{
  52        struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
  53        unsigned int reg, bit, value;
  54        int ret;
  55
  56        logicvc_gpio_offset(logicvc, offset, &reg, &bit);
  57
  58        ret = regmap_read(logicvc->regmap, reg, &value);
  59        if (ret)
  60                return ret;
  61
  62        return !!(value & bit);
  63}
  64
  65static void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
  66{
  67        struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
  68        unsigned int reg, bit;
  69
  70        logicvc_gpio_offset(logicvc, offset, &reg, &bit);
  71
  72        regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0);
  73}
  74
  75static int logicvc_gpio_direction_output(struct gpio_chip *chip,
  76                                         unsigned offset, int value)
  77{
  78        /* Pins are always configured as output, so just set the value. */
  79        logicvc_gpio_set(chip, offset, value);
  80
  81        return 0;
  82}
  83
  84static struct regmap_config logicvc_gpio_regmap_config = {
  85        .reg_bits       = 32,
  86        .val_bits       = 32,
  87        .reg_stride     = 4,
  88        .name           = "logicvc-gpio",
  89};
  90
  91static int logicvc_gpio_probe(struct platform_device *pdev)
  92{
  93        struct device *dev = &pdev->dev;
  94        struct device_node *of_node = dev->of_node;
  95        struct logicvc_gpio *logicvc;
  96        int ret;
  97
  98        logicvc = devm_kzalloc(dev, sizeof(*logicvc), GFP_KERNEL);
  99        if (!logicvc)
 100                return -ENOMEM;
 101
 102        /* Try to get regmap from parent first. */
 103        logicvc->regmap = syscon_node_to_regmap(of_node->parent);
 104
 105        /* Grab our own regmap if that fails. */
 106        if (IS_ERR(logicvc->regmap)) {
 107                struct resource res;
 108                void __iomem *base;
 109
 110                ret = of_address_to_resource(of_node, 0, &res);
 111                if (ret) {
 112                        dev_err(dev, "Failed to get resource from address\n");
 113                        return ret;
 114                }
 115
 116                base = devm_ioremap_resource(dev, &res);
 117                if (IS_ERR(base))
 118                        return PTR_ERR(base);
 119
 120                logicvc_gpio_regmap_config.max_register = resource_size(&res) -
 121                        logicvc_gpio_regmap_config.reg_stride;
 122
 123                logicvc->regmap =
 124                        devm_regmap_init_mmio(dev, base,
 125                                              &logicvc_gpio_regmap_config);
 126                if (IS_ERR(logicvc->regmap)) {
 127                        dev_err(dev, "Failed to create regmap for I/O\n");
 128                        return PTR_ERR(logicvc->regmap);
 129                }
 130        }
 131
 132        logicvc->chip.parent = dev;
 133        logicvc->chip.owner = THIS_MODULE;
 134        logicvc->chip.label = dev_name(dev);
 135        logicvc->chip.base = -1;
 136        logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS +
 137                              LOGICVC_POWER_CTRL_GPIO_BITS;
 138        logicvc->chip.get = logicvc_gpio_get;
 139        logicvc->chip.set = logicvc_gpio_set;
 140        logicvc->chip.direction_output = logicvc_gpio_direction_output;
 141
 142        platform_set_drvdata(pdev, logicvc);
 143
 144        return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc);
 145}
 146
 147static const struct of_device_id logicivc_gpio_of_table[] = {
 148        {
 149                .compatible     = "xylon,logicvc-3.02.a-gpio",
 150        },
 151        { }
 152};
 153
 154MODULE_DEVICE_TABLE(of, logicivc_gpio_of_table);
 155
 156static struct platform_driver logicvc_gpio_driver = {
 157        .driver = {
 158                .name           = "gpio-logicvc",
 159                .of_match_table = logicivc_gpio_of_table,
 160        },
 161        .probe  = logicvc_gpio_probe,
 162};
 163
 164module_platform_driver(logicvc_gpio_driver);
 165
 166MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
 167MODULE_DESCRIPTION("Xylon LogiCVC GPIO driver");
 168MODULE_LICENSE("GPL");
 169