aaec1a0f76
This is a reimplementation of the Generic Counter driver interface. There are no modifications to the Counter subsystem userspace interface, so existing userspace applications should continue to run seamlessly. The purpose of this patch is to internalize the sysfs interface code among the various counter drivers into a shared module. Counter drivers pass and take data natively (i.e. u8, u64, etc.) and the shared counter module handles the translation between the sysfs interface and the device drivers. This guarantees a standard userspace interface for all counter drivers, and helps generalize the Generic Counter driver ABI in order to support the Generic Counter chrdev interface (introduced in a subsequent patch) without significant changes to the existing counter drivers. Note, Counter device registration is the same as before: drivers populate a struct counter_device with components and callbacks, then pass the structure to the devm_counter_register function. However, what's different now is how the Counter subsystem code handles this registration internally. Whereas before callbacks would interact directly with sysfs data, this interaction is now abstracted and instead callbacks interact with native C data types. The counter_comp structure forms the basis for Counter extensions. The counter-sysfs.c file contains the code to parse through the counter_device structure and register the requested components and extensions. Attributes are created and populated based on type, with respective translation functions to handle the mapping between sysfs and the counter driver callbacks. The translation performed for each attribute is straightforward: the attribute type and data is parsed from the counter_attribute structure, the respective counter driver read/write callback is called, and sysfs I/O is handled before or after the driver read/write function is called. Cc: Jarkko Nikula <jarkko.nikula@linux.intel.com> Cc: Patrick Havelange <patrick.havelange@essensium.com> Cc: Kamel Bouhara <kamel.bouhara@bootlin.com> Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com> Cc: Alexandre Torgue <alexandre.torgue@st.com> Cc: Dan Carpenter <dan.carpenter@oracle.com> Acked-by: Syed Nayyar Waris <syednwaris@gmail.com> Reviewed-by: David Lechner <david@lechnology.com> Tested-by: David Lechner <david@lechnology.com> Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> Reviewed-by: Fabrice Gasnier <fabrice.gasnier@foss.st.com> # for stm32 Link: https://lore.kernel.org/r/c68b4a1ffb195c1a2f65e8dd5ad7b7c14e79c6ef.1630031207.git.vilhelm.gray@gmail.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
238 lines
5.8 KiB
C
238 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
|
|
*/
|
|
|
|
#include <linux/counter.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
|
|
#define INTERRUPT_CNT_NAME "interrupt-cnt"
|
|
|
|
struct interrupt_cnt_priv {
|
|
atomic_t count;
|
|
struct counter_device counter;
|
|
struct gpio_desc *gpio;
|
|
int irq;
|
|
bool enabled;
|
|
struct counter_signal signals;
|
|
struct counter_synapse synapses;
|
|
struct counter_count cnts;
|
|
};
|
|
|
|
static irqreturn_t interrupt_cnt_isr(int irq, void *dev_id)
|
|
{
|
|
struct interrupt_cnt_priv *priv = dev_id;
|
|
|
|
atomic_inc(&priv->count);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int interrupt_cnt_enable_read(struct counter_device *counter,
|
|
struct counter_count *count, u8 *enable)
|
|
{
|
|
struct interrupt_cnt_priv *priv = counter->priv;
|
|
|
|
*enable = priv->enabled;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int interrupt_cnt_enable_write(struct counter_device *counter,
|
|
struct counter_count *count, u8 enable)
|
|
{
|
|
struct interrupt_cnt_priv *priv = counter->priv;
|
|
|
|
if (priv->enabled == enable)
|
|
return 0;
|
|
|
|
if (enable) {
|
|
priv->enabled = true;
|
|
enable_irq(priv->irq);
|
|
} else {
|
|
disable_irq(priv->irq);
|
|
priv->enabled = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct counter_comp interrupt_cnt_ext[] = {
|
|
COUNTER_COMP_ENABLE(interrupt_cnt_enable_read,
|
|
interrupt_cnt_enable_write),
|
|
};
|
|
|
|
static const enum counter_synapse_action interrupt_cnt_synapse_actions[] = {
|
|
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
|
|
};
|
|
|
|
static int interrupt_cnt_action_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
struct counter_synapse *synapse,
|
|
enum counter_synapse_action *action)
|
|
{
|
|
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int interrupt_cnt_read(struct counter_device *counter,
|
|
struct counter_count *count, u64 *val)
|
|
{
|
|
struct interrupt_cnt_priv *priv = counter->priv;
|
|
|
|
*val = atomic_read(&priv->count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int interrupt_cnt_write(struct counter_device *counter,
|
|
struct counter_count *count, const u64 val)
|
|
{
|
|
struct interrupt_cnt_priv *priv = counter->priv;
|
|
|
|
if (val != (typeof(priv->count.counter))val)
|
|
return -ERANGE;
|
|
|
|
atomic_set(&priv->count, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const enum counter_function interrupt_cnt_functions[] = {
|
|
COUNTER_FUNCTION_INCREASE,
|
|
};
|
|
|
|
static int interrupt_cnt_function_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
enum counter_function *function)
|
|
{
|
|
*function = COUNTER_FUNCTION_INCREASE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int interrupt_cnt_signal_read(struct counter_device *counter,
|
|
struct counter_signal *signal,
|
|
enum counter_signal_level *level)
|
|
{
|
|
struct interrupt_cnt_priv *priv = counter->priv;
|
|
int ret;
|
|
|
|
if (!priv->gpio)
|
|
return -EINVAL;
|
|
|
|
ret = gpiod_get_value(priv->gpio);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*level = ret ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct counter_ops interrupt_cnt_ops = {
|
|
.action_read = interrupt_cnt_action_read,
|
|
.count_read = interrupt_cnt_read,
|
|
.count_write = interrupt_cnt_write,
|
|
.function_read = interrupt_cnt_function_read,
|
|
.signal_read = interrupt_cnt_signal_read,
|
|
};
|
|
|
|
static int interrupt_cnt_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct interrupt_cnt_priv *priv;
|
|
int ret;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->irq = platform_get_irq_optional(pdev, 0);
|
|
if (priv->irq == -ENXIO)
|
|
priv->irq = 0;
|
|
else if (priv->irq < 0)
|
|
return dev_err_probe(dev, priv->irq, "failed to get IRQ\n");
|
|
|
|
priv->gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
|
|
if (IS_ERR(priv->gpio))
|
|
return dev_err_probe(dev, PTR_ERR(priv->gpio), "failed to get GPIO\n");
|
|
|
|
if (!priv->irq && !priv->gpio) {
|
|
dev_err(dev, "IRQ and GPIO are not found. At least one source should be provided\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!priv->irq) {
|
|
int irq = gpiod_to_irq(priv->gpio);
|
|
|
|
if (irq < 0)
|
|
return dev_err_probe(dev, irq, "failed to get IRQ from GPIO\n");
|
|
|
|
priv->irq = irq;
|
|
}
|
|
|
|
priv->signals.name = devm_kasprintf(dev, GFP_KERNEL, "IRQ %d",
|
|
priv->irq);
|
|
if (!priv->signals.name)
|
|
return -ENOMEM;
|
|
|
|
priv->counter.signals = &priv->signals;
|
|
priv->counter.num_signals = 1;
|
|
|
|
priv->synapses.actions_list = interrupt_cnt_synapse_actions;
|
|
priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actions);
|
|
priv->synapses.signal = &priv->signals;
|
|
|
|
priv->cnts.name = "Channel 0 Count";
|
|
priv->cnts.functions_list = interrupt_cnt_functions;
|
|
priv->cnts.num_functions = ARRAY_SIZE(interrupt_cnt_functions);
|
|
priv->cnts.synapses = &priv->synapses;
|
|
priv->cnts.num_synapses = 1;
|
|
priv->cnts.ext = interrupt_cnt_ext;
|
|
priv->cnts.num_ext = ARRAY_SIZE(interrupt_cnt_ext);
|
|
|
|
priv->counter.priv = priv;
|
|
priv->counter.name = dev_name(dev);
|
|
priv->counter.parent = dev;
|
|
priv->counter.ops = &interrupt_cnt_ops;
|
|
priv->counter.counts = &priv->cnts;
|
|
priv->counter.num_counts = 1;
|
|
|
|
irq_set_status_flags(priv->irq, IRQ_NOAUTOEN);
|
|
ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr,
|
|
IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
|
|
dev_name(dev), priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return devm_counter_register(dev, &priv->counter);
|
|
}
|
|
|
|
static const struct of_device_id interrupt_cnt_of_match[] = {
|
|
{ .compatible = "interrupt-counter", },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match);
|
|
|
|
static struct platform_driver interrupt_cnt_driver = {
|
|
.probe = interrupt_cnt_probe,
|
|
.driver = {
|
|
.name = INTERRUPT_CNT_NAME,
|
|
.of_match_table = interrupt_cnt_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(interrupt_cnt_driver);
|
|
|
|
MODULE_ALIAS("platform:interrupt-counter");
|
|
MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
|
|
MODULE_DESCRIPTION("Interrupt counter driver");
|
|
MODULE_LICENSE("GPL v2");
|