1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* sl28cpld GPIO driver
*
* Copyright 2020 Michael Walle <michael@walle.cc>
*/
#include <linux/device.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/regmap.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/* GPIO flavor */
#define GPIO_REG_DIR 0x00
#define GPIO_REG_OUT 0x01
#define GPIO_REG_IN 0x02
#define GPIO_REG_IE 0x03
#define GPIO_REG_IP 0x04
/* input-only flavor */
#define GPI_REG_IN 0x00
/* output-only flavor */
#define GPO_REG_OUT 0x00
enum sl28cpld_gpio_type {
SL28CPLD_GPIO = 1,
SL28CPLD_GPI,
SL28CPLD_GPO,
};
static const struct regmap_irq sl28cpld_gpio_irqs[] = {
REGMAP_IRQ_REG_LINE(0, 8),
REGMAP_IRQ_REG_LINE(1, 8),
REGMAP_IRQ_REG_LINE(2, 8),
REGMAP_IRQ_REG_LINE(3, 8),
REGMAP_IRQ_REG_LINE(4, 8),
REGMAP_IRQ_REG_LINE(5, 8),
REGMAP_IRQ_REG_LINE(6, 8),
REGMAP_IRQ_REG_LINE(7, 8),
};
static int sl28cpld_gpio_irq_init(struct platform_device *pdev,
unsigned int base,
struct gpio_regmap_config *config)
{
struct regmap_irq_chip_data *irq_data;
struct regmap_irq_chip *irq_chip;
struct device *dev = &pdev->dev;
int irq, ret;
if (!device_property_read_bool(dev, "interrupt-controller"))
return 0;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL);
if (!irq_chip)
return -ENOMEM;
irq_chip->name = "sl28cpld-gpio-irq";
irq_chip->irqs = sl28cpld_gpio_irqs;
irq_chip->num_irqs = ARRAY_SIZE(sl28cpld_gpio_irqs);
irq_chip->num_regs = 1;
irq_chip->status_base = base + GPIO_REG_IP;
irq_chip->mask_base = base + GPIO_REG_IE;
irq_chip->mask_invert = true;
irq_chip->ack_base = base + GPIO_REG_IP;
ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev),
config->regmap, irq,
IRQF_SHARED | IRQF_ONESHOT,
0, irq_chip, &irq_data);
if (ret)
return ret;
config->irq_domain = regmap_irq_get_domain(irq_data);
return 0;
}
static int sl28cpld_gpio_probe(struct platform_device *pdev)
{
struct gpio_regmap_config config = {0};
enum sl28cpld_gpio_type type;
struct regmap *regmap;
u32 base;
int ret;
if (!pdev->dev.parent)
return -ENODEV;
type = (uintptr_t)device_get_match_data(&pdev->dev);
if (!type)
return -ENODEV;
ret = device_property_read_u32(&pdev->dev, "reg", &base);
if (ret)
return -EINVAL;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap)
return -ENODEV;
config.regmap = regmap;
config.parent = &pdev->dev;
config.ngpio = 8;
switch (type) {
case SL28CPLD_GPIO:
config.reg_dat_base = base + GPIO_REG_IN;
config.reg_set_base = base + GPIO_REG_OUT;
/* reg_dir_out_base might be zero */
config.reg_dir_out_base = GPIO_REGMAP_ADDR(base + GPIO_REG_DIR);
/* This type supports interrupts */
ret = sl28cpld_gpio_irq_init(pdev, base, &config);
if (ret)
return ret;
break;
case SL28CPLD_GPO:
config.reg_set_base = base + GPO_REG_OUT;
break;
case SL28CPLD_GPI:
config.reg_dat_base = base + GPI_REG_IN;
break;
default:
dev_err(&pdev->dev, "unknown type %d\n", type);
return -ENODEV;
}
return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&pdev->dev, &config));
}
static const struct of_device_id sl28cpld_gpio_of_match[] = {
{ .compatible = "kontron,sl28cpld-gpio", .data = (void *)SL28CPLD_GPIO },
{ .compatible = "kontron,sl28cpld-gpi", .data = (void *)SL28CPLD_GPI },
{ .compatible = "kontron,sl28cpld-gpo", .data = (void *)SL28CPLD_GPO },
{}
};
MODULE_DEVICE_TABLE(of, sl28cpld_gpio_of_match);
static struct platform_driver sl28cpld_gpio_driver = {
.probe = sl28cpld_gpio_probe,
.driver = {
.name = "sl28cpld-gpio",
.of_match_table = sl28cpld_gpio_of_match,
},
};
module_platform_driver(sl28cpld_gpio_driver);
MODULE_DESCRIPTION("sl28cpld GPIO Driver");
MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
MODULE_LICENSE("GPL");
|