summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-sl28cpld.c
blob: 2195f88c204855a6797841f13e6eb47cccac2859 (plain) (blame)
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
// 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->unmask_base = base + GPIO_REG_IE;
	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");