summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-regmap.c
blob: 6383136cbe5997714f0b10ffc0a9cbaa9cc17e11 (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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// SPDX-License-Identifier: GPL-2.0-only
/*
 * regmap based generic GPIO driver
 *
 * Copyright 2020 Michael Walle <michael@walle.cc>
 */

#include <linux/gpio/driver.h>
#include <linux/gpio/regmap.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>

struct gpio_regmap {
	struct device *parent;
	struct regmap *regmap;
	struct gpio_chip gpio_chip;

	int reg_stride;
	int ngpio_per_reg;
	unsigned int reg_dat_base;
	unsigned int reg_set_base;
	unsigned int reg_clr_base;
	unsigned int reg_dir_in_base;
	unsigned int reg_dir_out_base;

	int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
			      unsigned int offset, unsigned int *reg,
			      unsigned int *mask);

	void *driver_data;
};

static unsigned int gpio_regmap_addr(unsigned int addr)
{
	if (addr == GPIO_REGMAP_ADDR_ZERO)
		return 0;

	return addr;
}

static int gpio_regmap_simple_xlate(struct gpio_regmap *gpio,
				    unsigned int base, unsigned int offset,
				    unsigned int *reg, unsigned int *mask)
{
	unsigned int line = offset % gpio->ngpio_per_reg;
	unsigned int stride = offset / gpio->ngpio_per_reg;

	*reg = base + stride * gpio->reg_stride;
	*mask = BIT(line);

	return 0;
}

static int gpio_regmap_get(struct gpio_chip *chip, unsigned int offset)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	unsigned int base, val, reg, mask;
	int ret;

	/* we might not have an output register if we are input only */
	if (gpio->reg_dat_base)
		base = gpio_regmap_addr(gpio->reg_dat_base);
	else
		base = gpio_regmap_addr(gpio->reg_set_base);

	ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
	if (ret)
		return ret;

	ret = regmap_read(gpio->regmap, reg, &val);
	if (ret)
		return ret;

	return !!(val & mask);
}

static void gpio_regmap_set(struct gpio_chip *chip, unsigned int offset,
			    int val)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	unsigned int base = gpio_regmap_addr(gpio->reg_set_base);
	unsigned int reg, mask;

	gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
	if (val)
		regmap_update_bits(gpio->regmap, reg, mask, mask);
	else
		regmap_update_bits(gpio->regmap, reg, mask, 0);
}

static void gpio_regmap_set_with_clear(struct gpio_chip *chip,
				       unsigned int offset, int val)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	unsigned int base, reg, mask;

	if (val)
		base = gpio_regmap_addr(gpio->reg_set_base);
	else
		base = gpio_regmap_addr(gpio->reg_clr_base);

	gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
	regmap_write(gpio->regmap, reg, mask);
}

static int gpio_regmap_get_direction(struct gpio_chip *chip,
				     unsigned int offset)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	unsigned int base, val, reg, mask;
	int invert, ret;

	if (gpio->reg_dir_out_base) {
		base = gpio_regmap_addr(gpio->reg_dir_out_base);
		invert = 0;
	} else if (gpio->reg_dir_in_base) {
		base = gpio_regmap_addr(gpio->reg_dir_in_base);
		invert = 1;
	} else {
		return -EOPNOTSUPP;
	}

	ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
	if (ret)
		return ret;

	ret = regmap_read(gpio->regmap, reg, &val);
	if (ret)
		return ret;

	if (!!(val & mask) ^ invert)
		return GPIO_LINE_DIRECTION_OUT;
	else
		return GPIO_LINE_DIRECTION_IN;
}

static int gpio_regmap_set_direction(struct gpio_chip *chip,
				     unsigned int offset, bool output)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	unsigned int base, val, reg, mask;
	int invert, ret;

	if (gpio->reg_dir_out_base) {
		base = gpio_regmap_addr(gpio->reg_dir_out_base);
		invert = 0;
	} else if (gpio->reg_dir_in_base) {
		base = gpio_regmap_addr(gpio->reg_dir_in_base);
		invert = 1;
	} else {
		return -EOPNOTSUPP;
	}

	ret = gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
	if (ret)
		return ret;

	if (invert)
		val = output ? 0 : mask;
	else
		val = output ? mask : 0;

	return regmap_update_bits(gpio->regmap, reg, mask, val);
}

static int gpio_regmap_direction_input(struct gpio_chip *chip,
				       unsigned int offset)
{
	return gpio_regmap_set_direction(chip, offset, false);
}

static int gpio_regmap_direction_output(struct gpio_chip *chip,
					unsigned int offset, int value)
{
	gpio_regmap_set(chip, offset, value);

	return gpio_regmap_set_direction(chip, offset, true);
}

void *gpio_regmap_get_drvdata(struct gpio_regmap *gpio)
{
	return gpio->driver_data;
}
EXPORT_SYMBOL_GPL(gpio_regmap_get_drvdata);

/**
 * gpio_regmap_register() - Register a generic regmap GPIO controller
 * @config: configuration for gpio_regmap
 *
 * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
 */
struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config)
{
	struct gpio_regmap *gpio;
	struct gpio_chip *chip;
	int ret;

	if (!config->parent)
		return ERR_PTR(-EINVAL);

	if (!config->ngpio)
		return ERR_PTR(-EINVAL);

	/* we need at least one */
	if (!config->reg_dat_base && !config->reg_set_base)
		return ERR_PTR(-EINVAL);

	/* if we have a direction register we need both input and output */
	if ((config->reg_dir_out_base || config->reg_dir_in_base) &&
	    (!config->reg_dat_base || !config->reg_set_base))
		return ERR_PTR(-EINVAL);

	/* we don't support having both registers simultaneously for now */
	if (config->reg_dir_out_base && config->reg_dir_in_base)
		return ERR_PTR(-EINVAL);

	gpio = kzalloc(sizeof(*gpio), GFP_KERNEL);
	if (!gpio)
		return ERR_PTR(-ENOMEM);

	gpio->parent = config->parent;
	gpio->driver_data = config->drvdata;
	gpio->regmap = config->regmap;
	gpio->ngpio_per_reg = config->ngpio_per_reg;
	gpio->reg_stride = config->reg_stride;
	gpio->reg_mask_xlate = config->reg_mask_xlate;
	gpio->reg_dat_base = config->reg_dat_base;
	gpio->reg_set_base = config->reg_set_base;
	gpio->reg_clr_base = config->reg_clr_base;
	gpio->reg_dir_in_base = config->reg_dir_in_base;
	gpio->reg_dir_out_base = config->reg_dir_out_base;

	/* if not set, assume there is only one register */
	if (!gpio->ngpio_per_reg)
		gpio->ngpio_per_reg = config->ngpio;

	/* if not set, assume they are consecutive */
	if (!gpio->reg_stride)
		gpio->reg_stride = 1;

	if (!gpio->reg_mask_xlate)
		gpio->reg_mask_xlate = gpio_regmap_simple_xlate;

	chip = &gpio->gpio_chip;
	chip->parent = config->parent;
	chip->fwnode = config->fwnode;
	chip->base = -1;
	chip->ngpio = config->ngpio;
	chip->names = config->names;
	chip->label = config->label ?: dev_name(config->parent);

	/*
	 * If our regmap is fast_io we should probably set can_sleep to false.
	 * Right now, the regmap doesn't save this property, nor is there any
	 * access function for it.
	 * The only regmap type which uses fast_io is regmap-mmio. For now,
	 * assume a safe default of true here.
	 */
	chip->can_sleep = true;

	chip->get = gpio_regmap_get;
	if (gpio->reg_set_base && gpio->reg_clr_base)
		chip->set = gpio_regmap_set_with_clear;
	else if (gpio->reg_set_base)
		chip->set = gpio_regmap_set;

	if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
		chip->get_direction = gpio_regmap_get_direction;
		chip->direction_input = gpio_regmap_direction_input;
		chip->direction_output = gpio_regmap_direction_output;
	}

	ret = gpiochip_add_data(chip, gpio);
	if (ret < 0)
		goto err_free_gpio;

	if (config->irq_domain) {
		ret = gpiochip_irqchip_add_domain(chip, config->irq_domain);
		if (ret)
			goto err_remove_gpiochip;
	}

	return gpio;

err_remove_gpiochip:
	gpiochip_remove(chip);
err_free_gpio:
	kfree(gpio);
	return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(gpio_regmap_register);

/**
 * gpio_regmap_unregister() - Unregister a generic regmap GPIO controller
 * @gpio: gpio_regmap device to unregister
 */
void gpio_regmap_unregister(struct gpio_regmap *gpio)
{
	gpiochip_remove(&gpio->gpio_chip);
	kfree(gpio);
}
EXPORT_SYMBOL_GPL(gpio_regmap_unregister);

static void devm_gpio_regmap_unregister(void *res)
{
	gpio_regmap_unregister(res);
}

/**
 * devm_gpio_regmap_register() - resource managed gpio_regmap_register()
 * @dev: device that is registering this GPIO device
 * @config: configuration for gpio_regmap
 *
 * Managed gpio_regmap_register(). For generic regmap GPIO device registered by
 * this function, gpio_regmap_unregister() is automatically called on driver
 * detach. See gpio_regmap_register() for more information.
 *
 * Return: A pointer to the registered gpio_regmap or ERR_PTR error value.
 */
struct gpio_regmap *devm_gpio_regmap_register(struct device *dev,
					      const struct gpio_regmap_config *config)
{
	struct gpio_regmap *gpio;
	int ret;

	gpio = gpio_regmap_register(config);

	if (IS_ERR(gpio))
		return gpio;

	ret = devm_add_action_or_reset(dev, devm_gpio_regmap_unregister, gpio);
	if (ret)
		return ERR_PTR(ret);

	return gpio;
}
EXPORT_SYMBOL_GPL(devm_gpio_regmap_register);

MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
MODULE_DESCRIPTION("GPIO generic regmap driver core");
MODULE_LICENSE("GPL");