aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/counter/counter-core.c
blob: 5acc54539623879b7d80900c54a75173a4627f69 (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * Generic Counter interface
 * Copyright (C) 2020 William Breathitt Gray
 */
#include <linux/cdev.h>
#include <linux/counter.h>
#include <linux/device.h>
#include <linux/device/bus.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/wait.h>

#include "counter-chrdev.h"
#include "counter-sysfs.h"

/* Provides a unique ID for each counter device */
static DEFINE_IDA(counter_ida);

static void counter_device_release(struct device *dev)
{
	struct counter_device *const counter = dev_get_drvdata(dev);

	counter_chrdev_remove(counter);
	ida_free(&counter_ida, dev->id);
}

static struct device_type counter_device_type = {
	.name = "counter_device",
	.release = counter_device_release,
};

static struct bus_type counter_bus_type = {
	.name = "counter",
	.dev_name = "counter",
};

static dev_t counter_devt;

/**
 * counter_register - register Counter to the system
 * @counter:	pointer to Counter to register
 *
 * This function registers a Counter to the system. A sysfs "counter" directory
 * will be created and populated with sysfs attributes correlating with the
 * Counter Signals, Synapses, and Counts respectively.
 *
 * RETURNS:
 * 0 on success, negative error number on failure.
 */
int counter_register(struct counter_device *const counter)
{
	struct device *const dev = &counter->dev;
	int id;
	int err;

	/* Acquire unique ID */
	id = ida_alloc(&counter_ida, GFP_KERNEL);
	if (id < 0)
		return id;

	mutex_init(&counter->ops_exist_lock);

	/* Configure device structure for Counter */
	dev->id = id;
	dev->type = &counter_device_type;
	dev->bus = &counter_bus_type;
	dev->devt = MKDEV(MAJOR(counter_devt), id);
	if (counter->parent) {
		dev->parent = counter->parent;
		dev->of_node = counter->parent->of_node;
	}
	device_initialize(dev);
	dev_set_drvdata(dev, counter);

	err = counter_sysfs_add(counter);
	if (err < 0)
		goto err_free_id;

	err = counter_chrdev_add(counter);
	if (err < 0)
		goto err_free_id;

	err = cdev_device_add(&counter->chrdev, dev);
	if (err < 0)
		goto err_remove_chrdev;

	return 0;

err_remove_chrdev:
	counter_chrdev_remove(counter);
err_free_id:
	put_device(dev);
	return err;
}
EXPORT_SYMBOL_GPL(counter_register);

/**
 * counter_unregister - unregister Counter from the system
 * @counter:	pointer to Counter to unregister
 *
 * The Counter is unregistered from the system.
 */
void counter_unregister(struct counter_device *const counter)
{
	if (!counter)
		return;

	cdev_device_del(&counter->chrdev, &counter->dev);

	mutex_lock(&counter->ops_exist_lock);

	counter->ops = NULL;
	wake_up(&counter->events_wait);

	mutex_unlock(&counter->ops_exist_lock);

	put_device(&counter->dev);
}
EXPORT_SYMBOL_GPL(counter_unregister);

static void devm_counter_release(void *counter)
{
	counter_unregister(counter);
}

/**
 * devm_counter_register - Resource-managed counter_register
 * @dev:	device to allocate counter_device for
 * @counter:	pointer to Counter to register
 *
 * Managed counter_register. The Counter registered with this function is
 * automatically unregistered on driver detach. This function calls
 * counter_register internally. Refer to that function for more information.
 *
 * RETURNS:
 * 0 on success, negative error number on failure.
 */
int devm_counter_register(struct device *dev,
			  struct counter_device *const counter)
{
	int err;

	err = counter_register(counter);
	if (err < 0)
		return err;

	return devm_add_action_or_reset(dev, devm_counter_release, counter);
}
EXPORT_SYMBOL_GPL(devm_counter_register);

#define COUNTER_DEV_MAX 256

static int __init counter_init(void)
{
	int err;

	err = bus_register(&counter_bus_type);
	if (err < 0)
		return err;

	err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, "counter");
	if (err < 0)
		goto err_unregister_bus;

	return 0;

err_unregister_bus:
	bus_unregister(&counter_bus_type);
	return err;
}

static void __exit counter_exit(void)
{
	unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
	bus_unregister(&counter_bus_type);
}

subsys_initcall(counter_init);
module_exit(counter_exit);

MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
MODULE_DESCRIPTION("Generic Counter interface");
MODULE_LICENSE("GPL v2");