Linux 6.9-rc1
[linux-2.6-microblaze.git] / drivers / counter / counter-core.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Generic Counter interface
4  * Copyright (C) 2020 William Breathitt Gray
5  */
6 #include <linux/cdev.h>
7 #include <linux/counter.h>
8 #include <linux/device.h>
9 #include <linux/device/bus.h>
10 #include <linux/export.h>
11 #include <linux/fs.h>
12 #include <linux/gfp.h>
13 #include <linux/idr.h>
14 #include <linux/init.h>
15 #include <linux/kdev_t.h>
16 #include <linux/module.h>
17 #include <linux/mutex.h>
18 #include <linux/slab.h>
19 #include <linux/types.h>
20 #include <linux/wait.h>
21
22 #include "counter-chrdev.h"
23 #include "counter-sysfs.h"
24
25 #define COUNTER_NAME    "counter"
26
27 /* Provides a unique ID for each counter device */
28 static DEFINE_IDA(counter_ida);
29
30 struct counter_device_allochelper {
31         struct counter_device counter;
32
33         /*
34          * This ensures private data behaves like if it were kmalloced
35          * separately. Also ensures the minimum alignment for safe DMA
36          * operations (which may or may not mean cache alignment).
37          */
38         unsigned long privdata[] __aligned(ARCH_DMA_MINALIGN);
39 };
40
41 static void counter_device_release(struct device *dev)
42 {
43         struct counter_device *const counter =
44                 container_of(dev, struct counter_device, dev);
45
46         counter_chrdev_remove(counter);
47         ida_free(&counter_ida, dev->id);
48
49         kfree(container_of(counter, struct counter_device_allochelper, counter));
50 }
51
52 static struct device_type counter_device_type = {
53         .name = "counter_device",
54         .release = counter_device_release,
55 };
56
57 static struct bus_type counter_bus_type = {
58         .name = "counter",
59         .dev_name = "counter",
60 };
61
62 static dev_t counter_devt;
63
64 /**
65  * counter_priv - access counter device private data
66  * @counter: counter device
67  *
68  * Get the counter device private data
69  */
70 void *counter_priv(const struct counter_device *const counter)
71 {
72         struct counter_device_allochelper *ch =
73                 container_of(counter, struct counter_device_allochelper, counter);
74
75         return &ch->privdata;
76 }
77 EXPORT_SYMBOL_NS_GPL(counter_priv, COUNTER);
78
79 /**
80  * counter_alloc - allocate a counter_device
81  * @sizeof_priv: size of the driver private data
82  *
83  * This is part one of counter registration. The structure is allocated
84  * dynamically to ensure the right lifetime for the embedded struct device.
85  *
86  * If this succeeds, call counter_put() to get rid of the counter_device again.
87  */
88 struct counter_device *counter_alloc(size_t sizeof_priv)
89 {
90         struct counter_device_allochelper *ch;
91         struct counter_device *counter;
92         struct device *dev;
93         int err;
94
95         ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
96         if (!ch)
97                 return NULL;
98
99         counter = &ch->counter;
100         dev = &counter->dev;
101
102         /* Acquire unique ID */
103         err = ida_alloc(&counter_ida, GFP_KERNEL);
104         if (err < 0)
105                 goto err_ida_alloc;
106         dev->id = err;
107
108         mutex_init(&counter->ops_exist_lock);
109         dev->type = &counter_device_type;
110         dev->bus = &counter_bus_type;
111         dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
112
113         err = counter_chrdev_add(counter);
114         if (err < 0)
115                 goto err_chrdev_add;
116
117         device_initialize(dev);
118
119         err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);
120         if (err)
121                 goto err_dev_set_name;
122
123         return counter;
124
125 err_dev_set_name:
126
127         counter_chrdev_remove(counter);
128 err_chrdev_add:
129
130         ida_free(&counter_ida, dev->id);
131 err_ida_alloc:
132
133         kfree(ch);
134
135         return NULL;
136 }
137 EXPORT_SYMBOL_NS_GPL(counter_alloc, COUNTER);
138
139 void counter_put(struct counter_device *counter)
140 {
141         put_device(&counter->dev);
142 }
143 EXPORT_SYMBOL_NS_GPL(counter_put, COUNTER);
144
145 /**
146  * counter_add - complete registration of a counter
147  * @counter: the counter to add
148  *
149  * This is part two of counter registration.
150  *
151  * If this succeeds, call counter_unregister() to get rid of the counter_device again.
152  */
153 int counter_add(struct counter_device *counter)
154 {
155         int err;
156         struct device *dev = &counter->dev;
157
158         if (counter->parent) {
159                 dev->parent = counter->parent;
160                 dev->of_node = counter->parent->of_node;
161         }
162
163         err = counter_sysfs_add(counter);
164         if (err < 0)
165                 return err;
166
167         /* implies device_add(dev) */
168         return cdev_device_add(&counter->chrdev, dev);
169 }
170 EXPORT_SYMBOL_NS_GPL(counter_add, COUNTER);
171
172 /**
173  * counter_unregister - unregister Counter from the system
174  * @counter:    pointer to Counter to unregister
175  *
176  * The Counter is unregistered from the system.
177  */
178 void counter_unregister(struct counter_device *const counter)
179 {
180         if (!counter)
181                 return;
182
183         cdev_device_del(&counter->chrdev, &counter->dev);
184
185         mutex_lock(&counter->ops_exist_lock);
186
187         counter->ops = NULL;
188         wake_up(&counter->events_wait);
189
190         mutex_unlock(&counter->ops_exist_lock);
191 }
192 EXPORT_SYMBOL_NS_GPL(counter_unregister, COUNTER);
193
194 static void devm_counter_release(void *counter)
195 {
196         counter_unregister(counter);
197 }
198
199 static void devm_counter_put(void *counter)
200 {
201         counter_put(counter);
202 }
203
204 /**
205  * devm_counter_alloc - allocate a counter_device
206  * @dev: the device to register the release callback for
207  * @sizeof_priv: size of the driver private data
208  *
209  * This is the device managed version of counter_add(). It registers a cleanup
210  * callback to care for calling counter_put().
211  */
212 struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
213 {
214         struct counter_device *counter;
215         int err;
216
217         counter = counter_alloc(sizeof_priv);
218         if (!counter)
219                 return NULL;
220
221         err = devm_add_action_or_reset(dev, devm_counter_put, counter);
222         if (err < 0)
223                 return NULL;
224
225         return counter;
226 }
227 EXPORT_SYMBOL_NS_GPL(devm_counter_alloc, COUNTER);
228
229 /**
230  * devm_counter_add - complete registration of a counter
231  * @dev: the device to register the release callback for
232  * @counter: the counter to add
233  *
234  * This is the device managed version of counter_add(). It registers a cleanup
235  * callback to care for calling counter_unregister().
236  */
237 int devm_counter_add(struct device *dev,
238                      struct counter_device *const counter)
239 {
240         int err;
241
242         err = counter_add(counter);
243         if (err < 0)
244                 return err;
245
246         return devm_add_action_or_reset(dev, devm_counter_release, counter);
247 }
248 EXPORT_SYMBOL_NS_GPL(devm_counter_add, COUNTER);
249
250 #define COUNTER_DEV_MAX 256
251
252 static int __init counter_init(void)
253 {
254         int err;
255
256         err = bus_register(&counter_bus_type);
257         if (err < 0)
258                 return err;
259
260         err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX,
261                                   COUNTER_NAME);
262         if (err < 0)
263                 goto err_unregister_bus;
264
265         return 0;
266
267 err_unregister_bus:
268         bus_unregister(&counter_bus_type);
269         return err;
270 }
271
272 static void __exit counter_exit(void)
273 {
274         unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
275         bus_unregister(&counter_bus_type);
276 }
277
278 subsys_initcall(counter_init);
279 module_exit(counter_exit);
280
281 MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
282 MODULE_DESCRIPTION("Generic Counter interface");
283 MODULE_LICENSE("GPL v2");