162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic Counter interface
462306a36Sopenharmony_ci * Copyright (C) 2020 William Breathitt Gray
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/cdev.h>
762306a36Sopenharmony_ci#include <linux/counter.h>
862306a36Sopenharmony_ci#include <linux/device.h>
962306a36Sopenharmony_ci#include <linux/device/bus.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci#include <linux/fs.h>
1262306a36Sopenharmony_ci#include <linux/gfp.h>
1362306a36Sopenharmony_ci#include <linux/idr.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/kdev_t.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/mutex.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/types.h>
2062306a36Sopenharmony_ci#include <linux/wait.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "counter-chrdev.h"
2362306a36Sopenharmony_ci#include "counter-sysfs.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define COUNTER_NAME	"counter"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* Provides a unique ID for each counter device */
2862306a36Sopenharmony_cistatic DEFINE_IDA(counter_ida);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct counter_device_allochelper {
3162306a36Sopenharmony_ci	struct counter_device counter;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	/*
3462306a36Sopenharmony_ci	 * This is cache line aligned to ensure private data behaves like if it
3562306a36Sopenharmony_ci	 * were kmalloced separately.
3662306a36Sopenharmony_ci	 */
3762306a36Sopenharmony_ci	unsigned long privdata[] ____cacheline_aligned;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic void counter_device_release(struct device *dev)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct counter_device *const counter =
4362306a36Sopenharmony_ci		container_of(dev, struct counter_device, dev);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	counter_chrdev_remove(counter);
4662306a36Sopenharmony_ci	ida_free(&counter_ida, dev->id);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	kfree(container_of(counter, struct counter_device_allochelper, counter));
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic struct device_type counter_device_type = {
5262306a36Sopenharmony_ci	.name = "counter_device",
5362306a36Sopenharmony_ci	.release = counter_device_release,
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic struct bus_type counter_bus_type = {
5762306a36Sopenharmony_ci	.name = "counter",
5862306a36Sopenharmony_ci	.dev_name = "counter",
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic dev_t counter_devt;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/**
6462306a36Sopenharmony_ci * counter_priv - access counter device private data
6562306a36Sopenharmony_ci * @counter: counter device
6662306a36Sopenharmony_ci *
6762306a36Sopenharmony_ci * Get the counter device private data
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_civoid *counter_priv(const struct counter_device *const counter)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct counter_device_allochelper *ch =
7262306a36Sopenharmony_ci		container_of(counter, struct counter_device_allochelper, counter);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	return &ch->privdata;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_priv, COUNTER);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/**
7962306a36Sopenharmony_ci * counter_alloc - allocate a counter_device
8062306a36Sopenharmony_ci * @sizeof_priv: size of the driver private data
8162306a36Sopenharmony_ci *
8262306a36Sopenharmony_ci * This is part one of counter registration. The structure is allocated
8362306a36Sopenharmony_ci * dynamically to ensure the right lifetime for the embedded struct device.
8462306a36Sopenharmony_ci *
8562306a36Sopenharmony_ci * If this succeeds, call counter_put() to get rid of the counter_device again.
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_cistruct counter_device *counter_alloc(size_t sizeof_priv)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct counter_device_allochelper *ch;
9062306a36Sopenharmony_ci	struct counter_device *counter;
9162306a36Sopenharmony_ci	struct device *dev;
9262306a36Sopenharmony_ci	int err;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
9562306a36Sopenharmony_ci	if (!ch)
9662306a36Sopenharmony_ci		return NULL;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	counter = &ch->counter;
9962306a36Sopenharmony_ci	dev = &counter->dev;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* Acquire unique ID */
10262306a36Sopenharmony_ci	err = ida_alloc(&counter_ida, GFP_KERNEL);
10362306a36Sopenharmony_ci	if (err < 0)
10462306a36Sopenharmony_ci		goto err_ida_alloc;
10562306a36Sopenharmony_ci	dev->id = err;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	mutex_init(&counter->ops_exist_lock);
10862306a36Sopenharmony_ci	dev->type = &counter_device_type;
10962306a36Sopenharmony_ci	dev->bus = &counter_bus_type;
11062306a36Sopenharmony_ci	dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	err = counter_chrdev_add(counter);
11362306a36Sopenharmony_ci	if (err < 0)
11462306a36Sopenharmony_ci		goto err_chrdev_add;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	device_initialize(dev);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);
11962306a36Sopenharmony_ci	if (err)
12062306a36Sopenharmony_ci		goto err_dev_set_name;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return counter;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cierr_dev_set_name:
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	counter_chrdev_remove(counter);
12762306a36Sopenharmony_cierr_chrdev_add:
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	ida_free(&counter_ida, dev->id);
13062306a36Sopenharmony_cierr_ida_alloc:
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	kfree(ch);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return NULL;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_alloc, COUNTER);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_civoid counter_put(struct counter_device *counter)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	put_device(&counter->dev);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_put, COUNTER);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/**
14562306a36Sopenharmony_ci * counter_add - complete registration of a counter
14662306a36Sopenharmony_ci * @counter: the counter to add
14762306a36Sopenharmony_ci *
14862306a36Sopenharmony_ci * This is part two of counter registration.
14962306a36Sopenharmony_ci *
15062306a36Sopenharmony_ci * If this succeeds, call counter_unregister() to get rid of the counter_device again.
15162306a36Sopenharmony_ci */
15262306a36Sopenharmony_ciint counter_add(struct counter_device *counter)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	int err;
15562306a36Sopenharmony_ci	struct device *dev = &counter->dev;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (counter->parent) {
15862306a36Sopenharmony_ci		dev->parent = counter->parent;
15962306a36Sopenharmony_ci		dev->of_node = counter->parent->of_node;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	err = counter_sysfs_add(counter);
16362306a36Sopenharmony_ci	if (err < 0)
16462306a36Sopenharmony_ci		return err;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/* implies device_add(dev) */
16762306a36Sopenharmony_ci	return cdev_device_add(&counter->chrdev, dev);
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_add, COUNTER);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci/**
17262306a36Sopenharmony_ci * counter_unregister - unregister Counter from the system
17362306a36Sopenharmony_ci * @counter:	pointer to Counter to unregister
17462306a36Sopenharmony_ci *
17562306a36Sopenharmony_ci * The Counter is unregistered from the system.
17662306a36Sopenharmony_ci */
17762306a36Sopenharmony_civoid counter_unregister(struct counter_device *const counter)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	if (!counter)
18062306a36Sopenharmony_ci		return;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	cdev_device_del(&counter->chrdev, &counter->dev);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	mutex_lock(&counter->ops_exist_lock);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	counter->ops = NULL;
18762306a36Sopenharmony_ci	wake_up(&counter->events_wait);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	mutex_unlock(&counter->ops_exist_lock);
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(counter_unregister, COUNTER);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic void devm_counter_release(void *counter)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	counter_unregister(counter);
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic void devm_counter_put(void *counter)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	counter_put(counter);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci/**
20462306a36Sopenharmony_ci * devm_counter_alloc - allocate a counter_device
20562306a36Sopenharmony_ci * @dev: the device to register the release callback for
20662306a36Sopenharmony_ci * @sizeof_priv: size of the driver private data
20762306a36Sopenharmony_ci *
20862306a36Sopenharmony_ci * This is the device managed version of counter_add(). It registers a cleanup
20962306a36Sopenharmony_ci * callback to care for calling counter_put().
21062306a36Sopenharmony_ci */
21162306a36Sopenharmony_cistruct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct counter_device *counter;
21462306a36Sopenharmony_ci	int err;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	counter = counter_alloc(sizeof_priv);
21762306a36Sopenharmony_ci	if (!counter)
21862306a36Sopenharmony_ci		return NULL;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	err = devm_add_action_or_reset(dev, devm_counter_put, counter);
22162306a36Sopenharmony_ci	if (err < 0)
22262306a36Sopenharmony_ci		return NULL;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return counter;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_counter_alloc, COUNTER);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci/**
22962306a36Sopenharmony_ci * devm_counter_add - complete registration of a counter
23062306a36Sopenharmony_ci * @dev: the device to register the release callback for
23162306a36Sopenharmony_ci * @counter: the counter to add
23262306a36Sopenharmony_ci *
23362306a36Sopenharmony_ci * This is the device managed version of counter_add(). It registers a cleanup
23462306a36Sopenharmony_ci * callback to care for calling counter_unregister().
23562306a36Sopenharmony_ci */
23662306a36Sopenharmony_ciint devm_counter_add(struct device *dev,
23762306a36Sopenharmony_ci		     struct counter_device *const counter)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	int err;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	err = counter_add(counter);
24262306a36Sopenharmony_ci	if (err < 0)
24362306a36Sopenharmony_ci		return err;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return devm_add_action_or_reset(dev, devm_counter_release, counter);
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_counter_add, COUNTER);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci#define COUNTER_DEV_MAX 256
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic int __init counter_init(void)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	int err;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	err = bus_register(&counter_bus_type);
25662306a36Sopenharmony_ci	if (err < 0)
25762306a36Sopenharmony_ci		return err;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX,
26062306a36Sopenharmony_ci				  COUNTER_NAME);
26162306a36Sopenharmony_ci	if (err < 0)
26262306a36Sopenharmony_ci		goto err_unregister_bus;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_cierr_unregister_bus:
26762306a36Sopenharmony_ci	bus_unregister(&counter_bus_type);
26862306a36Sopenharmony_ci	return err;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic void __exit counter_exit(void)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
27462306a36Sopenharmony_ci	bus_unregister(&counter_bus_type);
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cisubsys_initcall(counter_init);
27862306a36Sopenharmony_cimodule_exit(counter_exit);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ciMODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
28162306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic Counter interface");
28262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
283