summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-visconti.c
blob: 47455810bdb91ad35eaf50d2087f35f009732792 (plain)
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// SPDX-License-Identifier: GPL-2.0
/*
 * Toshiba Visconti GPIO Support
 *
 * (C) Copyright 2020 Toshiba Electronic Devices & Storage Corporation
 * (C) Copyright 2020 TOSHIBA CORPORATION
 *
 * Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>
 */

#include <linux/gpio/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/bitops.h>

/* register offset */
#define GPIO_DIR	0x00
#define GPIO_IDATA	0x08
#define GPIO_ODATA	0x10
#define GPIO_OSET	0x18
#define GPIO_OCLR	0x20
#define GPIO_INTMODE	0x30

#define BASE_HW_IRQ 24

struct visconti_gpio {
	void __iomem *base;
	spinlock_t lock; /* protect gpio register */
	struct gpio_chip gpio_chip;
	struct irq_chip irq_chip;
};

static int visconti_gpio_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct visconti_gpio *priv = gpiochip_get_data(gc);
	u32 offset = irqd_to_hwirq(d);
	u32 bit = BIT(offset);
	u32 intc_type = IRQ_TYPE_EDGE_RISING;
	u32 intmode, odata;
	int ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&priv->lock, flags);

	odata = readl(priv->base + GPIO_ODATA);
	intmode = readl(priv->base + GPIO_INTMODE);

	switch (type) {
	case IRQ_TYPE_EDGE_RISING:
		odata &= ~bit;
		intmode &= ~bit;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		odata |= bit;
		intmode &= ~bit;
		break;
	case IRQ_TYPE_EDGE_BOTH:
		intmode |= bit;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		intc_type = IRQ_TYPE_LEVEL_HIGH;
		odata &= ~bit;
		intmode &= ~bit;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		intc_type = IRQ_TYPE_LEVEL_HIGH;
		odata |= bit;
		intmode &= ~bit;
		break;
	default:
		ret = -EINVAL;
		goto err;
	}

	writel(odata, priv->base + GPIO_ODATA);
	writel(intmode, priv->base + GPIO_INTMODE);
	irq_set_irq_type(offset, intc_type);

	ret = irq_chip_set_type_parent(d, type);
err:
	spin_unlock_irqrestore(&priv->lock, flags);
	return ret;
}

static int visconti_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
					       unsigned int child,
					       unsigned int child_type,
					       unsigned int *parent,
					       unsigned int *parent_type)
{
	/* Interrupts 0..15 mapped to interrupts 24..39 on the GIC */
	if (child < 16) {
		/* All these interrupts are level high in the CPU */
		*parent_type = IRQ_TYPE_LEVEL_HIGH;
		*parent = child + BASE_HW_IRQ;
		return 0;
	}
	return -EINVAL;
}

static void *visconti_gpio_populate_parent_fwspec(struct gpio_chip *chip,
						  unsigned int parent_hwirq,
						  unsigned int parent_type)
{
	struct irq_fwspec *fwspec;

	fwspec = kmalloc(sizeof(*fwspec), GFP_KERNEL);
	if (!fwspec)
		return NULL;

	fwspec->fwnode = chip->irq.parent_domain->fwnode;
	fwspec->param_count = 3;
	fwspec->param[0] = 0;
	fwspec->param[1] = parent_hwirq;
	fwspec->param[2] = parent_type;

	return fwspec;
}

static int visconti_gpio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct visconti_gpio *priv;
	struct irq_chip *irq_chip;
	struct gpio_irq_chip *girq;
	struct irq_domain *parent;
	struct device_node *irq_parent;
	struct fwnode_handle *fwnode;
	int ret;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	spin_lock_init(&priv->lock);

	priv->base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(priv->base))
		return PTR_ERR(priv->base);

	irq_parent = of_irq_find_parent(dev->of_node);
	if (!irq_parent) {
		dev_err(dev, "No IRQ parent node\n");
		return -ENODEV;
	}

	parent = irq_find_host(irq_parent);
	if (!parent) {
		dev_err(dev, "No IRQ parent domain\n");
		return -ENODEV;
	}

	fwnode = of_node_to_fwnode(irq_parent);
	of_node_put(irq_parent);

	ret = bgpio_init(&priv->gpio_chip, dev, 4,
			 priv->base + GPIO_IDATA,
			 priv->base + GPIO_OSET,
			 priv->base + GPIO_OCLR,
			 priv->base + GPIO_DIR,
			 NULL,
			 0);
	if (ret) {
		dev_err(dev, "unable to init generic GPIO\n");
		return ret;
	}

	irq_chip = &priv->irq_chip;
	irq_chip->name = dev_name(dev);
	irq_chip->irq_mask = irq_chip_mask_parent;
	irq_chip->irq_unmask = irq_chip_unmask_parent;
	irq_chip->irq_eoi = irq_chip_eoi_parent;
	irq_chip->irq_set_type = visconti_gpio_irq_set_type;
	irq_chip->flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;

	girq = &priv->gpio_chip.irq;
	girq->chip = irq_chip;
	girq->fwnode = fwnode;
	girq->parent_domain = parent;
	girq->child_to_parent_hwirq = visconti_gpio_child_to_parent_hwirq;
	girq->populate_parent_alloc_arg = visconti_gpio_populate_parent_fwspec;
	girq->default_type = IRQ_TYPE_NONE;
	girq->handler = handle_level_irq;

	return devm_gpiochip_add_data(dev, &priv->gpio_chip, priv);
}

static const struct of_device_id visconti_gpio_of_match[] = {
	{ .compatible = "toshiba,gpio-tmpv7708", },
	{ /* end of table */ }
};
MODULE_DEVICE_TABLE(of, visconti_gpio_of_match);

static struct platform_driver visconti_gpio_driver = {
	.probe		= visconti_gpio_probe,
	.driver		= {
		.name	= "visconti_gpio",
		.of_match_table = of_match_ptr(visconti_gpio_of_match),
	}
};
module_platform_driver(visconti_gpio_driver);

MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>");
MODULE_DESCRIPTION("Toshiba Visconti GPIO Driver");
MODULE_LICENSE("GPL v2");