summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-delay.c
blob: b489b561b225178566bfc44f9fd2edfca874da93 (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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2022 TQ-Systems GmbH
 * Author: Alexander Stein <linux@ew.tq-group.com>
 */

#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/delay.h>

#include "gpiolib.h"

struct gpio_delay_timing {
	unsigned long ramp_up_delay_us;
	unsigned long ramp_down_delay_us;
};

struct gpio_delay_priv {
	struct gpio_chip gc;
	struct gpio_descs *input_gpio;
	struct gpio_delay_timing *delay_timings;
};

static int gpio_delay_get_direction(struct gpio_chip *gc, unsigned int offset)
{
	return GPIO_LINE_DIRECTION_OUT;
}

static void gpio_delay_set(struct gpio_chip *gc, unsigned int offset, int val)
{
	struct gpio_delay_priv *priv = gpiochip_get_data(gc);
	struct gpio_desc *gpio_desc = priv->input_gpio->desc[offset];
	const struct gpio_delay_timing *delay_timings;
	bool ramp_up;

	gpiod_set_value(gpio_desc, val);

	delay_timings = &priv->delay_timings[offset];
	ramp_up = (!gpiod_is_active_low(gpio_desc) && val) ||
		  (gpiod_is_active_low(gpio_desc) && !val);

	if (ramp_up && delay_timings->ramp_up_delay_us)
		udelay(delay_timings->ramp_up_delay_us);
	if (!ramp_up && delay_timings->ramp_down_delay_us)
		udelay(delay_timings->ramp_down_delay_us);
}

static void gpio_delay_set_can_sleep(struct gpio_chip *gc, unsigned int offset, int val)
{
	struct gpio_delay_priv *priv = gpiochip_get_data(gc);
	struct gpio_desc *gpio_desc = priv->input_gpio->desc[offset];
	const struct gpio_delay_timing *delay_timings;
	bool ramp_up;

	gpiod_set_value_cansleep(gpio_desc, val);

	delay_timings = &priv->delay_timings[offset];
	ramp_up = (!gpiod_is_active_low(gpio_desc) && val) ||
		  (gpiod_is_active_low(gpio_desc) && !val);

	if (ramp_up && delay_timings->ramp_up_delay_us)
		fsleep(delay_timings->ramp_up_delay_us);
	if (!ramp_up && delay_timings->ramp_down_delay_us)
		fsleep(delay_timings->ramp_down_delay_us);
}

static int gpio_delay_of_xlate(struct gpio_chip *gc,
			       const struct of_phandle_args *gpiospec,
			       u32 *flags)
{
	struct gpio_delay_priv *priv = gpiochip_get_data(gc);
	struct gpio_delay_timing *timings;
	u32 line;

	if (gpiospec->args_count != gc->of_gpio_n_cells)
		return -EINVAL;

	line = gpiospec->args[0];
	if (line >= gc->ngpio)
		return -EINVAL;

	timings = &priv->delay_timings[line];
	timings->ramp_up_delay_us = gpiospec->args[1];
	timings->ramp_down_delay_us = gpiospec->args[2];

	return line;
}

static bool gpio_delay_can_sleep(const struct gpio_delay_priv *priv)
{
	int i;

	for (i = 0; i < priv->input_gpio->ndescs; i++)
		if (gpiod_cansleep(priv->input_gpio->desc[i]))
			return true;

	return false;
}

static int gpio_delay_probe(struct platform_device *pdev)
{
	struct gpio_delay_priv *priv;

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

	priv->input_gpio = devm_gpiod_get_array(&pdev->dev, NULL, GPIOD_OUT_LOW);
	if (IS_ERR(priv->input_gpio))
		return dev_err_probe(&pdev->dev, PTR_ERR(priv->input_gpio),
				     "Failed to get input-gpios");

	priv->delay_timings = devm_kcalloc(&pdev->dev,
					   priv->input_gpio->ndescs,
					   sizeof(*priv->delay_timings),
					   GFP_KERNEL);
	if (!priv->delay_timings)
		return -ENOMEM;

	if (gpio_delay_can_sleep(priv)) {
		priv->gc.can_sleep = true;
		priv->gc.set = gpio_delay_set_can_sleep;
	} else {
		priv->gc.can_sleep = false;
		priv->gc.set = gpio_delay_set;
	}

	priv->gc.get_direction = gpio_delay_get_direction;
	priv->gc.of_xlate = gpio_delay_of_xlate;
	priv->gc.of_gpio_n_cells = 3;
	priv->gc.ngpio = priv->input_gpio->ndescs;
	priv->gc.owner = THIS_MODULE;
	priv->gc.base = -1;
	priv->gc.parent = &pdev->dev;

	platform_set_drvdata(pdev, priv);

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

static const struct of_device_id gpio_delay_ids[] = {
	{
		.compatible = "gpio-delay",
	},
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gpio_delay_ids);

static struct platform_driver gpio_delay_driver = {
	.driver	= {
		.name		= "gpio-delay",
		.of_match_table	= gpio_delay_ids,
	},
	.probe	= gpio_delay_probe,
};
module_platform_driver(gpio_delay_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alexander Stein <alexander.stein@ew.tq-group.com>");
MODULE_DESCRIPTION("GPIO delay driver");