Commit | Line | Data |
---|---|---|
1454a928 SH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * GPIO latch driver | |
4 | * | |
5 | * Copyright (C) 2022 Sascha Hauer <s.hauer@pengutronix.de> | |
6 | * | |
7 | * This driver implements a GPIO (or better GPO as there is no input) | |
8 | * multiplexer based on latches like this: | |
9 | * | |
10 | * CLK0 ----------------------. ,--------. | |
11 | * CLK1 -------------------. `--------|> #0 | | |
12 | * | | | | |
13 | * OUT0 ----------------+--|-----------|D0 Q0|-----|< | |
14 | * OUT1 --------------+-|--|-----------|D1 Q1|-----|< | |
15 | * OUT2 ------------+-|-|--|-----------|D2 Q2|-----|< | |
16 | * OUT3 ----------+-|-|-|--|-----------|D3 Q3|-----|< | |
17 | * OUT4 --------+-|-|-|-|--|-----------|D4 Q4|-----|< | |
18 | * OUT5 ------+-|-|-|-|-|--|-----------|D5 Q5|-----|< | |
19 | * OUT6 ----+-|-|-|-|-|-|--|-----------|D6 Q6|-----|< | |
20 | * OUT7 --+-|-|-|-|-|-|-|--|-----------|D7 Q7|-----|< | |
21 | * | | | | | | | | | `--------' | |
22 | * | | | | | | | | | | |
23 | * | | | | | | | | | ,--------. | |
24 | * | | | | | | | | `-----------|> #1 | | |
25 | * | | | | | | | | | | | |
26 | * | | | | | | | `--------------|D0 Q0|-----|< | |
27 | * | | | | | | `----------------|D1 Q1|-----|< | |
28 | * | | | | | `------------------|D2 Q2|-----|< | |
29 | * | | | | `--------------------|D3 Q3|-----|< | |
30 | * | | | `----------------------|D4 Q4|-----|< | |
31 | * | | `------------------------|D5 Q5|-----|< | |
32 | * | `--------------------------|D6 Q6|-----|< | |
33 | * `----------------------------|D7 Q7|-----|< | |
34 | * `--------' | |
35 | * | |
36 | * The above is just an example. The actual number of number of latches and | |
37 | * the number of inputs per latch is derived from the number of GPIOs given | |
38 | * in the corresponding device tree properties. | |
39 | */ | |
40 | ||
41 | #include <linux/err.h> | |
42 | #include <linux/gpio/consumer.h> | |
43 | #include <linux/gpio/driver.h> | |
44 | #include <linux/module.h> | |
45 | #include <linux/mod_devicetable.h> | |
46 | #include <linux/platform_device.h> | |
47 | #include <linux/delay.h> | |
48 | ||
49 | #include "gpiolib.h" | |
50 | ||
51 | struct gpio_latch_priv { | |
52 | struct gpio_chip gc; | |
53 | struct gpio_descs *clk_gpios; | |
54 | struct gpio_descs *latched_gpios; | |
55 | int n_latched_gpios; | |
56 | unsigned int setup_duration_ns; | |
57 | unsigned int clock_duration_ns; | |
58 | unsigned long *shadow; | |
59 | /* | |
60 | * Depending on whether any of the underlying GPIOs may sleep we either | |
61 | * use a mutex or a spinlock to protect our shadow map. | |
62 | */ | |
63 | union { | |
64 | struct mutex mutex; /* protects @shadow */ | |
65 | spinlock_t spinlock; /* protects @shadow */ | |
66 | }; | |
67 | }; | |
68 | ||
69 | static int gpio_latch_get_direction(struct gpio_chip *gc, unsigned int offset) | |
70 | { | |
71 | return GPIO_LINE_DIRECTION_OUT; | |
72 | } | |
73 | ||
74 | static void gpio_latch_set_unlocked(struct gpio_latch_priv *priv, | |
75 | void (*set)(struct gpio_desc *desc, int value), | |
76 | unsigned int offset, bool val) | |
77 | { | |
78 | int latch = offset / priv->n_latched_gpios; | |
79 | int i; | |
80 | ||
81 | assign_bit(offset, priv->shadow, val); | |
82 | ||
83 | for (i = 0; i < priv->n_latched_gpios; i++) | |
84 | set(priv->latched_gpios->desc[i], | |
85 | test_bit(latch * priv->n_latched_gpios + i, priv->shadow)); | |
86 | ||
87 | ndelay(priv->setup_duration_ns); | |
88 | set(priv->clk_gpios->desc[latch], 1); | |
89 | ndelay(priv->clock_duration_ns); | |
90 | set(priv->clk_gpios->desc[latch], 0); | |
91 | } | |
92 | ||
93 | static void gpio_latch_set(struct gpio_chip *gc, unsigned int offset, int val) | |
94 | { | |
95 | struct gpio_latch_priv *priv = gpiochip_get_data(gc); | |
96 | unsigned long flags; | |
97 | ||
98 | spin_lock_irqsave(&priv->spinlock, flags); | |
99 | ||
100 | gpio_latch_set_unlocked(priv, gpiod_set_value, offset, val); | |
101 | ||
102 | spin_unlock_irqrestore(&priv->spinlock, flags); | |
103 | } | |
104 | ||
105 | static void gpio_latch_set_can_sleep(struct gpio_chip *gc, unsigned int offset, int val) | |
106 | { | |
107 | struct gpio_latch_priv *priv = gpiochip_get_data(gc); | |
108 | ||
109 | mutex_lock(&priv->mutex); | |
110 | ||
111 | gpio_latch_set_unlocked(priv, gpiod_set_value_cansleep, offset, val); | |
112 | ||
113 | mutex_unlock(&priv->mutex); | |
114 | } | |
115 | ||
116 | static bool gpio_latch_can_sleep(struct gpio_latch_priv *priv, unsigned int n_latches) | |
117 | { | |
118 | int i; | |
119 | ||
120 | for (i = 0; i < n_latches; i++) | |
121 | if (gpiod_cansleep(priv->clk_gpios->desc[i])) | |
122 | return true; | |
123 | ||
124 | for (i = 0; i < priv->n_latched_gpios; i++) | |
125 | if (gpiod_cansleep(priv->latched_gpios->desc[i])) | |
126 | return true; | |
127 | ||
128 | return false; | |
129 | } | |
130 | ||
131 | /* | |
132 | * Some value which is still acceptable to delay in atomic context. | |
133 | * If we need to go higher we might have to switch to usleep_range(), | |
134 | * but that cannot ne used in atomic context and the driver would have | |
135 | * to be adjusted to support that. | |
136 | */ | |
137 | #define DURATION_NS_MAX 5000 | |
138 | ||
139 | static int gpio_latch_probe(struct platform_device *pdev) | |
140 | { | |
141 | struct gpio_latch_priv *priv; | |
142 | unsigned int n_latches; | |
143 | struct device_node *np = pdev->dev.of_node; | |
144 | ||
145 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
146 | if (!priv) | |
147 | return -ENOMEM; | |
148 | ||
149 | priv->clk_gpios = devm_gpiod_get_array(&pdev->dev, "clk", GPIOD_OUT_LOW); | |
150 | if (IS_ERR(priv->clk_gpios)) | |
151 | return PTR_ERR(priv->clk_gpios); | |
152 | ||
153 | priv->latched_gpios = devm_gpiod_get_array(&pdev->dev, "latched", GPIOD_OUT_LOW); | |
154 | if (IS_ERR(priv->latched_gpios)) | |
155 | return PTR_ERR(priv->latched_gpios); | |
156 | ||
157 | n_latches = priv->clk_gpios->ndescs; | |
158 | priv->n_latched_gpios = priv->latched_gpios->ndescs; | |
159 | ||
160 | priv->shadow = devm_bitmap_zalloc(&pdev->dev, n_latches * priv->n_latched_gpios, | |
161 | GFP_KERNEL); | |
162 | if (!priv->shadow) | |
163 | return -ENOMEM; | |
164 | ||
165 | if (gpio_latch_can_sleep(priv, n_latches)) { | |
166 | priv->gc.can_sleep = true; | |
167 | priv->gc.set = gpio_latch_set_can_sleep; | |
168 | mutex_init(&priv->mutex); | |
169 | } else { | |
170 | priv->gc.can_sleep = false; | |
171 | priv->gc.set = gpio_latch_set; | |
172 | spin_lock_init(&priv->spinlock); | |
173 | } | |
174 | ||
175 | of_property_read_u32(np, "setup-duration-ns", &priv->setup_duration_ns); | |
176 | if (priv->setup_duration_ns > DURATION_NS_MAX) { | |
177 | dev_warn(&pdev->dev, "setup-duration-ns too high, limit to %d\n", | |
178 | DURATION_NS_MAX); | |
179 | priv->setup_duration_ns = DURATION_NS_MAX; | |
180 | } | |
181 | ||
182 | of_property_read_u32(np, "clock-duration-ns", &priv->clock_duration_ns); | |
183 | if (priv->clock_duration_ns > DURATION_NS_MAX) { | |
184 | dev_warn(&pdev->dev, "clock-duration-ns too high, limit to %d\n", | |
185 | DURATION_NS_MAX); | |
186 | priv->clock_duration_ns = DURATION_NS_MAX; | |
187 | } | |
188 | ||
189 | priv->gc.get_direction = gpio_latch_get_direction; | |
190 | priv->gc.ngpio = n_latches * priv->n_latched_gpios; | |
191 | priv->gc.owner = THIS_MODULE; | |
192 | priv->gc.base = -1; | |
193 | priv->gc.parent = &pdev->dev; | |
194 | ||
195 | platform_set_drvdata(pdev, priv); | |
196 | ||
197 | return devm_gpiochip_add_data(&pdev->dev, &priv->gc, priv); | |
198 | } | |
199 | ||
200 | static const struct of_device_id gpio_latch_ids[] = { | |
201 | { | |
202 | .compatible = "gpio-latch", | |
203 | }, | |
204 | { /* sentinel */ } | |
205 | }; | |
206 | MODULE_DEVICE_TABLE(of, gpio_latch_ids); | |
207 | ||
208 | static struct platform_driver gpio_latch_driver = { | |
209 | .driver = { | |
210 | .name = "gpio-latch", | |
211 | .of_match_table = gpio_latch_ids, | |
212 | }, | |
213 | .probe = gpio_latch_probe, | |
214 | }; | |
215 | module_platform_driver(gpio_latch_driver); | |
216 | ||
217 | MODULE_LICENSE("GPL v2"); | |
218 | MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); | |
219 | MODULE_DESCRIPTION("GPIO latch driver"); |