1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Generic Counter interface
4 * Copyright (C) 2020 William Breathitt Gray
5 */
6 #include <linux/counter.h>
7 #include <linux/device.h>
8 #include <linux/export.h>
9 #include <linux/gfp.h>
10 #include <linux/idr.h>
11 #include <linux/init.h>
12 #include <linux/module.h>
13
14 #include "counter-sysfs.h"
15
16 /* Provides a unique ID for each counter device */
17 static DEFINE_IDA(counter_ida);
18
counter_device_release(struct device * dev)19 static void counter_device_release(struct device *dev)
20 {
21 ida_free(&counter_ida, dev->id);
22 }
23
24 static struct device_type counter_device_type = {
25 .name = "counter_device",
26 .release = counter_device_release,
27 };
28
29 static struct bus_type counter_bus_type = {
30 .name = "counter",
31 .dev_name = "counter",
32 };
33
34 /**
35 * counter_register - register Counter to the system
36 * @counter: pointer to Counter to register
37 *
38 * This function registers a Counter to the system. A sysfs "counter" directory
39 * will be created and populated with sysfs attributes correlating with the
40 * Counter Signals, Synapses, and Counts respectively.
41 */
counter_register(struct counter_device * const counter)42 int counter_register(struct counter_device *const counter)
43 {
44 struct device *const dev = &counter->dev;
45 int id;
46 int err;
47
48 /* Acquire unique ID */
49 id = ida_alloc(&counter_ida, GFP_KERNEL);
50 if (id < 0)
51 return id;
52
53 /* Configure device structure for Counter */
54 dev->id = id;
55 dev->type = &counter_device_type;
56 dev->bus = &counter_bus_type;
57 if (counter->parent) {
58 dev->parent = counter->parent;
59 dev->of_node = counter->parent->of_node;
60 }
61 device_initialize(dev);
62 dev_set_drvdata(dev, counter);
63
64 /* Add Counter sysfs attributes */
65 err = counter_sysfs_add(counter);
66 if (err < 0)
67 goto err_free_id;
68
69 /* Add device to system */
70 err = device_add(dev);
71 if (err < 0)
72 goto err_free_id;
73
74 return 0;
75
76 err_free_id:
77 put_device(dev);
78 return err;
79 }
80 EXPORT_SYMBOL_GPL(counter_register);
81
82 /**
83 * counter_unregister - unregister Counter from the system
84 * @counter: pointer to Counter to unregister
85 *
86 * The Counter is unregistered from the system.
87 */
counter_unregister(struct counter_device * const counter)88 void counter_unregister(struct counter_device *const counter)
89 {
90 if (!counter)
91 return;
92
93 device_unregister(&counter->dev);
94 }
95 EXPORT_SYMBOL_GPL(counter_unregister);
96
devm_counter_release(void * counter)97 static void devm_counter_release(void *counter)
98 {
99 counter_unregister(counter);
100 }
101
102 /**
103 * devm_counter_register - Resource-managed counter_register
104 * @dev: device to allocate counter_device for
105 * @counter: pointer to Counter to register
106 *
107 * Managed counter_register. The Counter registered with this function is
108 * automatically unregistered on driver detach. This function calls
109 * counter_register internally. Refer to that function for more information.
110 *
111 * RETURNS:
112 * 0 on success, negative error number on failure.
113 */
devm_counter_register(struct device * dev,struct counter_device * const counter)114 int devm_counter_register(struct device *dev,
115 struct counter_device *const counter)
116 {
117 int err;
118
119 err = counter_register(counter);
120 if (err < 0)
121 return err;
122
123 return devm_add_action_or_reset(dev, devm_counter_release, counter);
124 }
125 EXPORT_SYMBOL_GPL(devm_counter_register);
126
counter_init(void)127 static int __init counter_init(void)
128 {
129 return bus_register(&counter_bus_type);
130 }
131
counter_exit(void)132 static void __exit counter_exit(void)
133 {
134 bus_unregister(&counter_bus_type);
135 }
136
137 subsys_initcall(counter_init);
138 module_exit(counter_exit);
139
140 MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
141 MODULE_DESCRIPTION("Generic Counter interface");
142 MODULE_LICENSE("GPL v2");
143