Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Counter devices
#
config I8254
tristate
select COUNTER
select REGMAP
help
Enables support for the i8254 interface library functions. The i8254
interface library provides functions to facilitate communication with
interfaces compatible with the venerable Intel 8254 Programmable
Interval Timer (PIT). The Intel 825x family of chips was first
released in the early 1980s but compatible interfaces are nowadays
typically found embedded in larger VLSI processing chips and FPGA
components.
If built as a module its name will be i8254.
menuconfig COUNTER
tristate "Counter support"
help
This enables counter device support through the Generic Counter
interface. You only need to enable this, if you also want to enable
one or more of the counter device drivers below.
if COUNTER
config 104_QUAD_8
tristate "ACCES 104-QUAD-8 driver"
depends on (PC104 && X86) || COMPILE_TEST
depends on HAS_IOPORT_MAP
select ISA_BUS_API
select REGMAP_MMIO
help
Say yes here to build support for the ACCES 104-QUAD-8 quadrature
encoder counter/interface device family (104-QUAD-8, 104-QUAD-4).
A counter's respective error flag may be cleared by performing a write
operation on the respective count value attribute. The 104-QUAD-8
counters may be set either directly or via the counter's preset
attribute.
The base port addresses for the devices may be configured via the base
array module parameter. The interrupt line numbers for the devices may
be configured via the irq array module parameter.
config FTM_QUADDEC
tristate "Flex Timer Module Quadrature decoder driver"
depends on SOC_LS1021A || COMPILE_TEST
depends on HAS_IOMEM && OF
help
Select this option to enable the Flex Timer Quadrature decoder
driver.
To compile this driver as a module, choose M here: the
module will be called ftm-quaddec.
config INTEL_QEP
tristate "Intel Quadrature Encoder Peripheral driver"
depends on X86
depends on PCI
help
Select this option to enable the Intel Quadrature Encoder Peripheral
driver.
To compile this driver as a module, choose M here: the module
will be called intel-qep.
config INTERRUPT_CNT
tristate "Interrupt counter driver"
depends on GPIOLIB
help
Select this option to enable interrupt counter driver. Any interrupt
source can be used by this driver as the event source.
To compile this driver as a module, choose M here: the
module will be called interrupt-cnt.
config MICROCHIP_TCB_CAPTURE
tristate "Microchip Timer Counter Capture driver"
depends on SOC_AT91SAM9 || SOC_SAM_V7 || COMPILE_TEST
depends on HAS_IOMEM && OF
select REGMAP_MMIO
help
Select this option to enable the Microchip Timer Counter Block
capture driver.
To compile this driver as a module, choose M here: the
module will be called microchip-tcb-capture.
config RZ_MTU3_CNT
tristate "Renesas RZ/G2L MTU3a counter driver"
depends on RZ_MTU3
help
Enable support for MTU3a counter driver found on Renesas RZ/G2L alike
SoCs. This IP supports both 16-bit and 32-bit phase counting mode
support.
To compile this driver as a module, choose M here: the
module will be called rz-mtu3-cnt.
config STM32_LPTIMER_CNT
tristate "STM32 LP Timer encoder counter driver"
depends on MFD_STM32_LPTIMER || COMPILE_TEST
help
Select this option to enable STM32 Low-Power Timer quadrature encoder
and counter driver.
To compile this driver as a module, choose M here: the
module will be called stm32-lptimer-cnt.
config STM32_TIMER_CNT
tristate "STM32 Timer encoder counter driver"
depends on MFD_STM32_TIMERS || COMPILE_TEST
help
Select this option to enable STM32 Timer quadrature encoder
and counter driver.
To compile this driver as a module, choose M here: the
module will be called stm32-timer-cnt.
config TI_ECAP_CAPTURE
tristate "TI eCAP capture driver"
depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
depends on HAS_IOMEM
select REGMAP_MMIO
help
Select this option to enable the Texas Instruments Enhanced Capture
(eCAP) driver in input mode.
It can be used to timestamp events (falling/rising edges) detected
on ECAP input signal.
To compile this driver as a module, choose M here: the module
will be called ti-ecap-capture.
config TI_EQEP
tristate "TI eQEP counter driver"
depends on SOC_AM33XX || ARCH_K3 || COMPILE_TEST
select REGMAP_MMIO
help
Select this option to enable the Texas Instruments Enhanced Quadrature
Encoder Pulse (eQEP) counter driver.
To compile this driver as a module, choose M here: the module will be
called ti-eqep.
endif # COUNTER
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for Counter devices
#
obj-$(CONFIG_COUNTER) += counter.o
counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
obj-$(CONFIG_I8254) += i8254.o
obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
obj-$(CONFIG_INTERRUPT_CNT) += interrupt-cnt.o
obj-$(CONFIG_RZ_MTU3_CNT) += rz-mtu3-cnt.o
obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o
obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o
obj-$(CONFIG_TI_EQEP) += ti-eqep.o
obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o
obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o
obj-$(CONFIG_INTEL_QEP) += intel-qep.o
obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o
@@ -0,0 +1,675 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Generic Counter character device interface
* Copyright (C) 2020 William Breathitt Gray
*/
#include <linux/cdev.h>
#include <linux/counter.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/kfifo.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/nospec.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/timekeeping.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include "counter-chrdev.h"
struct counter_comp_node {
struct list_head l;
struct counter_component component;
struct counter_comp comp;
void *parent;
};
#define counter_comp_read_is_equal(a, b) \
(a.action_read == b.action_read || \
a.device_u8_read == b.device_u8_read || \
a.count_u8_read == b.count_u8_read || \
a.signal_u8_read == b.signal_u8_read || \
a.device_u32_read == b.device_u32_read || \
a.count_u32_read == b.count_u32_read || \
a.signal_u32_read == b.signal_u32_read || \
a.device_u64_read == b.device_u64_read || \
a.count_u64_read == b.count_u64_read || \
a.signal_u64_read == b.signal_u64_read || \
a.signal_array_u32_read == b.signal_array_u32_read || \
a.device_array_u64_read == b.device_array_u64_read || \
a.count_array_u64_read == b.count_array_u64_read || \
a.signal_array_u64_read == b.signal_array_u64_read)
#define counter_comp_read_is_set(comp) \
(comp.action_read || \
comp.device_u8_read || \
comp.count_u8_read || \
comp.signal_u8_read || \
comp.device_u32_read || \
comp.count_u32_read || \
comp.signal_u32_read || \
comp.device_u64_read || \
comp.count_u64_read || \
comp.signal_u64_read || \
comp.signal_array_u32_read || \
comp.device_array_u64_read || \
comp.count_array_u64_read || \
comp.signal_array_u64_read)
static ssize_t counter_chrdev_read(struct file *filp, char __user *buf,
size_t len, loff_t *f_ps)
{
struct counter_device *const counter = filp->private_data;
int err;
unsigned int copied;
if (!counter->ops)
return -ENODEV;
if (len < sizeof(struct counter_event))
return -EINVAL;
do {
if (kfifo_is_empty(&counter->events)) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
err = wait_event_interruptible(counter->events_wait,
!kfifo_is_empty(&counter->events) ||
!counter->ops);
if (err < 0)
return err;
if (!counter->ops)
return -ENODEV;
}
if (mutex_lock_interruptible(&counter->events_out_lock))
return -ERESTARTSYS;
err = kfifo_to_user(&counter->events, buf, len, &copied);
mutex_unlock(&counter->events_out_lock);
if (err < 0)
return err;
} while (!copied);
return copied;
}
static __poll_t counter_chrdev_poll(struct file *filp,
struct poll_table_struct *pollt)
{
struct counter_device *const counter = filp->private_data;
__poll_t events = 0;
if (!counter->ops)
return events;
poll_wait(filp, &counter->events_wait, pollt);
if (!kfifo_is_empty(&counter->events))
events = EPOLLIN | EPOLLRDNORM;
return events;
}
static void counter_events_list_free(struct list_head *const events_list)
{
struct counter_event_node *p, *n;
struct counter_comp_node *q, *o;
list_for_each_entry_safe(p, n, events_list, l) {
/* Free associated component nodes */
list_for_each_entry_safe(q, o, &p->comp_list, l) {
list_del(&q->l);
kfree(q);
}
/* Free event node */
list_del(&p->l);
kfree(p);
}
}
static int counter_set_event_node(struct counter_device *const counter,
struct counter_watch *const watch,
const struct counter_comp_node *const cfg)
{
struct counter_event_node *event_node;
int err = 0;
struct counter_comp_node *comp_node;
/* Search for event in the list */
list_for_each_entry(event_node, &counter->next_events_list, l)
if (event_node->event == watch->event &&
event_node->channel == watch->channel)
break;
/* If event is not already in the list */
if (&event_node->l == &counter->next_events_list) {
/* Allocate new event node */
event_node = kmalloc_obj(*event_node);
if (!event_node)
return -ENOMEM;
/* Configure event node and add to the list */
event_node->event = watch->event;
event_node->channel = watch->channel;
INIT_LIST_HEAD(&event_node->comp_list);
list_add(&event_node->l, &counter->next_events_list);
}
/* Check if component watch has already been set before */
list_for_each_entry(comp_node, &event_node->comp_list, l)
if (comp_node->parent == cfg->parent &&
counter_comp_read_is_equal(comp_node->comp, cfg->comp)) {
err = -EINVAL;
goto exit_free_event_node;
}
/* Allocate component node */
comp_node = kmalloc_obj(*comp_node);
if (!comp_node) {
err = -ENOMEM;
goto exit_free_event_node;
}
*comp_node = *cfg;
/* Add component node to event node */
list_add_tail(&comp_node->l, &event_node->comp_list);
exit_free_event_node:
/* Free event node if no one else is watching */
if (list_empty(&event_node->comp_list)) {
list_del(&event_node->l);
kfree(event_node);
}
return err;
}
static int counter_enable_events(struct counter_device *const counter)
{
unsigned long flags;
int err = 0;
mutex_lock(&counter->n_events_list_lock);
spin_lock_irqsave(&counter->events_list_lock, flags);
counter_events_list_free(&counter->events_list);
list_replace_init(&counter->next_events_list,
&counter->events_list);
if (counter->ops->events_configure)
err = counter->ops->events_configure(counter);
spin_unlock_irqrestore(&counter->events_list_lock, flags);
mutex_unlock(&counter->n_events_list_lock);
return err;
}
static int counter_disable_events(struct counter_device *const counter)
{
unsigned long flags;
int err = 0;
spin_lock_irqsave(&counter->events_list_lock, flags);
counter_events_list_free(&counter->events_list);
if (counter->ops->events_configure)
err = counter->ops->events_configure(counter);
spin_unlock_irqrestore(&counter->events_list_lock, flags);
mutex_lock(&counter->n_events_list_lock);
counter_events_list_free(&counter->next_events_list);
mutex_unlock(&counter->n_events_list_lock);
return err;
}
static int counter_get_ext(const struct counter_comp *const ext,
const size_t num_ext, const size_t component_id,
size_t *const ext_idx, size_t *const id)
{
struct counter_array *element;
*id = 0;
for (*ext_idx = 0; *ext_idx < num_ext; (*ext_idx)++) {
if (*id == component_id)
return 0;
if (ext[*ext_idx].type == COUNTER_COMP_ARRAY) {
element = ext[*ext_idx].priv;
if (component_id - *id < element->length)
return 0;
*id += element->length;
} else
(*id)++;
}
return -EINVAL;
}
static int counter_add_watch(struct counter_device *const counter,
const unsigned long arg)
{
void __user *const uwatch = (void __user *)arg;
struct counter_watch watch;
struct counter_comp_node comp_node = {};
size_t parent, id;
struct counter_comp *ext;
size_t num_ext;
size_t ext_idx, ext_id;
int err = 0;
if (copy_from_user(&watch, uwatch, sizeof(watch)))
return -EFAULT;
if (watch.component.type == COUNTER_COMPONENT_NONE)
goto no_component;
parent = watch.component.parent;
/* Configure parent component info for comp node */
switch (watch.component.scope) {
case COUNTER_SCOPE_DEVICE:
ext = counter->ext;
num_ext = counter->num_ext;
break;
case COUNTER_SCOPE_SIGNAL:
if (parent >= counter->num_signals)
return -EINVAL;
parent = array_index_nospec(parent, counter->num_signals);
comp_node.parent = counter->signals + parent;
ext = counter->signals[parent].ext;
num_ext = counter->signals[parent].num_ext;
break;
case COUNTER_SCOPE_COUNT:
if (parent >= counter->num_counts)
return -EINVAL;
parent = array_index_nospec(parent, counter->num_counts);
comp_node.parent = counter->counts + parent;
ext = counter->counts[parent].ext;
num_ext = counter->counts[parent].num_ext;
break;
default:
return -EINVAL;
}
id = watch.component.id;
/* Configure component info for comp node */
switch (watch.component.type) {
case COUNTER_COMPONENT_SIGNAL:
if (watch.component.scope != COUNTER_SCOPE_SIGNAL)
return -EINVAL;
comp_node.comp.type = COUNTER_COMP_SIGNAL_LEVEL;
comp_node.comp.signal_u32_read = counter->ops->signal_read;
break;
case COUNTER_COMPONENT_COUNT:
if (watch.component.scope != COUNTER_SCOPE_COUNT)
return -EINVAL;
comp_node.comp.type = COUNTER_COMP_U64;
comp_node.comp.count_u64_read = counter->ops->count_read;
break;
case COUNTER_COMPONENT_FUNCTION:
if (watch.component.scope != COUNTER_SCOPE_COUNT)
return -EINVAL;
comp_node.comp.type = COUNTER_COMP_FUNCTION;
comp_node.comp.count_u32_read = counter->ops->function_read;
break;
case COUNTER_COMPONENT_SYNAPSE_ACTION:
if (watch.component.scope != COUNTER_SCOPE_COUNT)
return -EINVAL;
if (id >= counter->counts[parent].num_synapses)
return -EINVAL;
id = array_index_nospec(id, counter->counts[parent].num_synapses);
comp_node.comp.type = COUNTER_COMP_SYNAPSE_ACTION;
comp_node.comp.action_read = counter->ops->action_read;
comp_node.comp.priv = counter->counts[parent].synapses + id;
break;
case COUNTER_COMPONENT_EXTENSION:
err = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
if (err < 0)
return err;
comp_node.comp = ext[ext_idx];
break;
default:
return -EINVAL;
}
if (!counter_comp_read_is_set(comp_node.comp))
return -EOPNOTSUPP;
no_component:
mutex_lock(&counter->n_events_list_lock);
if (counter->ops->watch_validate) {
err = counter->ops->watch_validate(counter, &watch);
if (err < 0)
goto err_exit;
}
comp_node.component = watch.component;
err = counter_set_event_node(counter, &watch, &comp_node);
err_exit:
mutex_unlock(&counter->n_events_list_lock);
return err;
}
static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct counter_device *const counter = filp->private_data;
int ret = -ENODEV;
mutex_lock(&counter->ops_exist_lock);
if (!counter->ops)
goto out_unlock;
switch (cmd) {
case COUNTER_ADD_WATCH_IOCTL:
ret = counter_add_watch(counter, arg);
break;
case COUNTER_ENABLE_EVENTS_IOCTL:
ret = counter_enable_events(counter);
break;
case COUNTER_DISABLE_EVENTS_IOCTL:
ret = counter_disable_events(counter);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
out_unlock:
mutex_unlock(&counter->ops_exist_lock);
return ret;
}
static int counter_chrdev_open(struct inode *inode, struct file *filp)
{
struct counter_device *const counter = container_of(inode->i_cdev,
typeof(*counter),
chrdev);
get_device(&counter->dev);
filp->private_data = counter;
return nonseekable_open(inode, filp);
}
static int counter_chrdev_release(struct inode *inode, struct file *filp)
{
struct counter_device *const counter = filp->private_data;
int ret = 0;
mutex_lock(&counter->ops_exist_lock);
if (!counter->ops) {
/* Free any lingering held memory */
counter_events_list_free(&counter->events_list);
counter_events_list_free(&counter->next_events_list);
ret = -ENODEV;
goto out_unlock;
}
ret = counter_disable_events(counter);
if (ret < 0) {
mutex_unlock(&counter->ops_exist_lock);
return ret;
}
out_unlock:
mutex_unlock(&counter->ops_exist_lock);
put_device(&counter->dev);
return ret;
}
static const struct file_operations counter_fops = {
.owner = THIS_MODULE,
.read = counter_chrdev_read,
.poll = counter_chrdev_poll,
.unlocked_ioctl = counter_chrdev_ioctl,
.open = counter_chrdev_open,
.release = counter_chrdev_release,
};
int counter_chrdev_add(struct counter_device *const counter)
{
/* Initialize Counter events lists */
INIT_LIST_HEAD(&counter->events_list);
INIT_LIST_HEAD(&counter->next_events_list);
spin_lock_init(&counter->events_list_lock);
mutex_init(&counter->n_events_list_lock);
init_waitqueue_head(&counter->events_wait);
spin_lock_init(&counter->events_in_lock);
mutex_init(&counter->events_out_lock);
/* Initialize character device */
cdev_init(&counter->chrdev, &counter_fops);
/* Allocate Counter events queue */
return kfifo_alloc(&counter->events, 64, GFP_KERNEL);
}
void counter_chrdev_remove(struct counter_device *const counter)
{
kfifo_free(&counter->events);
}
static int counter_get_array_data(struct counter_device *const counter,
const enum counter_scope scope,
void *const parent,
const struct counter_comp *const comp,
const size_t idx, u64 *const value)
{
const struct counter_array *const element = comp->priv;
u32 value_u32 = 0;
int ret;
switch (element->type) {
case COUNTER_COMP_SIGNAL_POLARITY:
if (scope != COUNTER_SCOPE_SIGNAL)
return -EINVAL;
ret = comp->signal_array_u32_read(counter, parent, idx,
&value_u32);
*value = value_u32;
return ret;
case COUNTER_COMP_U64:
switch (scope) {
case COUNTER_SCOPE_DEVICE:
return comp->device_array_u64_read(counter, idx, value);
case COUNTER_SCOPE_SIGNAL:
return comp->signal_array_u64_read(counter, parent, idx,
value);
case COUNTER_SCOPE_COUNT:
return comp->count_array_u64_read(counter, parent, idx,
value);
default:
return -EINVAL;
}
default:
return -EINVAL;
}
}
static int counter_get_data(struct counter_device *const counter,
const struct counter_comp_node *const comp_node,
u64 *const value)
{
const struct counter_comp *const comp = &comp_node->comp;
const enum counter_scope scope = comp_node->component.scope;
const size_t id = comp_node->component.id;
struct counter_signal *const signal = comp_node->parent;
struct counter_count *const count = comp_node->parent;
u8 value_u8 = 0;
u32 value_u32 = 0;
const struct counter_comp *ext;
size_t num_ext;
size_t ext_idx, ext_id;
int ret;
if (comp_node->component.type == COUNTER_COMPONENT_NONE)
return 0;
switch (comp->type) {
case COUNTER_COMP_U8:
case COUNTER_COMP_BOOL:
switch (scope) {
case COUNTER_SCOPE_DEVICE:
ret = comp->device_u8_read(counter, &value_u8);
break;
case COUNTER_SCOPE_SIGNAL:
ret = comp->signal_u8_read(counter, signal, &value_u8);
break;
case COUNTER_SCOPE_COUNT:
ret = comp->count_u8_read(counter, count, &value_u8);
break;
default:
return -EINVAL;
}
*value = value_u8;
return ret;
case COUNTER_COMP_SIGNAL_LEVEL:
case COUNTER_COMP_FUNCTION:
case COUNTER_COMP_ENUM:
case COUNTER_COMP_COUNT_DIRECTION:
case COUNTER_COMP_COUNT_MODE:
case COUNTER_COMP_SIGNAL_POLARITY:
switch (scope) {
case COUNTER_SCOPE_DEVICE:
ret = comp->device_u32_read(counter, &value_u32);
break;
case COUNTER_SCOPE_SIGNAL:
ret = comp->signal_u32_read(counter, signal,
&value_u32);
break;
case COUNTER_SCOPE_COUNT:
ret = comp->count_u32_read(counter, count, &value_u32);
break;
default:
return -EINVAL;
}
*value = value_u32;
return ret;
case COUNTER_COMP_U64:
switch (scope) {
case COUNTER_SCOPE_DEVICE:
return comp->device_u64_read(counter, value);
case COUNTER_SCOPE_SIGNAL:
return comp->signal_u64_read(counter, signal, value);
case COUNTER_SCOPE_COUNT:
return comp->count_u64_read(counter, count, value);
default:
return -EINVAL;
}
case COUNTER_COMP_SYNAPSE_ACTION:
ret = comp->action_read(counter, count, comp->priv, &value_u32);
*value = value_u32;
return ret;
case COUNTER_COMP_ARRAY:
switch (scope) {
case COUNTER_SCOPE_DEVICE:
ext = counter->ext;
num_ext = counter->num_ext;
break;
case COUNTER_SCOPE_SIGNAL:
ext = signal->ext;
num_ext = signal->num_ext;
break;
case COUNTER_SCOPE_COUNT:
ext = count->ext;
num_ext = count->num_ext;
break;
default:
return -EINVAL;
}
ret = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
if (ret < 0)
return ret;
return counter_get_array_data(counter, scope, comp_node->parent,
comp, id - ext_id, value);
default:
return -EINVAL;
}
}
/**
* counter_push_event - queue event for userspace reading
* @counter: pointer to Counter structure
* @event: triggered event
* @channel: event channel
*
* Note: If no one is watching for the respective event, it is silently
* discarded.
*/
void counter_push_event(struct counter_device *const counter, const u8 event,
const u8 channel)
{
struct counter_event ev;
unsigned int copied = 0;
unsigned long flags;
struct counter_event_node *event_node;
struct counter_comp_node *comp_node;
ev.timestamp = ktime_get_ns();
ev.watch.event = event;
ev.watch.channel = channel;
/* Could be in an interrupt context, so use a spin lock */
spin_lock_irqsave(&counter->events_list_lock, flags);
/* Search for event in the list */
list_for_each_entry(event_node, &counter->events_list, l)
if (event_node->event == event &&
event_node->channel == channel)
break;
/* If event is not in the list */
if (&event_node->l == &counter->events_list)
goto exit_early;
/* Read and queue relevant comp for userspace */
list_for_each_entry(comp_node, &event_node->comp_list, l) {
ev.watch.component = comp_node->component;
ev.status = -counter_get_data(counter, comp_node, &ev.value);
copied += kfifo_in_spinlocked_noirqsave(&counter->events, &ev,
1, &counter->events_in_lock);
}
exit_early:
spin_unlock_irqrestore(&counter->events_list_lock, flags);
if (copied)
wake_up_poll(&counter->events_wait, EPOLLIN);
}
EXPORT_SYMBOL_NS_GPL(counter_push_event, "COUNTER");
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Counter character device interface
* Copyright (C) 2020 William Breathitt Gray
*/
#ifndef _COUNTER_CHRDEV_H_
#define _COUNTER_CHRDEV_H_
#include <linux/counter.h>
int counter_chrdev_add(struct counter_device *const counter);
void counter_chrdev_remove(struct counter_device *const counter);
#endif /* _COUNTER_CHRDEV_H_ */
+284
View File
@@ -0,0 +1,284 @@
// 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/slab.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "counter-chrdev.h"
#include "counter-sysfs.h"
#define COUNTER_NAME "counter"
/* Provides a unique ID for each counter device */
static DEFINE_IDA(counter_ida);
struct counter_device_allochelper {
struct counter_device counter;
/*
* This ensures private data behaves like if it were kmalloced
* separately. Also ensures the minimum alignment for safe DMA
* operations (which may or may not mean cache alignment).
*/
unsigned long privdata[] __aligned(ARCH_DMA_MINALIGN);
};
static void counter_device_release(struct device *dev)
{
struct counter_device *const counter =
container_of(dev, struct counter_device, dev);
counter_chrdev_remove(counter);
ida_free(&counter_ida, dev->id);
kfree(container_of(counter, struct counter_device_allochelper, counter));
}
static const struct device_type counter_device_type = {
.name = "counter_device",
.release = counter_device_release,
};
static const struct bus_type counter_bus_type = {
.name = "counter",
.dev_name = "counter",
};
static dev_t counter_devt;
/**
* counter_priv - access counter device private data
* @counter: counter device
*
* Get the counter device private data
*/
void *counter_priv(const struct counter_device *const counter)
{
struct counter_device_allochelper *ch =
container_of(counter, struct counter_device_allochelper, counter);
return &ch->privdata;
}
EXPORT_SYMBOL_NS_GPL(counter_priv, "COUNTER");
/**
* counter_alloc - allocate a counter_device
* @sizeof_priv: size of the driver private data
*
* This is part one of counter registration. The structure is allocated
* dynamically to ensure the right lifetime for the embedded struct device.
*
* If this succeeds, call counter_put() to get rid of the counter_device again.
*/
struct counter_device *counter_alloc(size_t sizeof_priv)
{
struct counter_device_allochelper *ch;
struct counter_device *counter;
struct device *dev;
int err;
ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
if (!ch)
return NULL;
counter = &ch->counter;
dev = &counter->dev;
/* Acquire unique ID */
err = ida_alloc(&counter_ida, GFP_KERNEL);
if (err < 0)
goto err_ida_alloc;
dev->id = err;
mutex_init(&counter->ops_exist_lock);
dev->type = &counter_device_type;
dev->bus = &counter_bus_type;
dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
err = counter_chrdev_add(counter);
if (err < 0)
goto err_chrdev_add;
device_initialize(dev);
err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);
if (err)
goto err_dev_set_name;
return counter;
err_dev_set_name:
put_device(dev);
return NULL;
err_chrdev_add:
ida_free(&counter_ida, dev->id);
err_ida_alloc:
kfree(ch);
return NULL;
}
EXPORT_SYMBOL_NS_GPL(counter_alloc, "COUNTER");
void counter_put(struct counter_device *counter)
{
put_device(&counter->dev);
}
EXPORT_SYMBOL_NS_GPL(counter_put, "COUNTER");
/**
* counter_add - complete registration of a counter
* @counter: the counter to add
*
* This is part two of counter registration.
*
* If this succeeds, call counter_unregister() to get rid of the counter_device again.
*/
int counter_add(struct counter_device *counter)
{
int err;
struct device *dev = &counter->dev;
if (counter->parent) {
dev->parent = counter->parent;
dev->of_node = counter->parent->of_node;
}
err = counter_sysfs_add(counter);
if (err < 0)
return err;
/* implies device_add(dev) */
return cdev_device_add(&counter->chrdev, dev);
}
EXPORT_SYMBOL_NS_GPL(counter_add, "COUNTER");
/**
* 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);
}
EXPORT_SYMBOL_NS_GPL(counter_unregister, "COUNTER");
static void devm_counter_release(void *counter)
{
counter_unregister(counter);
}
static void devm_counter_put(void *counter)
{
counter_put(counter);
}
/**
* devm_counter_alloc - allocate a counter_device
* @dev: the device to register the release callback for
* @sizeof_priv: size of the driver private data
*
* This is the device managed version of counter_add(). It registers a cleanup
* callback to care for calling counter_put().
*/
struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
{
struct counter_device *counter;
int err;
counter = counter_alloc(sizeof_priv);
if (!counter)
return NULL;
err = devm_add_action_or_reset(dev, devm_counter_put, counter);
if (err < 0)
return NULL;
return counter;
}
EXPORT_SYMBOL_NS_GPL(devm_counter_alloc, "COUNTER");
/**
* devm_counter_add - complete registration of a counter
* @dev: the device to register the release callback for
* @counter: the counter to add
*
* This is the device managed version of counter_add(). It registers a cleanup
* callback to care for calling counter_unregister().
*/
int devm_counter_add(struct device *dev,
struct counter_device *const counter)
{
int err;
err = counter_add(counter);
if (err < 0)
return err;
return devm_add_action_or_reset(dev, devm_counter_release, counter);
}
EXPORT_SYMBOL_NS_GPL(devm_counter_add, "COUNTER");
#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_NAME);
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");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Counter sysfs interface
* Copyright (C) 2020 William Breathitt Gray
*/
#ifndef _COUNTER_SYSFS_H_
#define _COUNTER_SYSFS_H_
#include <linux/counter.h>
int counter_sysfs_add(struct counter_device *const counter);
#endif /* _COUNTER_SYSFS_H_ */
+332
View File
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Flex Timer Module Quadrature decoder
*
* This module implements a driver for decoding the FTM quadrature
* of ex. a LS1021A
*/
#include <linux/fsl/ftm.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/counter.h>
#include <linux/bitfield.h>
#include <linux/types.h>
#define FTM_FIELD_UPDATE(ftm, offset, mask, val) \
({ \
uint32_t flags; \
ftm_read(ftm, offset, &flags); \
flags &= ~mask; \
flags |= FIELD_PREP(mask, val); \
ftm_write(ftm, offset, flags); \
})
struct ftm_quaddec {
struct platform_device *pdev;
void __iomem *ftm_base;
bool big_endian;
struct mutex ftm_quaddec_mutex;
};
static void ftm_read(struct ftm_quaddec *ftm, uint32_t offset, uint32_t *data)
{
if (ftm->big_endian)
*data = ioread32be(ftm->ftm_base + offset);
else
*data = ioread32(ftm->ftm_base + offset);
}
static void ftm_write(struct ftm_quaddec *ftm, uint32_t offset, uint32_t data)
{
if (ftm->big_endian)
iowrite32be(data, ftm->ftm_base + offset);
else
iowrite32(data, ftm->ftm_base + offset);
}
/* Hold mutex before modifying write protection state */
static void ftm_clear_write_protection(struct ftm_quaddec *ftm)
{
uint32_t flag;
/* First see if it is enabled */
ftm_read(ftm, FTM_FMS, &flag);
if (flag & FTM_FMS_WPEN)
FTM_FIELD_UPDATE(ftm, FTM_MODE, FTM_MODE_WPDIS, 1);
}
static void ftm_set_write_protection(struct ftm_quaddec *ftm)
{
FTM_FIELD_UPDATE(ftm, FTM_FMS, FTM_FMS_WPEN, 1);
}
static void ftm_reset_counter(struct ftm_quaddec *ftm)
{
/* Reset hardware counter to CNTIN */
ftm_write(ftm, FTM_CNT, 0x0);
}
static void ftm_quaddec_init(struct ftm_quaddec *ftm)
{
ftm_clear_write_protection(ftm);
/*
* Do not write in the region from the CNTIN register through the
* PWMLOAD register when FTMEN = 0.
* Also reset other fields to zero
*/
ftm_write(ftm, FTM_MODE, FTM_MODE_FTMEN);
ftm_write(ftm, FTM_CNTIN, 0x0000);
ftm_write(ftm, FTM_MOD, 0xffff);
ftm_write(ftm, FTM_CNT, 0x0);
/* Set prescaler, reset other fields to zero */
ftm_write(ftm, FTM_SC, FTM_SC_PS_1);
/* Select quad mode, reset other fields to zero */
ftm_write(ftm, FTM_QDCTRL, FTM_QDCTRL_QUADEN);
/* Unused features and reset to default section */
ftm_write(ftm, FTM_POL, 0x0);
ftm_write(ftm, FTM_FLTCTRL, 0x0);
ftm_write(ftm, FTM_SYNCONF, 0x0);
ftm_write(ftm, FTM_SYNC, 0xffff);
/* Lock the FTM */
ftm_set_write_protection(ftm);
}
static void ftm_quaddec_disable(void *ftm)
{
struct ftm_quaddec *ftm_qua = ftm;
ftm_clear_write_protection(ftm_qua);
ftm_write(ftm_qua, FTM_MODE, 0);
ftm_write(ftm_qua, FTM_QDCTRL, 0);
/*
* This is enough to disable the counter. No clock has been
* selected by writing to FTM_SC in init()
*/
ftm_set_write_protection(ftm_qua);
}
static int ftm_quaddec_get_prescaler(struct counter_device *counter,
struct counter_count *count, u32 *cnt_mode)
{
struct ftm_quaddec *ftm = counter_priv(counter);
uint32_t scflags;
ftm_read(ftm, FTM_SC, &scflags);
*cnt_mode = FIELD_GET(FTM_SC_PS_MASK, scflags);
return 0;
}
static int ftm_quaddec_set_prescaler(struct counter_device *counter,
struct counter_count *count, u32 cnt_mode)
{
struct ftm_quaddec *ftm = counter_priv(counter);
mutex_lock(&ftm->ftm_quaddec_mutex);
ftm_clear_write_protection(ftm);
FTM_FIELD_UPDATE(ftm, FTM_SC, FTM_SC_PS_MASK, cnt_mode);
ftm_set_write_protection(ftm);
/* Also resets the counter as it is undefined anyway now */
ftm_reset_counter(ftm);
mutex_unlock(&ftm->ftm_quaddec_mutex);
return 0;
}
static const char * const ftm_quaddec_prescaler[] = {
"1", "2", "4", "8", "16", "32", "64", "128"
};
static const enum counter_synapse_action ftm_quaddec_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_BOTH_EDGES
};
static const enum counter_function ftm_quaddec_count_functions[] = {
COUNTER_FUNCTION_QUADRATURE_X4
};
static int ftm_quaddec_count_read(struct counter_device *counter,
struct counter_count *count,
u64 *val)
{
struct ftm_quaddec *const ftm = counter_priv(counter);
uint32_t cntval;
ftm_read(ftm, FTM_CNT, &cntval);
*val = cntval;
return 0;
}
static int ftm_quaddec_count_write(struct counter_device *counter,
struct counter_count *count,
const u64 val)
{
struct ftm_quaddec *const ftm = counter_priv(counter);
if (val != 0) {
dev_warn(&ftm->pdev->dev, "Can only accept '0' as new counter value\n");
return -EINVAL;
}
ftm_reset_counter(ftm);
return 0;
}
static int ftm_quaddec_count_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
*function = COUNTER_FUNCTION_QUADRATURE_X4;
return 0;
}
static int ftm_quaddec_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
}
static const struct counter_ops ftm_quaddec_cnt_ops = {
.count_read = ftm_quaddec_count_read,
.count_write = ftm_quaddec_count_write,
.function_read = ftm_quaddec_count_function_read,
.action_read = ftm_quaddec_action_read,
};
static struct counter_signal ftm_quaddec_signals[] = {
{
.id = 0,
.name = "Channel 1 Phase A"
},
{
.id = 1,
.name = "Channel 1 Phase B"
}
};
static struct counter_synapse ftm_quaddec_count_synapses[] = {
{
.actions_list = ftm_quaddec_synapse_actions,
.num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions),
.signal = &ftm_quaddec_signals[0]
},
{
.actions_list = ftm_quaddec_synapse_actions,
.num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions),
.signal = &ftm_quaddec_signals[1]
}
};
static DEFINE_COUNTER_ENUM(ftm_quaddec_prescaler_enum, ftm_quaddec_prescaler);
static struct counter_comp ftm_quaddec_count_ext[] = {
COUNTER_COMP_COUNT_ENUM("prescaler", ftm_quaddec_get_prescaler,
ftm_quaddec_set_prescaler,
ftm_quaddec_prescaler_enum),
};
static struct counter_count ftm_quaddec_counts = {
.id = 0,
.name = "Channel 1 Count",
.functions_list = ftm_quaddec_count_functions,
.num_functions = ARRAY_SIZE(ftm_quaddec_count_functions),
.synapses = ftm_quaddec_count_synapses,
.num_synapses = ARRAY_SIZE(ftm_quaddec_count_synapses),
.ext = ftm_quaddec_count_ext,
.num_ext = ARRAY_SIZE(ftm_quaddec_count_ext)
};
static int ftm_quaddec_probe(struct platform_device *pdev)
{
struct counter_device *counter;
struct ftm_quaddec *ftm;
struct device_node *node = pdev->dev.of_node;
struct resource *io;
int ret;
counter = devm_counter_alloc(&pdev->dev, sizeof(*ftm));
if (!counter)
return -ENOMEM;
ftm = counter_priv(counter);
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!io) {
dev_err(&pdev->dev, "Failed to get memory region\n");
return -ENODEV;
}
ftm->pdev = pdev;
ftm->big_endian = of_property_read_bool(node, "big-endian");
ftm->ftm_base = devm_ioremap(&pdev->dev, io->start, resource_size(io));
if (!ftm->ftm_base) {
dev_err(&pdev->dev, "Failed to map memory region\n");
return -EINVAL;
}
counter->name = dev_name(&pdev->dev);
counter->parent = &pdev->dev;
counter->ops = &ftm_quaddec_cnt_ops;
counter->counts = &ftm_quaddec_counts;
counter->num_counts = 1;
counter->signals = ftm_quaddec_signals;
counter->num_signals = ARRAY_SIZE(ftm_quaddec_signals);
ret = devm_mutex_init(&pdev->dev, &ftm->ftm_quaddec_mutex);
if (ret)
return ret;
ftm_quaddec_init(ftm);
ret = devm_add_action_or_reset(&pdev->dev, ftm_quaddec_disable, ftm);
if (ret)
return ret;
ret = devm_counter_add(&pdev->dev, counter);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
return 0;
}
static const struct of_device_id ftm_quaddec_match[] = {
{ .compatible = "fsl,ftm-quaddec" },
{},
};
MODULE_DEVICE_TABLE(of, ftm_quaddec_match);
static struct platform_driver ftm_quaddec_driver = {
.driver = {
.name = "ftm-quaddec",
.of_match_table = ftm_quaddec_match,
},
.probe = ftm_quaddec_probe,
};
module_platform_driver(ftm_quaddec_driver);
MODULE_DESCRIPTION("Flex Timer Module Quadrature decoder");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kjeld Flarup <kfa@deif.com>");
MODULE_AUTHOR("Patrick Havelange <patrick.havelange@essensium.com>");
MODULE_IMPORT_NS("COUNTER");
+447
View File
@@ -0,0 +1,447 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel 8254 Programmable Interval Timer
* Copyright (C) William Breathitt Gray
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/counter.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/i8254.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/unaligned.h>
#define I8254_COUNTER_REG(_counter) (_counter)
#define I8254_CONTROL_REG 0x3
#define I8254_SC GENMASK(7, 6)
#define I8254_RW GENMASK(5, 4)
#define I8254_M GENMASK(3, 1)
#define I8254_CONTROL(_sc, _rw, _m) \
(u8_encode_bits(_sc, I8254_SC) | u8_encode_bits(_rw, I8254_RW) | \
u8_encode_bits(_m, I8254_M))
#define I8254_RW_TWO_BYTE 0x3
#define I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT 0
#define I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT 1
#define I8254_MODE_RATE_GENERATOR 2
#define I8254_MODE_SQUARE_WAVE_MODE 3
#define I8254_MODE_SOFTWARE_TRIGGERED_STROBE 4
#define I8254_MODE_HARDWARE_TRIGGERED_STROBE 5
#define I8254_COUNTER_LATCH(_counter) I8254_CONTROL(_counter, 0x0, 0x0)
#define I8254_PROGRAM_COUNTER(_counter, _mode) I8254_CONTROL(_counter, I8254_RW_TWO_BYTE, _mode)
#define I8254_NUM_COUNTERS 3
/**
* struct i8254 - I8254 device private data structure
* @lock: synchronization lock to prevent I/O race conditions
* @preset: array of Counter Register states
* @out_mode: array of mode configuration states
* @map: Regmap for the device
*/
struct i8254 {
struct mutex lock;
u16 preset[I8254_NUM_COUNTERS];
u8 out_mode[I8254_NUM_COUNTERS];
struct regmap *map;
};
static int i8254_count_read(struct counter_device *const counter, struct counter_count *const count,
u64 *const val)
{
struct i8254 *const priv = counter_priv(counter);
int ret;
u8 value[2];
mutex_lock(&priv->lock);
ret = regmap_write(priv->map, I8254_CONTROL_REG, I8254_COUNTER_LATCH(count->id));
if (ret) {
mutex_unlock(&priv->lock);
return ret;
}
ret = regmap_noinc_read(priv->map, I8254_COUNTER_REG(count->id), value, sizeof(value));
if (ret) {
mutex_unlock(&priv->lock);
return ret;
}
mutex_unlock(&priv->lock);
*val = get_unaligned_le16(value);
return ret;
}
static int i8254_function_read(struct counter_device *const counter,
struct counter_count *const count,
enum counter_function *const function)
{
*function = COUNTER_FUNCTION_DECREASE;
return 0;
}
#define I8254_SYNAPSES_PER_COUNT 2
#define I8254_SIGNAL_ID_CLK 0
#define I8254_SIGNAL_ID_GATE 1
static int i8254_action_read(struct counter_device *const counter,
struct counter_count *const count,
struct counter_synapse *const synapse,
enum counter_synapse_action *const action)
{
struct i8254 *const priv = counter_priv(counter);
switch (synapse->signal->id % I8254_SYNAPSES_PER_COUNT) {
case I8254_SIGNAL_ID_CLK:
*action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
return 0;
case I8254_SIGNAL_ID_GATE:
switch (priv->out_mode[count->id]) {
case I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT:
case I8254_MODE_RATE_GENERATOR:
case I8254_MODE_SQUARE_WAVE_MODE:
case I8254_MODE_HARDWARE_TRIGGERED_STROBE:
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
return 0;
default:
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
}
default:
/* should never reach this path */
return -EINVAL;
}
}
static int i8254_count_ceiling_read(struct counter_device *const counter,
struct counter_count *const count, u64 *const ceiling)
{
struct i8254 *const priv = counter_priv(counter);
mutex_lock(&priv->lock);
switch (priv->out_mode[count->id]) {
case I8254_MODE_RATE_GENERATOR:
/* Rate Generator decrements 0 by one and the counter "wraps around" */
*ceiling = (priv->preset[count->id] == 0) ? U16_MAX : priv->preset[count->id];
break;
case I8254_MODE_SQUARE_WAVE_MODE:
if (priv->preset[count->id] % 2)
*ceiling = priv->preset[count->id] - 1;
else if (priv->preset[count->id] == 0)
/* Square Wave Mode decrements 0 by two and the counter "wraps around" */
*ceiling = U16_MAX - 1;
else
*ceiling = priv->preset[count->id];
break;
default:
*ceiling = U16_MAX;
break;
}
mutex_unlock(&priv->lock);
return 0;
}
static int i8254_count_mode_read(struct counter_device *const counter,
struct counter_count *const count,
enum counter_count_mode *const count_mode)
{
const struct i8254 *const priv = counter_priv(counter);
switch (priv->out_mode[count->id]) {
case I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT:
*count_mode = COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT;
return 0;
case I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT:
*count_mode = COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT;
return 0;
case I8254_MODE_RATE_GENERATOR:
*count_mode = COUNTER_COUNT_MODE_RATE_GENERATOR;
return 0;
case I8254_MODE_SQUARE_WAVE_MODE:
*count_mode = COUNTER_COUNT_MODE_SQUARE_WAVE_MODE;
return 0;
case I8254_MODE_SOFTWARE_TRIGGERED_STROBE:
*count_mode = COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE;
return 0;
case I8254_MODE_HARDWARE_TRIGGERED_STROBE:
*count_mode = COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
}
static int i8254_count_mode_write(struct counter_device *const counter,
struct counter_count *const count,
const enum counter_count_mode count_mode)
{
struct i8254 *const priv = counter_priv(counter);
u8 out_mode;
int ret;
switch (count_mode) {
case COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT:
out_mode = I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT;
break;
case COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT:
out_mode = I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT;
break;
case COUNTER_COUNT_MODE_RATE_GENERATOR:
out_mode = I8254_MODE_RATE_GENERATOR;
break;
case COUNTER_COUNT_MODE_SQUARE_WAVE_MODE:
out_mode = I8254_MODE_SQUARE_WAVE_MODE;
break;
case COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE:
out_mode = I8254_MODE_SOFTWARE_TRIGGERED_STROBE;
break;
case COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE:
out_mode = I8254_MODE_HARDWARE_TRIGGERED_STROBE;
break;
default:
/* should never reach this path */
return -EINVAL;
}
mutex_lock(&priv->lock);
/* Counter Register is cleared when the counter is programmed */
priv->preset[count->id] = 0;
priv->out_mode[count->id] = out_mode;
ret = regmap_write(priv->map, I8254_CONTROL_REG,
I8254_PROGRAM_COUNTER(count->id, out_mode));
mutex_unlock(&priv->lock);
return ret;
}
static int i8254_count_floor_read(struct counter_device *const counter,
struct counter_count *const count, u64 *const floor)
{
struct i8254 *const priv = counter_priv(counter);
mutex_lock(&priv->lock);
switch (priv->out_mode[count->id]) {
case I8254_MODE_RATE_GENERATOR:
/* counter is always reloaded after 1, but 0 is a possible reload value */
*floor = (priv->preset[count->id] == 0) ? 0 : 1;
break;
case I8254_MODE_SQUARE_WAVE_MODE:
/* counter is always reloaded after 2 for even preset values */
*floor = (priv->preset[count->id] % 2 || priv->preset[count->id] == 0) ? 0 : 2;
break;
default:
*floor = 0;
break;
}
mutex_unlock(&priv->lock);
return 0;
}
static int i8254_count_preset_read(struct counter_device *const counter,
struct counter_count *const count, u64 *const preset)
{
const struct i8254 *const priv = counter_priv(counter);
*preset = priv->preset[count->id];
return 0;
}
static int i8254_count_preset_write(struct counter_device *const counter,
struct counter_count *const count, const u64 preset)
{
struct i8254 *const priv = counter_priv(counter);
int ret;
u8 value[2];
if (preset > U16_MAX)
return -ERANGE;
mutex_lock(&priv->lock);
if (priv->out_mode[count->id] == I8254_MODE_RATE_GENERATOR ||
priv->out_mode[count->id] == I8254_MODE_SQUARE_WAVE_MODE) {
if (preset == 1) {
mutex_unlock(&priv->lock);
return -EINVAL;
}
}
priv->preset[count->id] = preset;
put_unaligned_le16(preset, value);
ret = regmap_noinc_write(priv->map, I8254_COUNTER_REG(count->id), value, 2);
mutex_unlock(&priv->lock);
return ret;
}
static int i8254_init_hw(struct regmap *const map)
{
unsigned long i;
int ret;
for (i = 0; i < I8254_NUM_COUNTERS; i++) {
/* Initialize each counter to Mode 0 */
ret = regmap_write(map, I8254_CONTROL_REG,
I8254_PROGRAM_COUNTER(i, I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT));
if (ret)
return ret;
}
return 0;
}
static const struct counter_ops i8254_ops = {
.count_read = i8254_count_read,
.function_read = i8254_function_read,
.action_read = i8254_action_read,
};
#define I8254_SIGNAL(_id, _name) { \
.id = (_id), \
.name = (_name), \
}
static struct counter_signal i8254_signals[] = {
I8254_SIGNAL(0, "CLK 0"), I8254_SIGNAL(1, "GATE 0"),
I8254_SIGNAL(2, "CLK 1"), I8254_SIGNAL(3, "GATE 1"),
I8254_SIGNAL(4, "CLK 2"), I8254_SIGNAL(5, "GATE 2"),
};
static const enum counter_synapse_action i8254_clk_actions[] = {
COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
};
static const enum counter_synapse_action i8254_gate_actions[] = {
COUNTER_SYNAPSE_ACTION_NONE,
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
};
#define I8254_SYNAPSES_BASE(_id) ((_id) * I8254_SYNAPSES_PER_COUNT)
#define I8254_SYNAPSE_CLK(_id) { \
.actions_list = i8254_clk_actions, \
.num_actions = ARRAY_SIZE(i8254_clk_actions), \
.signal = &i8254_signals[I8254_SYNAPSES_BASE(_id) + 0], \
}
#define I8254_SYNAPSE_GATE(_id) { \
.actions_list = i8254_gate_actions, \
.num_actions = ARRAY_SIZE(i8254_gate_actions), \
.signal = &i8254_signals[I8254_SYNAPSES_BASE(_id) + 1], \
}
static struct counter_synapse i8254_synapses[] = {
I8254_SYNAPSE_CLK(0), I8254_SYNAPSE_GATE(0),
I8254_SYNAPSE_CLK(1), I8254_SYNAPSE_GATE(1),
I8254_SYNAPSE_CLK(2), I8254_SYNAPSE_GATE(2),
};
static const enum counter_function i8254_functions_list[] = {
COUNTER_FUNCTION_DECREASE,
};
static const enum counter_count_mode i8254_count_modes[] = {
COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT,
COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT,
COUNTER_COUNT_MODE_RATE_GENERATOR,
COUNTER_COUNT_MODE_SQUARE_WAVE_MODE,
COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE,
COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE,
};
static DEFINE_COUNTER_AVAILABLE(i8254_count_modes_available, i8254_count_modes);
static struct counter_comp i8254_count_ext[] = {
COUNTER_COMP_CEILING(i8254_count_ceiling_read, NULL),
COUNTER_COMP_COUNT_MODE(i8254_count_mode_read, i8254_count_mode_write,
i8254_count_modes_available),
COUNTER_COMP_FLOOR(i8254_count_floor_read, NULL),
COUNTER_COMP_PRESET(i8254_count_preset_read, i8254_count_preset_write),
};
#define I8254_COUNT(_id, _name) { \
.id = (_id), \
.name = (_name), \
.functions_list = i8254_functions_list, \
.num_functions = ARRAY_SIZE(i8254_functions_list), \
.synapses = &i8254_synapses[I8254_SYNAPSES_BASE(_id)], \
.num_synapses = I8254_SYNAPSES_PER_COUNT, \
.ext = i8254_count_ext, \
.num_ext = ARRAY_SIZE(i8254_count_ext) \
}
static struct counter_count i8254_counts[I8254_NUM_COUNTERS] = {
I8254_COUNT(0, "Counter 0"), I8254_COUNT(1, "Counter 1"), I8254_COUNT(2, "Counter 2"),
};
/**
* devm_i8254_regmap_register - Register an i8254 Counter device
* @dev: device that is registering this i8254 Counter device
* @config: configuration for i8254_regmap_config
*
* Registers an Intel 8254 Programmable Interval Timer Counter device. Returns 0 on success and
* negative error number on failure.
*/
int devm_i8254_regmap_register(struct device *const dev,
const struct i8254_regmap_config *const config)
{
struct counter_device *counter;
struct i8254 *priv;
int err;
if (!config->parent)
return -EINVAL;
if (!config->map)
return -EINVAL;
counter = devm_counter_alloc(dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
priv->map = config->map;
counter->name = dev_name(config->parent);
counter->parent = config->parent;
counter->ops = &i8254_ops;
counter->counts = i8254_counts;
counter->num_counts = ARRAY_SIZE(i8254_counts);
counter->signals = i8254_signals;
counter->num_signals = ARRAY_SIZE(i8254_signals);
mutex_init(&priv->lock);
err = i8254_init_hw(priv->map);
if (err)
return err;
err = devm_counter_add(dev, counter);
if (err < 0)
return dev_err_probe(dev, err, "Failed to add counter\n");
return 0;
}
EXPORT_SYMBOL_NS_GPL(devm_i8254_regmap_register, "I8254");
MODULE_AUTHOR("William Breathitt Gray");
MODULE_DESCRIPTION("Intel 8254 Programmable Interval Timer");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("COUNTER");
+510
View File
@@ -0,0 +1,510 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel Quadrature Encoder Peripheral driver
*
* Copyright (C) 2019-2021 Intel Corporation
*
* Author: Felipe Balbi (Intel)
* Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
* Author: Raymond Tan <raymond.tan@intel.com>
*/
#include <linux/counter.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#define INTEL_QEPCON 0x00
#define INTEL_QEPFLT 0x04
#define INTEL_QEPCOUNT 0x08
#define INTEL_QEPMAX 0x0c
#define INTEL_QEPWDT 0x10
#define INTEL_QEPCAPDIV 0x14
#define INTEL_QEPCNTR 0x18
#define INTEL_QEPCAPBUF 0x1c
#define INTEL_QEPINT_STAT 0x20
#define INTEL_QEPINT_MASK 0x24
/* QEPCON */
#define INTEL_QEPCON_EN BIT(0)
#define INTEL_QEPCON_FLT_EN BIT(1)
#define INTEL_QEPCON_EDGE_A BIT(2)
#define INTEL_QEPCON_EDGE_B BIT(3)
#define INTEL_QEPCON_EDGE_INDX BIT(4)
#define INTEL_QEPCON_SWPAB BIT(5)
#define INTEL_QEPCON_OP_MODE BIT(6)
#define INTEL_QEPCON_PH_ERR BIT(7)
#define INTEL_QEPCON_COUNT_RST_MODE BIT(8)
#define INTEL_QEPCON_INDX_GATING_MASK GENMASK(10, 9)
#define INTEL_QEPCON_INDX_GATING(n) (((n) & 3) << 9)
#define INTEL_QEPCON_INDX_PAL_PBL INTEL_QEPCON_INDX_GATING(0)
#define INTEL_QEPCON_INDX_PAL_PBH INTEL_QEPCON_INDX_GATING(1)
#define INTEL_QEPCON_INDX_PAH_PBL INTEL_QEPCON_INDX_GATING(2)
#define INTEL_QEPCON_INDX_PAH_PBH INTEL_QEPCON_INDX_GATING(3)
#define INTEL_QEPCON_CAP_MODE BIT(11)
#define INTEL_QEPCON_FIFO_THRE_MASK GENMASK(14, 12)
#define INTEL_QEPCON_FIFO_THRE(n) ((((n) - 1) & 7) << 12)
#define INTEL_QEPCON_FIFO_EMPTY BIT(15)
/* QEPFLT */
#define INTEL_QEPFLT_MAX_COUNT(n) ((n) & 0x1fffff)
/* QEPINT */
#define INTEL_QEPINT_FIFOCRIT BIT(5)
#define INTEL_QEPINT_FIFOENTRY BIT(4)
#define INTEL_QEPINT_QEPDIR BIT(3)
#define INTEL_QEPINT_QEPRST_UP BIT(2)
#define INTEL_QEPINT_QEPRST_DOWN BIT(1)
#define INTEL_QEPINT_WDT BIT(0)
#define INTEL_QEPINT_MASK_ALL GENMASK(5, 0)
#define INTEL_QEP_CLK_PERIOD_NS 10
struct intel_qep {
struct mutex lock;
struct device *dev;
void __iomem *regs;
bool enabled;
/* Context save registers */
u32 qepcon;
u32 qepflt;
u32 qepmax;
};
static inline u32 intel_qep_readl(struct intel_qep *qep, u32 offset)
{
return readl(qep->regs + offset);
}
static inline void intel_qep_writel(struct intel_qep *qep,
u32 offset, u32 value)
{
writel(value, qep->regs + offset);
}
static void intel_qep_init(struct intel_qep *qep)
{
u32 reg;
reg = intel_qep_readl(qep, INTEL_QEPCON);
reg &= ~INTEL_QEPCON_EN;
intel_qep_writel(qep, INTEL_QEPCON, reg);
qep->enabled = false;
/*
* Make sure peripheral is disabled by flushing the write with
* a dummy read
*/
reg = intel_qep_readl(qep, INTEL_QEPCON);
reg &= ~(INTEL_QEPCON_OP_MODE | INTEL_QEPCON_FLT_EN);
reg |= INTEL_QEPCON_EDGE_A | INTEL_QEPCON_EDGE_B |
INTEL_QEPCON_EDGE_INDX | INTEL_QEPCON_COUNT_RST_MODE;
intel_qep_writel(qep, INTEL_QEPCON, reg);
intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL);
}
static int intel_qep_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct intel_qep *const qep = counter_priv(counter);
pm_runtime_get_sync(qep->dev);
*val = intel_qep_readl(qep, INTEL_QEPCOUNT);
pm_runtime_put(qep->dev);
return 0;
}
static const enum counter_function intel_qep_count_functions[] = {
COUNTER_FUNCTION_QUADRATURE_X4,
};
static int intel_qep_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
*function = COUNTER_FUNCTION_QUADRATURE_X4;
return 0;
}
static const enum counter_synapse_action intel_qep_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
};
static int intel_qep_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
}
static const struct counter_ops intel_qep_counter_ops = {
.count_read = intel_qep_count_read,
.function_read = intel_qep_function_read,
.action_read = intel_qep_action_read,
};
#define INTEL_QEP_SIGNAL(_id, _name) { \
.id = (_id), \
.name = (_name), \
}
static struct counter_signal intel_qep_signals[] = {
INTEL_QEP_SIGNAL(0, "Phase A"),
INTEL_QEP_SIGNAL(1, "Phase B"),
INTEL_QEP_SIGNAL(2, "Index"),
};
#define INTEL_QEP_SYNAPSE(_signal_id) { \
.actions_list = intel_qep_synapse_actions, \
.num_actions = ARRAY_SIZE(intel_qep_synapse_actions), \
.signal = &intel_qep_signals[(_signal_id)], \
}
static struct counter_synapse intel_qep_count_synapses[] = {
INTEL_QEP_SYNAPSE(0),
INTEL_QEP_SYNAPSE(1),
INTEL_QEP_SYNAPSE(2),
};
static int intel_qep_ceiling_read(struct counter_device *counter,
struct counter_count *count, u64 *ceiling)
{
struct intel_qep *qep = counter_priv(counter);
pm_runtime_get_sync(qep->dev);
*ceiling = intel_qep_readl(qep, INTEL_QEPMAX);
pm_runtime_put(qep->dev);
return 0;
}
static int intel_qep_ceiling_write(struct counter_device *counter,
struct counter_count *count, u64 max)
{
struct intel_qep *qep = counter_priv(counter);
/* Intel QEP ceiling configuration only supports 32-bit values */
if (max != (u32)max)
return -ERANGE;
guard(mutex)(&qep->lock);
if (qep->enabled)
return -EBUSY;
pm_runtime_get_sync(qep->dev);
intel_qep_writel(qep, INTEL_QEPMAX, max);
pm_runtime_put(qep->dev);
return 0;
}
static int intel_qep_enable_read(struct counter_device *counter,
struct counter_count *count, u8 *enable)
{
struct intel_qep *qep = counter_priv(counter);
*enable = qep->enabled;
return 0;
}
static int intel_qep_enable_write(struct counter_device *counter,
struct counter_count *count, u8 val)
{
struct intel_qep *qep = counter_priv(counter);
u32 reg;
bool changed;
guard(mutex)(&qep->lock);
changed = val ^ qep->enabled;
if (!changed)
return 0;
pm_runtime_get_sync(qep->dev);
reg = intel_qep_readl(qep, INTEL_QEPCON);
if (val) {
/* Enable peripheral and keep runtime PM always on */
reg |= INTEL_QEPCON_EN;
pm_runtime_get_noresume(qep->dev);
} else {
/* Let runtime PM be idle and disable peripheral */
pm_runtime_put_noidle(qep->dev);
reg &= ~INTEL_QEPCON_EN;
}
intel_qep_writel(qep, INTEL_QEPCON, reg);
pm_runtime_put(qep->dev);
qep->enabled = val;
return 0;
}
static int intel_qep_spike_filter_ns_read(struct counter_device *counter,
struct counter_count *count,
u64 *length)
{
struct intel_qep *qep = counter_priv(counter);
u32 reg;
pm_runtime_get_sync(qep->dev);
reg = intel_qep_readl(qep, INTEL_QEPCON);
if (!(reg & INTEL_QEPCON_FLT_EN)) {
pm_runtime_put(qep->dev);
return 0;
}
reg = INTEL_QEPFLT_MAX_COUNT(intel_qep_readl(qep, INTEL_QEPFLT));
pm_runtime_put(qep->dev);
*length = (reg + 2) * INTEL_QEP_CLK_PERIOD_NS;
return 0;
}
static int intel_qep_spike_filter_ns_write(struct counter_device *counter,
struct counter_count *count,
u64 length)
{
struct intel_qep *qep = counter_priv(counter);
u32 reg;
bool enable;
/*
* Spike filter length is (MAX_COUNT + 2) clock periods.
* Disable filter when userspace writes 0, enable for valid
* nanoseconds values and error out otherwise.
*/
do_div(length, INTEL_QEP_CLK_PERIOD_NS);
if (length == 0) {
enable = false;
length = 0;
} else if (length >= 2) {
enable = true;
length -= 2;
} else {
return -EINVAL;
}
if (length > INTEL_QEPFLT_MAX_COUNT(length))
return -ERANGE;
guard(mutex)(&qep->lock);
if (qep->enabled)
return -EBUSY;
pm_runtime_get_sync(qep->dev);
reg = intel_qep_readl(qep, INTEL_QEPCON);
if (enable)
reg |= INTEL_QEPCON_FLT_EN;
else
reg &= ~INTEL_QEPCON_FLT_EN;
intel_qep_writel(qep, INTEL_QEPFLT, length);
intel_qep_writel(qep, INTEL_QEPCON, reg);
pm_runtime_put(qep->dev);
return 0;
}
static int intel_qep_preset_enable_read(struct counter_device *counter,
struct counter_count *count,
u8 *preset_enable)
{
struct intel_qep *qep = counter_priv(counter);
u32 reg;
pm_runtime_get_sync(qep->dev);
reg = intel_qep_readl(qep, INTEL_QEPCON);
pm_runtime_put(qep->dev);
*preset_enable = !(reg & INTEL_QEPCON_COUNT_RST_MODE);
return 0;
}
static int intel_qep_preset_enable_write(struct counter_device *counter,
struct counter_count *count, u8 val)
{
struct intel_qep *qep = counter_priv(counter);
u32 reg;
guard(mutex)(&qep->lock);
if (qep->enabled)
return -EBUSY;
pm_runtime_get_sync(qep->dev);
reg = intel_qep_readl(qep, INTEL_QEPCON);
if (val)
reg &= ~INTEL_QEPCON_COUNT_RST_MODE;
else
reg |= INTEL_QEPCON_COUNT_RST_MODE;
intel_qep_writel(qep, INTEL_QEPCON, reg);
pm_runtime_put(qep->dev);
return 0;
}
static struct counter_comp intel_qep_count_ext[] = {
COUNTER_COMP_ENABLE(intel_qep_enable_read, intel_qep_enable_write),
COUNTER_COMP_CEILING(intel_qep_ceiling_read, intel_qep_ceiling_write),
COUNTER_COMP_PRESET_ENABLE(intel_qep_preset_enable_read,
intel_qep_preset_enable_write),
COUNTER_COMP_COUNT_U64("spike_filter_ns",
intel_qep_spike_filter_ns_read,
intel_qep_spike_filter_ns_write),
};
static struct counter_count intel_qep_counter_count[] = {
{
.id = 0,
.name = "Channel 1 Count",
.functions_list = intel_qep_count_functions,
.num_functions = ARRAY_SIZE(intel_qep_count_functions),
.synapses = intel_qep_count_synapses,
.num_synapses = ARRAY_SIZE(intel_qep_count_synapses),
.ext = intel_qep_count_ext,
.num_ext = ARRAY_SIZE(intel_qep_count_ext),
},
};
static int intel_qep_probe(struct pci_dev *pci, const struct pci_device_id *id)
{
struct counter_device *counter;
struct intel_qep *qep;
struct device *dev = &pci->dev;
void __iomem *regs;
int ret;
counter = devm_counter_alloc(dev, sizeof(*qep));
if (!counter)
return -ENOMEM;
qep = counter_priv(counter);
ret = pcim_enable_device(pci);
if (ret)
return ret;
pci_set_master(pci);
regs = pcim_iomap_region(pci, 0, pci_name(pci));
if (IS_ERR(regs))
return PTR_ERR(regs);
qep->dev = dev;
qep->regs = regs;
ret = devm_mutex_init(dev, &qep->lock);
if (ret)
return ret;
intel_qep_init(qep);
pci_set_drvdata(pci, qep);
counter->name = pci_name(pci);
counter->parent = dev;
counter->ops = &intel_qep_counter_ops;
counter->counts = intel_qep_counter_count;
counter->num_counts = ARRAY_SIZE(intel_qep_counter_count);
counter->signals = intel_qep_signals;
counter->num_signals = ARRAY_SIZE(intel_qep_signals);
qep->enabled = false;
pm_runtime_put(dev);
pm_runtime_allow(dev);
ret = devm_counter_add(&pci->dev, counter);
if (ret < 0)
return dev_err_probe(&pci->dev, ret, "Failed to add counter\n");
return 0;
}
static void intel_qep_remove(struct pci_dev *pci)
{
struct intel_qep *qep = pci_get_drvdata(pci);
struct device *dev = &pci->dev;
pm_runtime_forbid(dev);
if (!qep->enabled)
pm_runtime_get(dev);
intel_qep_writel(qep, INTEL_QEPCON, 0);
}
static int __maybe_unused intel_qep_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct intel_qep *qep = pci_get_drvdata(pdev);
qep->qepcon = intel_qep_readl(qep, INTEL_QEPCON);
qep->qepflt = intel_qep_readl(qep, INTEL_QEPFLT);
qep->qepmax = intel_qep_readl(qep, INTEL_QEPMAX);
return 0;
}
static int __maybe_unused intel_qep_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct intel_qep *qep = pci_get_drvdata(pdev);
/*
* Make sure peripheral is disabled when restoring registers and
* control register bits that are writable only when the peripheral
* is disabled
*/
intel_qep_writel(qep, INTEL_QEPCON, 0);
intel_qep_readl(qep, INTEL_QEPCON);
intel_qep_writel(qep, INTEL_QEPFLT, qep->qepflt);
intel_qep_writel(qep, INTEL_QEPMAX, qep->qepmax);
intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL);
/* Restore all other control register bits except enable status */
intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon & ~INTEL_QEPCON_EN);
intel_qep_readl(qep, INTEL_QEPCON);
/* Restore enable status */
intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon);
return 0;
}
static UNIVERSAL_DEV_PM_OPS(intel_qep_pm_ops,
intel_qep_suspend, intel_qep_resume, NULL);
static const struct pci_device_id intel_qep_id_table[] = {
/* EHL */
{ PCI_VDEVICE(INTEL, 0x4bc3), },
{ PCI_VDEVICE(INTEL, 0x4b81), },
{ PCI_VDEVICE(INTEL, 0x4b82), },
{ PCI_VDEVICE(INTEL, 0x4b83), },
{ } /* Terminating Entry */
};
MODULE_DEVICE_TABLE(pci, intel_qep_id_table);
static struct pci_driver intel_qep_driver = {
.name = "intel-qep",
.id_table = intel_qep_id_table,
.probe = intel_qep_probe,
.remove = intel_qep_remove,
.driver = {
.pm = &intel_qep_pm_ops,
}
};
module_pci_driver(intel_qep_driver);
MODULE_AUTHOR("Felipe Balbi (Intel)");
MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Intel Quadrature Encoder Peripheral driver");
MODULE_IMPORT_NS("COUNTER");
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
*/
#include <linux/cleanup.h>
#include <linux/counter.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#define INTERRUPT_CNT_NAME "interrupt-cnt"
struct interrupt_cnt_priv {
atomic_long_t count;
struct gpio_desc *gpio;
int irq;
bool enabled;
struct mutex lock;
struct counter_signal signals;
struct counter_synapse synapses;
struct counter_count cnts;
};
static irqreturn_t interrupt_cnt_isr(int irq, void *dev_id)
{
struct counter_device *counter = dev_id;
struct interrupt_cnt_priv *priv = counter_priv(counter);
atomic_long_inc(&priv->count);
counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE, 0);
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(counter);
guard(mutex)(&priv->lock);
*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(counter);
guard(mutex)(&priv->lock);
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(counter);
*val = atomic_long_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(counter);
if (val != (typeof(priv->count.counter))val)
return -ERANGE;
atomic_long_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(counter);
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 int interrupt_cnt_watch_validate(struct counter_device *counter,
const struct counter_watch *watch)
{
if (watch->channel != 0 ||
watch->event != COUNTER_EVENT_CHANGE_OF_STATE)
return -EINVAL;
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,
.watch_validate = interrupt_cnt_watch_validate,
};
static int interrupt_cnt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct counter_device *counter;
struct interrupt_cnt_priv *priv;
int ret;
counter = devm_counter_alloc(dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
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;
counter->signals = &priv->signals;
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);
counter->name = dev_name(dev);
counter->parent = dev;
counter->ops = &interrupt_cnt_ops;
counter->counts = &priv->cnts;
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, dev_name(dev), counter);
if (ret)
return ret;
ret = devm_mutex_init(dev, &priv->lock);
if (ret)
return ret;
ret = devm_counter_add(dev, counter);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to add counter\n");
return 0;
}
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");
MODULE_IMPORT_NS("COUNTER");
@@ -0,0 +1,608 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020 Microchip
*
* Author: Kamel Bouhara <kamel.bouhara@bootlin.com>
*/
#include <linux/clk.h>
#include <linux/counter.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <uapi/linux/counter/microchip-tcb-capture.h>
#include <soc/at91/atmel_tcb.h>
#define ATMEL_TC_CMR_MASK (ATMEL_TC_LDRA_RISING | ATMEL_TC_LDRB_FALLING | \
ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_LDBDIS | \
ATMEL_TC_LDBSTOP)
#define ATMEL_TC_DEF_IRQS (ATMEL_TC_ETRGS | ATMEL_TC_COVFS | \
ATMEL_TC_LDRAS | ATMEL_TC_LDRBS | ATMEL_TC_CPCS)
#define ATMEL_TC_QDEN BIT(8)
#define ATMEL_TC_POSEN BIT(9)
struct mchp_tc_data {
const struct atmel_tcb_config *tc_cfg;
struct regmap *regmap;
int qdec_mode;
int num_channels;
int channel[2];
};
static const enum counter_function mchp_tc_count_functions[] = {
COUNTER_FUNCTION_INCREASE,
COUNTER_FUNCTION_QUADRATURE_X4,
};
static const enum counter_synapse_action mchp_tc_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_NONE,
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
};
static struct counter_signal mchp_tc_count_signals[] = {
{
.id = 0,
.name = "Channel A",
},
{
.id = 1,
.name = "Channel B",
}
};
static struct counter_synapse mchp_tc_count_synapses[] = {
{
.actions_list = mchp_tc_synapse_actions,
.num_actions = ARRAY_SIZE(mchp_tc_synapse_actions),
.signal = &mchp_tc_count_signals[0]
},
{
.actions_list = mchp_tc_synapse_actions,
.num_actions = ARRAY_SIZE(mchp_tc_synapse_actions),
.signal = &mchp_tc_count_signals[1]
}
};
static int mchp_tc_count_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
struct mchp_tc_data *const priv = counter_priv(counter);
if (priv->qdec_mode)
*function = COUNTER_FUNCTION_QUADRATURE_X4;
else
*function = COUNTER_FUNCTION_INCREASE;
return 0;
}
static int mchp_tc_count_function_write(struct counter_device *counter,
struct counter_count *count,
enum counter_function function)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 bmr, cmr;
regmap_read(priv->regmap, ATMEL_TC_BMR, &bmr);
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr);
/* Set capture mode */
cmr &= ~ATMEL_TC_WAVE;
switch (function) {
case COUNTER_FUNCTION_INCREASE:
priv->qdec_mode = 0;
/* Set highest rate based on whether soc has gclk or not */
bmr &= ~(ATMEL_TC_QDEN | ATMEL_TC_POSEN);
if (!priv->tc_cfg->has_gclk)
cmr |= ATMEL_TC_TIMER_CLOCK2;
else
cmr |= ATMEL_TC_TIMER_CLOCK1;
/* Setup the period capture mode */
cmr |= ATMEL_TC_CMR_MASK;
cmr &= ~(ATMEL_TC_ABETRG | ATMEL_TC_XC0);
break;
case COUNTER_FUNCTION_QUADRATURE_X4:
if (!priv->tc_cfg->has_qdec)
return -EINVAL;
/* In QDEC mode settings both channels 0 and 1 are required */
if (priv->num_channels < 2 || priv->channel[0] != 0 ||
priv->channel[1] != 1) {
pr_err("Invalid channels number or id for quadrature mode\n");
return -EINVAL;
}
priv->qdec_mode = 1;
bmr |= ATMEL_TC_QDEN | ATMEL_TC_POSEN;
cmr |= ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_ABETRG | ATMEL_TC_XC0;
break;
default:
/* should never reach this path */
return -EINVAL;
}
regmap_write(priv->regmap, ATMEL_TC_BMR, bmr);
regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), cmr);
/* Enable clock and trigger counter */
regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CCR),
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG);
if (priv->qdec_mode) {
regmap_write(priv->regmap,
ATMEL_TC_REG(priv->channel[1], CMR), cmr);
regmap_write(priv->regmap,
ATMEL_TC_REG(priv->channel[1], CCR),
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG);
}
return 0;
}
static int mchp_tc_count_signal_read(struct counter_device *counter,
struct counter_signal *signal,
enum counter_signal_level *lvl)
{
struct mchp_tc_data *const priv = counter_priv(counter);
bool sigstatus;
u32 sr;
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], SR), &sr);
if (signal->id == 1)
sigstatus = (sr & ATMEL_TC_MTIOB);
else
sigstatus = (sr & ATMEL_TC_MTIOA);
*lvl = sigstatus ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
return 0;
}
static int mchp_tc_count_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 cmr;
if (priv->qdec_mode) {
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
}
/* Only TIOA signal is evaluated in non-QDEC mode */
if (synapse->signal->id != 0) {
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
}
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr);
switch (cmr & ATMEL_TC_ETRGEDG) {
default:
*action = COUNTER_SYNAPSE_ACTION_NONE;
break;
case ATMEL_TC_ETRGEDG_RISING:
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
break;
case ATMEL_TC_ETRGEDG_FALLING:
*action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
break;
case ATMEL_TC_ETRGEDG_BOTH:
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
break;
}
return 0;
}
static int mchp_tc_count_action_write(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action action)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 edge = ATMEL_TC_ETRGEDG_NONE;
/* QDEC mode is rising edge only; only TIOA handled in non-QDEC mode */
if (priv->qdec_mode || synapse->signal->id != 0)
return -EINVAL;
switch (action) {
case COUNTER_SYNAPSE_ACTION_NONE:
edge = ATMEL_TC_ETRGEDG_NONE;
break;
case COUNTER_SYNAPSE_ACTION_RISING_EDGE:
edge = ATMEL_TC_ETRGEDG_RISING;
break;
case COUNTER_SYNAPSE_ACTION_FALLING_EDGE:
edge = ATMEL_TC_ETRGEDG_FALLING;
break;
case COUNTER_SYNAPSE_ACTION_BOTH_EDGES:
edge = ATMEL_TC_ETRGEDG_BOTH;
break;
default:
/* should never reach this path */
return -EINVAL;
}
return regmap_write_bits(priv->regmap,
ATMEL_TC_REG(priv->channel[0], CMR),
ATMEL_TC_ETRGEDG, edge);
}
static int mchp_tc_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 cnt;
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CV), &cnt);
*val = cnt;
return 0;
}
static int mchp_tc_count_cap_read(struct counter_device *counter,
struct counter_count *count, size_t idx, u64 *val)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 cnt;
int ret;
switch (idx) {
case COUNTER_MCHP_EXCAP_RA:
ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RA), &cnt);
break;
case COUNTER_MCHP_EXCAP_RB:
ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RB), &cnt);
break;
default:
return -EINVAL;
}
if (ret < 0)
return ret;
*val = cnt;
return 0;
}
static int mchp_tc_count_cap_write(struct counter_device *counter,
struct counter_count *count, size_t idx, u64 val)
{
struct mchp_tc_data *const priv = counter_priv(counter);
int ret;
if (val > U32_MAX)
return -ERANGE;
switch (idx) {
case COUNTER_MCHP_EXCAP_RA:
ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RA), val);
break;
case COUNTER_MCHP_EXCAP_RB:
ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RB), val);
break;
default:
return -EINVAL;
}
return ret;
}
static int mchp_tc_count_compare_read(struct counter_device *counter, struct counter_count *count,
u64 *val)
{
struct mchp_tc_data *const priv = counter_priv(counter);
u32 cnt;
int ret;
ret = regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], RC), &cnt);
if (ret < 0)
return ret;
*val = cnt;
return 0;
}
static int mchp_tc_count_compare_write(struct counter_device *counter, struct counter_count *count,
u64 val)
{
struct mchp_tc_data *const priv = counter_priv(counter);
if (val > U32_MAX)
return -ERANGE;
return regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], RC), val);
}
static DEFINE_COUNTER_ARRAY_CAPTURE(mchp_tc_cnt_cap_array, 2);
static struct counter_comp mchp_tc_count_ext[] = {
COUNTER_COMP_ARRAY_CAPTURE(mchp_tc_count_cap_read, mchp_tc_count_cap_write,
mchp_tc_cnt_cap_array),
COUNTER_COMP_COMPARE(mchp_tc_count_compare_read, mchp_tc_count_compare_write),
};
static int mchp_tc_watch_validate(struct counter_device *counter,
const struct counter_watch *watch)
{
if (watch->channel == COUNTER_MCHP_EVCHN_CV || watch->channel == COUNTER_MCHP_EVCHN_RA)
switch (watch->event) {
case COUNTER_EVENT_CHANGE_OF_STATE:
case COUNTER_EVENT_OVERFLOW:
case COUNTER_EVENT_CAPTURE:
return 0;
default:
return -EINVAL;
}
if (watch->channel == COUNTER_MCHP_EVCHN_RB && watch->event == COUNTER_EVENT_CAPTURE)
return 0;
if (watch->channel == COUNTER_MCHP_EVCHN_RC && watch->event == COUNTER_EVENT_THRESHOLD)
return 0;
return -EINVAL;
}
static struct counter_count mchp_tc_counts[] = {
{
.id = 0,
.name = "Timer Counter",
.functions_list = mchp_tc_count_functions,
.num_functions = ARRAY_SIZE(mchp_tc_count_functions),
.synapses = mchp_tc_count_synapses,
.num_synapses = ARRAY_SIZE(mchp_tc_count_synapses),
.ext = mchp_tc_count_ext,
.num_ext = ARRAY_SIZE(mchp_tc_count_ext),
},
};
static const struct counter_ops mchp_tc_ops = {
.signal_read = mchp_tc_count_signal_read,
.count_read = mchp_tc_count_read,
.function_read = mchp_tc_count_function_read,
.function_write = mchp_tc_count_function_write,
.action_read = mchp_tc_count_action_read,
.action_write = mchp_tc_count_action_write,
.watch_validate = mchp_tc_watch_validate,
};
static const struct atmel_tcb_config tcb_rm9200_config = {
.counter_width = 16,
};
static const struct atmel_tcb_config tcb_sam9x5_config = {
.counter_width = 32,
};
static const struct atmel_tcb_config tcb_sama5d2_config = {
.counter_width = 32,
.has_gclk = true,
.has_qdec = true,
};
static const struct atmel_tcb_config tcb_sama5d3_config = {
.counter_width = 32,
.has_qdec = true,
};
static const struct of_device_id atmel_tc_of_match[] = {
{ .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, },
{ .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, },
{ .compatible = "atmel,sama5d2-tcb", .data = &tcb_sama5d2_config, },
{ .compatible = "atmel,sama5d3-tcb", .data = &tcb_sama5d3_config, },
{ /* sentinel */ }
};
static irqreturn_t mchp_tc_isr(int irq, void *dev_id)
{
struct counter_device *const counter = dev_id;
struct mchp_tc_data *const priv = counter_priv(counter);
u32 sr, mask;
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], SR), &sr);
regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], IMR), &mask);
sr &= mask;
if (!(sr & ATMEL_TC_ALL_IRQ))
return IRQ_NONE;
if (sr & ATMEL_TC_ETRGS)
counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE,
COUNTER_MCHP_EVCHN_CV);
if (sr & ATMEL_TC_LDRAS)
counter_push_event(counter, COUNTER_EVENT_CAPTURE,
COUNTER_MCHP_EVCHN_RA);
if (sr & ATMEL_TC_LDRBS)
counter_push_event(counter, COUNTER_EVENT_CAPTURE,
COUNTER_MCHP_EVCHN_RB);
if (sr & ATMEL_TC_CPCS)
counter_push_event(counter, COUNTER_EVENT_THRESHOLD,
COUNTER_MCHP_EVCHN_RC);
if (sr & ATMEL_TC_COVFS)
counter_push_event(counter, COUNTER_EVENT_OVERFLOW,
COUNTER_MCHP_EVCHN_CV);
return IRQ_HANDLED;
}
static void mchp_tc_irq_remove(void *ptr)
{
struct mchp_tc_data *priv = ptr;
regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], IDR), ATMEL_TC_DEF_IRQS);
}
static int mchp_tc_irq_enable(struct counter_device *const counter, int irq)
{
struct mchp_tc_data *const priv = counter_priv(counter);
int ret = devm_request_irq(counter->parent, irq, mchp_tc_isr, IRQF_SHARED,
dev_name(counter->parent), counter);
if (ret < 0)
return ret;
ret = regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], IER), ATMEL_TC_DEF_IRQS);
if (ret < 0)
return ret;
ret = devm_add_action_or_reset(counter->parent, mchp_tc_irq_remove, priv);
if (ret < 0)
return ret;
return 0;
}
static void mchp_tc_clk_remove(void *ptr)
{
clk_disable_unprepare((struct clk *)ptr);
}
static int mchp_tc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct atmel_tcb_config *tcb_config;
const struct of_device_id *match;
struct counter_device *counter;
struct mchp_tc_data *priv;
char clk_name[7];
struct regmap *regmap;
struct clk *clk[3];
u32 channel;
int ret, i;
counter = devm_counter_alloc(&pdev->dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
match = of_match_node(atmel_tc_of_match, np->parent);
tcb_config = match->data;
if (!tcb_config) {
dev_err(&pdev->dev, "No matching parent node found\n");
return -ENODEV;
}
regmap = syscon_node_to_regmap(np->parent);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
/* max. channels number is 2 when in QDEC mode */
priv->num_channels = of_property_count_u32_elems(np, "reg");
if (priv->num_channels < 0) {
dev_err(&pdev->dev, "Invalid or missing channel\n");
return -EINVAL;
}
/* Register channels and initialize clocks */
for (i = 0; i < priv->num_channels; i++) {
ret = of_property_read_u32_index(np, "reg", i, &channel);
if (ret < 0 || channel > 2)
return -ENODEV;
priv->channel[i] = channel;
snprintf(clk_name, sizeof(clk_name), "t%u_clk", channel);
clk[i] = of_clk_get_by_name(np->parent, clk_name);
if (IS_ERR(clk[i])) {
/* Fallback to t0_clk */
clk[i] = of_clk_get_by_name(np->parent, "t0_clk");
if (IS_ERR(clk[i]))
return PTR_ERR(clk[i]);
}
ret = clk_prepare_enable(clk[i]);
if (ret)
return ret;
ret = devm_add_action_or_reset(&pdev->dev,
mchp_tc_clk_remove,
clk[i]);
if (ret)
return ret;
dev_dbg(&pdev->dev,
"Initialized capture mode on channel %d\n",
channel);
}
/* Disable Quadrature Decoder and position measure */
ret = regmap_update_bits(regmap, ATMEL_TC_BMR, ATMEL_TC_QDEN | ATMEL_TC_POSEN, 0);
if (ret)
return ret;
/* Setup the period capture mode */
ret = regmap_update_bits(regmap, ATMEL_TC_REG(priv->channel[0], CMR),
ATMEL_TC_WAVE | ATMEL_TC_ABETRG | ATMEL_TC_CMR_MASK |
ATMEL_TC_TCCLKS,
ATMEL_TC_CMR_MASK);
if (ret)
return ret;
/* Enable clock and trigger counter */
ret = regmap_write(regmap, ATMEL_TC_REG(priv->channel[0], CCR),
ATMEL_TC_CLKEN | ATMEL_TC_SWTRG);
if (ret)
return ret;
priv->tc_cfg = tcb_config;
priv->regmap = regmap;
counter->name = dev_name(&pdev->dev);
counter->parent = &pdev->dev;
counter->ops = &mchp_tc_ops;
counter->num_counts = ARRAY_SIZE(mchp_tc_counts);
counter->counts = mchp_tc_counts;
counter->num_signals = ARRAY_SIZE(mchp_tc_count_signals);
counter->signals = mchp_tc_count_signals;
i = of_irq_get(np->parent, 0);
if (i == -EPROBE_DEFER)
return -EPROBE_DEFER;
if (i > 0) {
ret = mchp_tc_irq_enable(counter, i);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to set up IRQ");
}
ret = devm_counter_add(&pdev->dev, counter);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
return 0;
}
static const struct of_device_id mchp_tc_dt_ids[] = {
{ .compatible = "microchip,tcb-capture", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, mchp_tc_dt_ids);
static struct platform_driver mchp_tc_driver = {
.probe = mchp_tc_probe,
.driver = {
.name = "microchip-tcb-capture",
.of_match_table = mchp_tc_dt_ids,
},
};
module_platform_driver(mchp_tc_driver);
MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
MODULE_DESCRIPTION("Microchip TCB Capture driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("COUNTER");
+909
View File
@@ -0,0 +1,909 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Renesas RZ/G2L MTU3a Counter driver
*
* Copyright (C) 2022 Renesas Electronics Corporation
*/
#include <linux/clk.h>
#include <linux/counter.h>
#include <linux/mfd/rz-mtu3.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
/*
* Register descriptions
* TSR: Timer Status Register
* TMDR1: Timer Mode Register 1
* TMDR3: Timer Mode Register 3
* TIOR: Timer I/O Control Register
* TCR: Timer Control Register
* TCNT: Timer Counter
* TGRA: Timer general register A
* TCNTLW: Timer Longword Counter
* TGRALW: Timer longword general register A
*/
#define RZ_MTU3_TSR_TCFD BIT(7) /* Count Direction Flag */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_1 (4) /* Phase counting mode 1 */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_2 (5) /* Phase counting mode 2 */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_3 (6) /* Phase counting mode 3 */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_4 (7) /* Phase counting mode 4 */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_5 (9) /* Phase counting mode 5 */
#define RZ_MTU3_TMDR1_PH_CNT_MODE_MASK (0xf)
/*
* LWA: MTU1/MTU2 Combination Longword Access Control
* 0: 16-bit, 1: 32-bit
*/
#define RZ_MTU3_TMDR3_LWA (0)
/*
* PHCKSEL: External Input Phase Clock Select
* 0: MTCLKA and MTCLKB, 1: MTCLKC and MTCLKD
*/
#define RZ_MTU3_TMDR3_PHCKSEL (1)
#define RZ_MTU3_16_BIT_MTU1_CH (0)
#define RZ_MTU3_16_BIT_MTU2_CH (1)
#define RZ_MTU3_32_BIT_CH (2)
#define RZ_MTU3_TIOR_NO_OUTPUT (0) /* Output prohibited */
#define RZ_MTU3_TIOR_IC_BOTH (10) /* Input capture at both edges */
#define SIGNAL_A_ID (0)
#define SIGNAL_B_ID (1)
#define SIGNAL_C_ID (2)
#define SIGNAL_D_ID (3)
#define RZ_MTU3_MAX_HW_CNTR_CHANNELS (2)
#define RZ_MTU3_MAX_LOGICAL_CNTR_CHANNELS (3)
/**
* struct rz_mtu3_cnt - MTU3 counter private data
*
* @clk: MTU3 module clock
* @lock: Lock to prevent concurrent access for ceiling and count
* @ch: HW channels for the counters
* @count_is_enabled: Enabled state of Counter value channel
* @mtu_16bit_max: Cache for 16-bit counters
* @mtu_32bit_max: Cache for 32-bit counters
*/
struct rz_mtu3_cnt {
struct clk *clk;
struct mutex lock;
struct rz_mtu3_channel *ch;
bool count_is_enabled[RZ_MTU3_MAX_LOGICAL_CNTR_CHANNELS];
union {
u16 mtu_16bit_max[RZ_MTU3_MAX_HW_CNTR_CHANNELS];
u32 mtu_32bit_max;
};
};
static const enum counter_function rz_mtu3_count_functions[] = {
COUNTER_FUNCTION_QUADRATURE_X4,
COUNTER_FUNCTION_PULSE_DIRECTION,
COUNTER_FUNCTION_QUADRATURE_X2_B,
};
static inline size_t rz_mtu3_get_hw_ch(const size_t id)
{
return (id == RZ_MTU3_32_BIT_CH) ? 0 : id;
}
static inline struct rz_mtu3_channel *rz_mtu3_get_ch(struct counter_device *counter, int id)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
const size_t ch_id = rz_mtu3_get_hw_ch(id);
return &priv->ch[ch_id];
}
static bool rz_mtu3_is_counter_invalid(struct counter_device *counter, int id)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
unsigned long tmdr;
pm_runtime_get_sync(counter->parent);
tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3);
pm_runtime_put(counter->parent);
if (id == RZ_MTU3_32_BIT_CH && test_bit(RZ_MTU3_TMDR3_LWA, &tmdr))
return false;
if (id != RZ_MTU3_32_BIT_CH && !test_bit(RZ_MTU3_TMDR3_LWA, &tmdr))
return false;
return true;
}
static int rz_mtu3_lock_if_counter_is_valid(struct counter_device *counter,
struct rz_mtu3_channel *const ch,
struct rz_mtu3_cnt *const priv,
int id)
{
mutex_lock(&priv->lock);
if (ch->is_busy && !priv->count_is_enabled[id]) {
mutex_unlock(&priv->lock);
return -EINVAL;
}
if (rz_mtu3_is_counter_invalid(counter, id)) {
mutex_unlock(&priv->lock);
return -EBUSY;
}
return 0;
}
static int rz_mtu3_lock_if_count_is_enabled(struct rz_mtu3_channel *const ch,
struct rz_mtu3_cnt *const priv,
int id)
{
mutex_lock(&priv->lock);
if (ch->is_busy && !priv->count_is_enabled[id]) {
mutex_unlock(&priv->lock);
return -EINVAL;
}
return 0;
}
static int rz_mtu3_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
if (count->id == RZ_MTU3_32_BIT_CH)
*val = rz_mtu3_32bit_ch_read(ch, RZ_MTU3_TCNTLW);
else
*val = rz_mtu3_16bit_ch_read(ch, RZ_MTU3_TCNT);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_write(struct counter_device *counter,
struct counter_count *count, const u64 val)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
if (count->id == RZ_MTU3_32_BIT_CH)
rz_mtu3_32bit_ch_write(ch, RZ_MTU3_TCNTLW, val);
else
rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TCNT, val);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_function_read_helper(struct rz_mtu3_channel *const ch,
struct counter_device *const counter,
enum counter_function *function)
{
u8 timer_mode;
pm_runtime_get_sync(counter->parent);
timer_mode = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TMDR1);
pm_runtime_put(counter->parent);
switch (timer_mode & RZ_MTU3_TMDR1_PH_CNT_MODE_MASK) {
case RZ_MTU3_TMDR1_PH_CNT_MODE_1:
*function = COUNTER_FUNCTION_QUADRATURE_X4;
return 0;
case RZ_MTU3_TMDR1_PH_CNT_MODE_2:
*function = COUNTER_FUNCTION_PULSE_DIRECTION;
return 0;
case RZ_MTU3_TMDR1_PH_CNT_MODE_4:
*function = COUNTER_FUNCTION_QUADRATURE_X2_B;
return 0;
default:
/*
* TODO:
* - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_3
* - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_5
*/
return -EINVAL;
}
}
static int rz_mtu3_count_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id);
if (ret)
return ret;
ret = rz_mtu3_count_function_read_helper(ch, counter, function);
mutex_unlock(&priv->lock);
return ret;
}
static int rz_mtu3_count_function_write(struct counter_device *counter,
struct counter_count *count,
enum counter_function function)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
u8 timer_mode;
int ret;
ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id);
if (ret)
return ret;
switch (function) {
case COUNTER_FUNCTION_QUADRATURE_X4:
timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_1;
break;
case COUNTER_FUNCTION_PULSE_DIRECTION:
timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_2;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
timer_mode = RZ_MTU3_TMDR1_PH_CNT_MODE_4;
break;
default:
/*
* TODO:
* - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_3
* - need to add RZ_MTU3_TMDR1_PH_CNT_MODE_5
*/
mutex_unlock(&priv->lock);
return -EINVAL;
}
pm_runtime_get_sync(counter->parent);
rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, timer_mode);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_direction_read(struct counter_device *counter,
struct counter_count *count,
enum counter_count_direction *direction)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
u8 tsr;
ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
tsr = rz_mtu3_8bit_ch_read(ch, RZ_MTU3_TSR);
pm_runtime_put(counter->parent);
*direction = (tsr & RZ_MTU3_TSR_TCFD) ?
COUNTER_COUNT_DIRECTION_FORWARD : COUNTER_COUNT_DIRECTION_BACKWARD;
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_ceiling_read(struct counter_device *counter,
struct counter_count *count,
u64 *ceiling)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
const size_t ch_id = rz_mtu3_get_hw_ch(count->id);
int ret;
ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id);
if (ret)
return ret;
switch (count->id) {
case RZ_MTU3_16_BIT_MTU1_CH:
case RZ_MTU3_16_BIT_MTU2_CH:
*ceiling = priv->mtu_16bit_max[ch_id];
break;
case RZ_MTU3_32_BIT_CH:
*ceiling = priv->mtu_32bit_max;
break;
default:
/* should never reach this path */
mutex_unlock(&priv->lock);
return -EINVAL;
}
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_ceiling_write(struct counter_device *counter,
struct counter_count *count,
u64 ceiling)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
const size_t ch_id = rz_mtu3_get_hw_ch(count->id);
int ret;
ret = rz_mtu3_lock_if_counter_is_valid(counter, ch, priv, count->id);
if (ret)
return ret;
switch (count->id) {
case RZ_MTU3_16_BIT_MTU1_CH:
case RZ_MTU3_16_BIT_MTU2_CH:
if (ceiling > U16_MAX) {
mutex_unlock(&priv->lock);
return -ERANGE;
}
priv->mtu_16bit_max[ch_id] = ceiling;
break;
case RZ_MTU3_32_BIT_CH:
if (ceiling > U32_MAX) {
mutex_unlock(&priv->lock);
return -ERANGE;
}
priv->mtu_32bit_max = ceiling;
break;
default:
/* should never reach this path */
mutex_unlock(&priv->lock);
return -EINVAL;
}
pm_runtime_get_sync(counter->parent);
if (count->id == RZ_MTU3_32_BIT_CH)
rz_mtu3_32bit_ch_write(ch, RZ_MTU3_TGRALW, ceiling);
else
rz_mtu3_16bit_ch_write(ch, RZ_MTU3_TGRA, ceiling);
rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static void rz_mtu3_32bit_cnt_setting(struct counter_device *counter)
{
struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0);
struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1);
/* Phase counting mode 1 is used as default in initialization. */
rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_PH_CNT_MODE_1);
rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA);
rz_mtu3_8bit_ch_write(ch1, RZ_MTU3_TIOR, RZ_MTU3_TIOR_IC_BOTH);
rz_mtu3_enable(ch1);
rz_mtu3_enable(ch2);
}
static void rz_mtu3_16bit_cnt_setting(struct counter_device *counter, int id)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id);
/* Phase counting mode 1 is used as default in initialization. */
rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TMDR1, RZ_MTU3_TMDR1_PH_CNT_MODE_1);
rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TCR, RZ_MTU3_TCR_CCLR_TGRA);
rz_mtu3_8bit_ch_write(ch, RZ_MTU3_TIOR, RZ_MTU3_TIOR_NO_OUTPUT);
rz_mtu3_enable(ch);
}
static int rz_mtu3_initialize_counter(struct counter_device *counter, int id)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id);
struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0);
struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1);
switch (id) {
case RZ_MTU3_16_BIT_MTU1_CH:
case RZ_MTU3_16_BIT_MTU2_CH:
if (!rz_mtu3_request_channel(ch))
return -EBUSY;
rz_mtu3_16bit_cnt_setting(counter, id);
return 0;
case RZ_MTU3_32_BIT_CH:
/*
* 32-bit phase counting need MTU1 and MTU2 to create 32-bit
* cascade counter.
*/
if (!rz_mtu3_request_channel(ch1))
return -EBUSY;
if (!rz_mtu3_request_channel(ch2)) {
rz_mtu3_release_channel(ch1);
return -EBUSY;
}
rz_mtu3_32bit_cnt_setting(counter);
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
}
static void rz_mtu3_terminate_counter(struct counter_device *counter, int id)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, id);
struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0);
struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1);
if (id == RZ_MTU3_32_BIT_CH) {
rz_mtu3_release_channel(ch2);
rz_mtu3_release_channel(ch1);
rz_mtu3_disable(ch2);
rz_mtu3_disable(ch1);
} else {
rz_mtu3_release_channel(ch);
rz_mtu3_disable(ch);
}
}
static int rz_mtu3_count_enable_read(struct counter_device *counter,
struct counter_count *count, u8 *enable)
{
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_channel *const ch1 = rz_mtu3_get_ch(counter, 0);
struct rz_mtu3_channel *const ch2 = rz_mtu3_get_ch(counter, 1);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id);
if (ret)
return ret;
if (count->id == RZ_MTU3_32_BIT_CH)
*enable = rz_mtu3_is_enabled(ch1) && rz_mtu3_is_enabled(ch2);
else
*enable = rz_mtu3_is_enabled(ch);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_count_enable_write(struct counter_device *counter,
struct counter_count *count, u8 enable)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret = 0;
mutex_lock(&priv->lock);
if (priv->count_is_enabled[count->id] == enable)
goto exit;
if (enable) {
pm_runtime_get_sync(counter->parent);
ret = rz_mtu3_initialize_counter(counter, count->id);
if (ret == 0)
priv->count_is_enabled[count->id] = true;
} else {
rz_mtu3_terminate_counter(counter, count->id);
priv->count_is_enabled[count->id] = false;
pm_runtime_put(counter->parent);
}
exit:
mutex_unlock(&priv->lock);
return ret;
}
static int rz_mtu3_lock_if_ch0_is_enabled(struct rz_mtu3_cnt *const priv)
{
mutex_lock(&priv->lock);
if (priv->ch->is_busy && !(priv->count_is_enabled[RZ_MTU3_16_BIT_MTU1_CH] ||
priv->count_is_enabled[RZ_MTU3_32_BIT_CH])) {
mutex_unlock(&priv->lock);
return -EINVAL;
}
return 0;
}
static int rz_mtu3_cascade_counts_enable_get(struct counter_device *counter,
u8 *cascade_enable)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
unsigned long tmdr;
int ret;
ret = rz_mtu3_lock_if_ch0_is_enabled(priv);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3);
pm_runtime_put(counter->parent);
*cascade_enable = test_bit(RZ_MTU3_TMDR3_LWA, &tmdr);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_cascade_counts_enable_set(struct counter_device *counter,
u8 cascade_enable)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_ch0_is_enabled(priv);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
rz_mtu3_shared_reg_update_bit(priv->ch, RZ_MTU3_TMDR3,
RZ_MTU3_TMDR3_LWA, cascade_enable);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_ext_input_phase_clock_select_get(struct counter_device *counter,
u32 *ext_input_phase_clock_select)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
unsigned long tmdr;
int ret;
ret = rz_mtu3_lock_if_ch0_is_enabled(priv);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3);
pm_runtime_put(counter->parent);
*ext_input_phase_clock_select = test_bit(RZ_MTU3_TMDR3_PHCKSEL, &tmdr);
mutex_unlock(&priv->lock);
return 0;
}
static int rz_mtu3_ext_input_phase_clock_select_set(struct counter_device *counter,
u32 ext_input_phase_clock_select)
{
struct rz_mtu3_cnt *const priv = counter_priv(counter);
int ret;
ret = rz_mtu3_lock_if_ch0_is_enabled(priv);
if (ret)
return ret;
pm_runtime_get_sync(counter->parent);
rz_mtu3_shared_reg_update_bit(priv->ch, RZ_MTU3_TMDR3,
RZ_MTU3_TMDR3_PHCKSEL,
ext_input_phase_clock_select);
pm_runtime_put(counter->parent);
mutex_unlock(&priv->lock);
return 0;
}
static struct counter_comp rz_mtu3_count_ext[] = {
COUNTER_COMP_DIRECTION(rz_mtu3_count_direction_read),
COUNTER_COMP_ENABLE(rz_mtu3_count_enable_read,
rz_mtu3_count_enable_write),
COUNTER_COMP_CEILING(rz_mtu3_count_ceiling_read,
rz_mtu3_count_ceiling_write),
};
static const enum counter_synapse_action rz_mtu3_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
COUNTER_SYNAPSE_ACTION_NONE,
};
static int rz_mtu3_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
const bool is_signal_ab = (synapse->signal->id == SIGNAL_A_ID) ||
(synapse->signal->id == SIGNAL_B_ID);
struct rz_mtu3_channel *const ch = rz_mtu3_get_ch(counter, count->id);
struct rz_mtu3_cnt *const priv = counter_priv(counter);
enum counter_function function;
bool mtclkc_mtclkd;
unsigned long tmdr;
int ret;
ret = rz_mtu3_lock_if_count_is_enabled(ch, priv, count->id);
if (ret)
return ret;
ret = rz_mtu3_count_function_read_helper(ch, counter, &function);
if (ret) {
mutex_unlock(&priv->lock);
return ret;
}
/* Default action mode */
*action = COUNTER_SYNAPSE_ACTION_NONE;
if (count->id != RZ_MTU3_16_BIT_MTU1_CH) {
tmdr = rz_mtu3_shared_reg_read(priv->ch, RZ_MTU3_TMDR3);
mtclkc_mtclkd = test_bit(RZ_MTU3_TMDR3_PHCKSEL, &tmdr);
if ((mtclkc_mtclkd && is_signal_ab) ||
(!mtclkc_mtclkd && !is_signal_ab)) {
mutex_unlock(&priv->lock);
return 0;
}
}
switch (function) {
case COUNTER_FUNCTION_PULSE_DIRECTION:
/*
* Rising edges on signal A (signal C) updates the respective
* count. The input level of signal B (signal D) determines
* direction.
*/
if (synapse->signal->id == SIGNAL_A_ID ||
synapse->signal->id == SIGNAL_C_ID)
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
/*
* Any state transition on quadrature pair signal B (signal D)
* updates the respective count.
*/
if (synapse->signal->id == SIGNAL_B_ID ||
synapse->signal->id == SIGNAL_D_ID)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
break;
case COUNTER_FUNCTION_QUADRATURE_X4:
/* counts up/down on both edges of A (C) and B (D) signal */
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
break;
default:
/* should never reach this path */
mutex_unlock(&priv->lock);
return -EINVAL;
}
mutex_unlock(&priv->lock);
return 0;
}
static const struct counter_ops rz_mtu3_cnt_ops = {
.count_read = rz_mtu3_count_read,
.count_write = rz_mtu3_count_write,
.function_read = rz_mtu3_count_function_read,
.function_write = rz_mtu3_count_function_write,
.action_read = rz_mtu3_action_read,
};
#define RZ_MTU3_PHASE_SIGNAL(_id, _name) { \
.id = (_id), \
.name = (_name), \
}
static struct counter_signal rz_mtu3_signals[] = {
RZ_MTU3_PHASE_SIGNAL(SIGNAL_A_ID, "MTU1 MTCLKA"),
RZ_MTU3_PHASE_SIGNAL(SIGNAL_B_ID, "MTU1 MTCLKB"),
RZ_MTU3_PHASE_SIGNAL(SIGNAL_C_ID, "MTU2 MTCLKC"),
RZ_MTU3_PHASE_SIGNAL(SIGNAL_D_ID, "MTU2 MTCLKD"),
};
static struct counter_synapse rz_mtu3_mtu1_count_synapses[] = {
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals,
},
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals + 1,
}
};
static struct counter_synapse rz_mtu3_mtu2_count_synapses[] = {
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals,
},
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals + 1,
},
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals + 2,
},
{
.actions_list = rz_mtu3_synapse_actions,
.num_actions = ARRAY_SIZE(rz_mtu3_synapse_actions),
.signal = rz_mtu3_signals + 3,
}
};
static struct counter_count rz_mtu3_counts[] = {
{
.id = RZ_MTU3_16_BIT_MTU1_CH,
.name = "Channel 1 Count",
.functions_list = rz_mtu3_count_functions,
.num_functions = ARRAY_SIZE(rz_mtu3_count_functions),
.synapses = rz_mtu3_mtu1_count_synapses,
.num_synapses = ARRAY_SIZE(rz_mtu3_mtu1_count_synapses),
.ext = rz_mtu3_count_ext,
.num_ext = ARRAY_SIZE(rz_mtu3_count_ext),
},
{
.id = RZ_MTU3_16_BIT_MTU2_CH,
.name = "Channel 2 Count",
.functions_list = rz_mtu3_count_functions,
.num_functions = ARRAY_SIZE(rz_mtu3_count_functions),
.synapses = rz_mtu3_mtu2_count_synapses,
.num_synapses = ARRAY_SIZE(rz_mtu3_mtu2_count_synapses),
.ext = rz_mtu3_count_ext,
.num_ext = ARRAY_SIZE(rz_mtu3_count_ext),
},
{
.id = RZ_MTU3_32_BIT_CH,
.name = "Channel 1 and 2 (cascaded) Count",
.functions_list = rz_mtu3_count_functions,
.num_functions = ARRAY_SIZE(rz_mtu3_count_functions),
.synapses = rz_mtu3_mtu2_count_synapses,
.num_synapses = ARRAY_SIZE(rz_mtu3_mtu2_count_synapses),
.ext = rz_mtu3_count_ext,
.num_ext = ARRAY_SIZE(rz_mtu3_count_ext),
}
};
static const char *const rz_mtu3_ext_input_phase_clock_select[] = {
"MTCLKA-MTCLKB",
"MTCLKC-MTCLKD",
};
static DEFINE_COUNTER_ENUM(rz_mtu3_ext_input_phase_clock_select_enum,
rz_mtu3_ext_input_phase_clock_select);
static struct counter_comp rz_mtu3_device_ext[] = {
COUNTER_COMP_DEVICE_BOOL("cascade_counts_enable",
rz_mtu3_cascade_counts_enable_get,
rz_mtu3_cascade_counts_enable_set),
COUNTER_COMP_DEVICE_ENUM("external_input_phase_clock_select",
rz_mtu3_ext_input_phase_clock_select_get,
rz_mtu3_ext_input_phase_clock_select_set,
rz_mtu3_ext_input_phase_clock_select_enum),
};
static int rz_mtu3_cnt_pm_runtime_suspend(struct device *dev)
{
struct clk *const clk = dev_get_drvdata(dev);
clk_disable_unprepare(clk);
return 0;
}
static int rz_mtu3_cnt_pm_runtime_resume(struct device *dev)
{
struct clk *const clk = dev_get_drvdata(dev);
clk_prepare_enable(clk);
return 0;
}
static DEFINE_RUNTIME_DEV_PM_OPS(rz_mtu3_cnt_pm_ops,
rz_mtu3_cnt_pm_runtime_suspend,
rz_mtu3_cnt_pm_runtime_resume, NULL);
static void rz_mtu3_cnt_pm_disable(void *data)
{
struct device *dev = data;
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
}
static int rz_mtu3_cnt_probe(struct platform_device *pdev)
{
struct rz_mtu3 *ddata = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct counter_device *counter;
struct rz_mtu3_channel *ch;
struct rz_mtu3_cnt *priv;
unsigned int i;
int ret;
counter = devm_counter_alloc(dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
priv->clk = ddata->clk;
priv->mtu_32bit_max = U32_MAX;
priv->ch = &ddata->channels[RZ_MTU3_CHAN_1];
ch = &priv->ch[0];
for (i = 0; i < RZ_MTU3_MAX_HW_CNTR_CHANNELS; i++) {
ch->dev = dev;
priv->mtu_16bit_max[i] = U16_MAX;
ch++;
}
mutex_init(&priv->lock);
platform_set_drvdata(pdev, priv->clk);
clk_prepare_enable(priv->clk);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
ret = devm_add_action_or_reset(&pdev->dev, rz_mtu3_cnt_pm_disable, dev);
if (ret < 0)
goto disable_clock;
counter->name = dev_name(dev);
counter->parent = dev;
counter->ops = &rz_mtu3_cnt_ops;
counter->counts = rz_mtu3_counts;
counter->num_counts = ARRAY_SIZE(rz_mtu3_counts);
counter->signals = rz_mtu3_signals;
counter->num_signals = ARRAY_SIZE(rz_mtu3_signals);
counter->ext = rz_mtu3_device_ext;
counter->num_ext = ARRAY_SIZE(rz_mtu3_device_ext);
/* Register Counter device */
ret = devm_counter_add(dev, counter);
if (ret < 0) {
dev_err_probe(dev, ret, "Failed to add counter\n");
goto disable_clock;
}
return 0;
disable_clock:
clk_disable_unprepare(priv->clk);
return ret;
}
static struct platform_driver rz_mtu3_cnt_driver = {
.probe = rz_mtu3_cnt_probe,
.driver = {
.name = "rz-mtu3-counter",
.pm = pm_ptr(&rz_mtu3_cnt_pm_ops),
},
};
module_platform_driver(rz_mtu3_cnt_driver);
MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
MODULE_ALIAS("platform:rz-mtu3-counter");
MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a counter driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("COUNTER");
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0
/*
* STM32 Low-Power Timer Encoder and Counter driver
*
* Copyright (C) STMicroelectronics 2017
*
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>
*
* Inspired by 104-quad-8 and stm32-timer-trigger drivers.
*
*/
#include <linux/bitfield.h>
#include <linux/counter.h>
#include <linux/mfd/stm32-lptimer.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/types.h>
struct stm32_lptim_cnt {
struct device *dev;
struct regmap *regmap;
struct clk *clk;
u32 ceiling;
u32 polarity;
u32 quadrature_mode;
bool enabled;
};
static int stm32_lptim_is_enabled(struct stm32_lptim_cnt *priv)
{
u32 val;
int ret;
ret = regmap_read(priv->regmap, STM32_LPTIM_CR, &val);
if (ret)
return ret;
return FIELD_GET(STM32_LPTIM_ENABLE, val);
}
static int stm32_lptim_set_enable_state(struct stm32_lptim_cnt *priv,
int enable)
{
int ret;
u32 val;
val = FIELD_PREP(STM32_LPTIM_ENABLE, enable);
ret = regmap_write(priv->regmap, STM32_LPTIM_CR, val);
if (ret)
return ret;
if (!enable) {
clk_disable(priv->clk);
priv->enabled = false;
return 0;
}
ret = clk_enable(priv->clk);
if (ret)
goto disable_cnt;
/* LP timer must be enabled before writing CMP & ARR */
ret = regmap_write(priv->regmap, STM32_LPTIM_ARR, priv->ceiling);
if (ret)
goto disable_clk;
ret = regmap_write(priv->regmap, STM32_LPTIM_CMP, 0);
if (ret)
goto disable_clk;
/* ensure CMP & ARR registers are properly written */
ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val,
(val & STM32_LPTIM_CMPOK_ARROK) == STM32_LPTIM_CMPOK_ARROK,
100, 1000);
if (ret)
goto disable_clk;
ret = regmap_write(priv->regmap, STM32_LPTIM_ICR,
STM32_LPTIM_CMPOKCF_ARROKCF);
if (ret)
goto disable_clk;
priv->enabled = true;
/* Start LP timer in continuous mode */
return regmap_update_bits(priv->regmap, STM32_LPTIM_CR,
STM32_LPTIM_CNTSTRT, STM32_LPTIM_CNTSTRT);
disable_clk:
clk_disable(priv->clk);
disable_cnt:
regmap_write(priv->regmap, STM32_LPTIM_CR, 0);
return ret;
}
static int stm32_lptim_setup(struct stm32_lptim_cnt *priv, int enable)
{
u32 mask = STM32_LPTIM_ENC | STM32_LPTIM_COUNTMODE |
STM32_LPTIM_CKPOL | STM32_LPTIM_PRESC;
u32 val;
/* Setup LP timer encoder/counter and polarity, without prescaler */
if (priv->quadrature_mode)
val = enable ? STM32_LPTIM_ENC : 0;
else
val = enable ? STM32_LPTIM_COUNTMODE : 0;
val |= FIELD_PREP(STM32_LPTIM_CKPOL, enable ? priv->polarity : 0);
return regmap_update_bits(priv->regmap, STM32_LPTIM_CFGR, mask, val);
}
/*
* In non-quadrature mode, device counts up on active edge.
* In quadrature mode, encoder counting scenarios are as follows:
* +---------+----------+--------------------+--------------------+
* | Active | Level on | IN1 signal | IN2 signal |
* | edge | opposite +----------+---------+----------+---------+
* | | signal | Rising | Falling | Rising | Falling |
* +---------+----------+----------+---------+----------+---------+
* | Rising | High -> | Down | - | Up | - |
* | edge | Low -> | Up | - | Down | - |
* +---------+----------+----------+---------+----------+---------+
* | Falling | High -> | - | Up | - | Down |
* | edge | Low -> | - | Down | - | Up |
* +---------+----------+----------+---------+----------+---------+
* | Both | High -> | Down | Up | Up | Down |
* | edges | Low -> | Up | Down | Down | Up |
* +---------+----------+----------+---------+----------+---------+
*/
static const enum counter_function stm32_lptim_cnt_functions[] = {
COUNTER_FUNCTION_INCREASE,
COUNTER_FUNCTION_QUADRATURE_X4,
};
static const enum counter_synapse_action stm32_lptim_cnt_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
COUNTER_SYNAPSE_ACTION_NONE,
};
static int stm32_lptim_cnt_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
u32 cnt;
int ret;
ret = regmap_read(priv->regmap, STM32_LPTIM_CNT, &cnt);
if (ret)
return ret;
*val = cnt;
return 0;
}
static int stm32_lptim_cnt_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
if (!priv->quadrature_mode) {
*function = COUNTER_FUNCTION_INCREASE;
return 0;
}
if (priv->polarity == STM32_LPTIM_CKPOL_BOTH_EDGES) {
*function = COUNTER_FUNCTION_QUADRATURE_X4;
return 0;
}
return -EINVAL;
}
static int stm32_lptim_cnt_function_write(struct counter_device *counter,
struct counter_count *count,
enum counter_function function)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
if (stm32_lptim_is_enabled(priv))
return -EBUSY;
switch (function) {
case COUNTER_FUNCTION_INCREASE:
priv->quadrature_mode = 0;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X4:
priv->quadrature_mode = 1;
priv->polarity = STM32_LPTIM_CKPOL_BOTH_EDGES;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
}
static int stm32_lptim_cnt_enable_read(struct counter_device *counter,
struct counter_count *count,
u8 *enable)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
int ret;
ret = stm32_lptim_is_enabled(priv);
if (ret < 0)
return ret;
*enable = ret;
return 0;
}
static int stm32_lptim_cnt_enable_write(struct counter_device *counter,
struct counter_count *count,
u8 enable)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
int ret;
/* Check nobody uses the timer, or already disabled/enabled */
ret = stm32_lptim_is_enabled(priv);
if ((ret < 0) || (!ret && !enable))
return ret;
if (enable && ret)
return -EBUSY;
ret = stm32_lptim_setup(priv, enable);
if (ret)
return ret;
ret = stm32_lptim_set_enable_state(priv, enable);
if (ret)
return ret;
return 0;
}
static int stm32_lptim_cnt_ceiling_read(struct counter_device *counter,
struct counter_count *count,
u64 *ceiling)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
*ceiling = priv->ceiling;
return 0;
}
static int stm32_lptim_cnt_ceiling_write(struct counter_device *counter,
struct counter_count *count,
u64 ceiling)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
if (stm32_lptim_is_enabled(priv))
return -EBUSY;
if (ceiling > STM32_LPTIM_MAX_ARR)
return -ERANGE;
priv->ceiling = ceiling;
return 0;
}
static struct counter_comp stm32_lptim_cnt_ext[] = {
COUNTER_COMP_ENABLE(stm32_lptim_cnt_enable_read,
stm32_lptim_cnt_enable_write),
COUNTER_COMP_CEILING(stm32_lptim_cnt_ceiling_read,
stm32_lptim_cnt_ceiling_write),
};
static int stm32_lptim_cnt_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
enum counter_function function;
int err;
err = stm32_lptim_cnt_function_read(counter, count, &function);
if (err)
return err;
switch (function) {
case COUNTER_FUNCTION_INCREASE:
/* LP Timer acts as up-counter on input 1 */
if (synapse->signal->id != count->synapses[0].signal->id) {
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
}
switch (priv->polarity) {
case STM32_LPTIM_CKPOL_RISING_EDGE:
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
return 0;
case STM32_LPTIM_CKPOL_FALLING_EDGE:
*action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
return 0;
case STM32_LPTIM_CKPOL_BOTH_EDGES:
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
case COUNTER_FUNCTION_QUADRATURE_X4:
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
}
static int stm32_lptim_cnt_action_write(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action action)
{
struct stm32_lptim_cnt *const priv = counter_priv(counter);
enum counter_function function;
int err;
if (stm32_lptim_is_enabled(priv))
return -EBUSY;
err = stm32_lptim_cnt_function_read(counter, count, &function);
if (err)
return err;
/* only set polarity when in counter mode (on input 1) */
if (function != COUNTER_FUNCTION_INCREASE
|| synapse->signal->id != count->synapses[0].signal->id)
return -EINVAL;
switch (action) {
case COUNTER_SYNAPSE_ACTION_RISING_EDGE:
priv->polarity = STM32_LPTIM_CKPOL_RISING_EDGE;
return 0;
case COUNTER_SYNAPSE_ACTION_FALLING_EDGE:
priv->polarity = STM32_LPTIM_CKPOL_FALLING_EDGE;
return 0;
case COUNTER_SYNAPSE_ACTION_BOTH_EDGES:
priv->polarity = STM32_LPTIM_CKPOL_BOTH_EDGES;
return 0;
default:
return -EINVAL;
}
}
static const struct counter_ops stm32_lptim_cnt_ops = {
.count_read = stm32_lptim_cnt_read,
.function_read = stm32_lptim_cnt_function_read,
.function_write = stm32_lptim_cnt_function_write,
.action_read = stm32_lptim_cnt_action_read,
.action_write = stm32_lptim_cnt_action_write,
};
static struct counter_signal stm32_lptim_cnt_signals[] = {
{
.id = 0,
.name = "Channel 1 Quadrature A"
},
{
.id = 1,
.name = "Channel 1 Quadrature B"
}
};
static struct counter_synapse stm32_lptim_cnt_synapses[] = {
{
.actions_list = stm32_lptim_cnt_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_lptim_cnt_synapse_actions),
.signal = &stm32_lptim_cnt_signals[0]
},
{
.actions_list = stm32_lptim_cnt_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_lptim_cnt_synapse_actions),
.signal = &stm32_lptim_cnt_signals[1]
}
};
/* LP timer with encoder */
static struct counter_count stm32_lptim_enc_counts = {
.id = 0,
.name = "LPTimer Count",
.functions_list = stm32_lptim_cnt_functions,
.num_functions = ARRAY_SIZE(stm32_lptim_cnt_functions),
.synapses = stm32_lptim_cnt_synapses,
.num_synapses = ARRAY_SIZE(stm32_lptim_cnt_synapses),
.ext = stm32_lptim_cnt_ext,
.num_ext = ARRAY_SIZE(stm32_lptim_cnt_ext)
};
/* LP timer without encoder (counter only) */
static struct counter_count stm32_lptim_in1_counts = {
.id = 0,
.name = "LPTimer Count",
.functions_list = stm32_lptim_cnt_functions,
.num_functions = 1,
.synapses = stm32_lptim_cnt_synapses,
.num_synapses = 1,
.ext = stm32_lptim_cnt_ext,
.num_ext = ARRAY_SIZE(stm32_lptim_cnt_ext)
};
static int stm32_lptim_cnt_probe(struct platform_device *pdev)
{
struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent);
struct counter_device *counter;
struct stm32_lptim_cnt *priv;
int ret;
if (IS_ERR_OR_NULL(ddata))
return -EINVAL;
counter = devm_counter_alloc(&pdev->dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
priv->dev = &pdev->dev;
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
priv->ceiling = STM32_LPTIM_MAX_ARR;
/* Initialize Counter device */
counter->name = dev_name(&pdev->dev);
counter->parent = &pdev->dev;
counter->ops = &stm32_lptim_cnt_ops;
if (ddata->has_encoder) {
counter->counts = &stm32_lptim_enc_counts;
counter->num_signals = ARRAY_SIZE(stm32_lptim_cnt_signals);
} else {
counter->counts = &stm32_lptim_in1_counts;
counter->num_signals = 1;
}
counter->num_counts = 1;
counter->signals = stm32_lptim_cnt_signals;
platform_set_drvdata(pdev, priv);
ret = devm_counter_add(&pdev->dev, counter);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int stm32_lptim_cnt_suspend(struct device *dev)
{
struct stm32_lptim_cnt *priv = dev_get_drvdata(dev);
int ret;
/* Only take care of enabled counter: don't disturb other MFD child */
if (priv->enabled) {
ret = stm32_lptim_setup(priv, 0);
if (ret)
return ret;
ret = stm32_lptim_set_enable_state(priv, 0);
if (ret)
return ret;
/* Force enable state for later resume */
priv->enabled = true;
}
return pinctrl_pm_select_sleep_state(dev);
}
static int stm32_lptim_cnt_resume(struct device *dev)
{
struct stm32_lptim_cnt *priv = dev_get_drvdata(dev);
int ret;
ret = pinctrl_pm_select_default_state(dev);
if (ret)
return ret;
if (priv->enabled) {
priv->enabled = false;
ret = stm32_lptim_setup(priv, 1);
if (ret)
return ret;
ret = stm32_lptim_set_enable_state(priv, 1);
if (ret)
return ret;
}
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(stm32_lptim_cnt_pm_ops, stm32_lptim_cnt_suspend,
stm32_lptim_cnt_resume);
static const struct of_device_id stm32_lptim_cnt_of_match[] = {
{ .compatible = "st,stm32-lptimer-counter", },
{},
};
MODULE_DEVICE_TABLE(of, stm32_lptim_cnt_of_match);
static struct platform_driver stm32_lptim_cnt_driver = {
.probe = stm32_lptim_cnt_probe,
.driver = {
.name = "stm32-lptimer-counter",
.of_match_table = stm32_lptim_cnt_of_match,
.pm = &stm32_lptim_cnt_pm_ops,
},
};
module_platform_driver(stm32_lptim_cnt_driver);
MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
MODULE_ALIAS("platform:stm32-lptimer-counter");
MODULE_DESCRIPTION("STMicroelectronics STM32 LPTIM counter driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("COUNTER");
@@ -0,0 +1,869 @@
// SPDX-License-Identifier: GPL-2.0
/*
* STM32 Timer Encoder and Counter driver
*
* Copyright (C) STMicroelectronics 2018
*
* Author: Benjamin Gaignard <benjamin.gaignard@st.com>
*
*/
#include <linux/counter.h>
#include <linux/interrupt.h>
#include <linux/mfd/stm32-timers.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#define TIM_CCMR_CCXS (BIT(8) | BIT(0))
#define TIM_CCMR_MASK (TIM_CCMR_CC1S | TIM_CCMR_CC2S | \
TIM_CCMR_IC1F | TIM_CCMR_IC2F)
#define TIM_CCER_MASK (TIM_CCER_CC1P | TIM_CCER_CC1NP | \
TIM_CCER_CC2P | TIM_CCER_CC2NP)
#define STM32_CH1_SIG 0
#define STM32_CH2_SIG 1
#define STM32_CLOCK_SIG 2
#define STM32_CH3_SIG 3
#define STM32_CH4_SIG 4
struct stm32_timer_regs {
u32 cr1;
u32 cnt;
u32 smcr;
u32 arr;
};
struct stm32_timer_cnt {
struct regmap *regmap;
struct clk *clk;
u32 max_arr;
bool enabled;
struct stm32_timer_regs bak;
bool has_encoder;
unsigned int nchannels;
unsigned int nr_irqs;
spinlock_t lock; /* protects nb_ovf */
u64 nb_ovf;
};
static const enum counter_function stm32_count_functions[] = {
COUNTER_FUNCTION_INCREASE,
COUNTER_FUNCTION_QUADRATURE_X2_A,
COUNTER_FUNCTION_QUADRATURE_X2_B,
COUNTER_FUNCTION_QUADRATURE_X4,
};
static int stm32_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 cnt;
regmap_read(priv->regmap, TIM_CNT, &cnt);
*val = cnt;
return 0;
}
static int stm32_count_write(struct counter_device *counter,
struct counter_count *count, const u64 val)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 ceiling;
regmap_read(priv->regmap, TIM_ARR, &ceiling);
if (val > ceiling)
return -EINVAL;
return regmap_write(priv->regmap, TIM_CNT, val);
}
static int stm32_count_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 smcr;
regmap_read(priv->regmap, TIM_SMCR, &smcr);
switch (smcr & TIM_SMCR_SMS) {
case TIM_SMCR_SMS_SLAVE_MODE_DISABLED:
*function = COUNTER_FUNCTION_INCREASE;
return 0;
case TIM_SMCR_SMS_ENCODER_MODE_1:
*function = COUNTER_FUNCTION_QUADRATURE_X2_A;
return 0;
case TIM_SMCR_SMS_ENCODER_MODE_2:
*function = COUNTER_FUNCTION_QUADRATURE_X2_B;
return 0;
case TIM_SMCR_SMS_ENCODER_MODE_3:
*function = COUNTER_FUNCTION_QUADRATURE_X4;
return 0;
default:
return -EINVAL;
}
}
static int stm32_count_function_write(struct counter_device *counter,
struct counter_count *count,
enum counter_function function)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 cr1, sms;
switch (function) {
case COUNTER_FUNCTION_INCREASE:
sms = TIM_SMCR_SMS_SLAVE_MODE_DISABLED;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_A:
if (!priv->has_encoder)
return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_1;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
if (!priv->has_encoder)
return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_2;
break;
case COUNTER_FUNCTION_QUADRATURE_X4:
if (!priv->has_encoder)
return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_3;
break;
default:
return -EINVAL;
}
/* Store enable status */
regmap_read(priv->regmap, TIM_CR1, &cr1);
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms);
/* Make sure that registers are updated */
regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG);
/* Restore the enable status */
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, cr1);
return 0;
}
static int stm32_count_direction_read(struct counter_device *counter,
struct counter_count *count,
enum counter_count_direction *direction)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 cr1;
regmap_read(priv->regmap, TIM_CR1, &cr1);
*direction = (cr1 & TIM_CR1_DIR) ? COUNTER_COUNT_DIRECTION_BACKWARD :
COUNTER_COUNT_DIRECTION_FORWARD;
return 0;
}
static int stm32_count_ceiling_read(struct counter_device *counter,
struct counter_count *count, u64 *ceiling)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 arr;
regmap_read(priv->regmap, TIM_ARR, &arr);
*ceiling = arr;
return 0;
}
static int stm32_count_ceiling_write(struct counter_device *counter,
struct counter_count *count, u64 ceiling)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
if (ceiling > priv->max_arr)
return -ERANGE;
/* TIMx_ARR register shouldn't be buffered (ARPE=0) */
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, 0);
regmap_write(priv->regmap, TIM_ARR, ceiling);
return 0;
}
static int stm32_count_enable_read(struct counter_device *counter,
struct counter_count *count, u8 *enable)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 cr1;
regmap_read(priv->regmap, TIM_CR1, &cr1);
*enable = cr1 & TIM_CR1_CEN;
return 0;
}
static int stm32_count_enable_write(struct counter_device *counter,
struct counter_count *count, u8 enable)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 cr1;
int ret;
if (enable) {
regmap_read(priv->regmap, TIM_CR1, &cr1);
if (!(cr1 & TIM_CR1_CEN)) {
ret = clk_enable(priv->clk);
if (ret) {
dev_err(counter->parent, "Cannot enable clock %d\n", ret);
return ret;
}
}
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN,
TIM_CR1_CEN);
} else {
regmap_read(priv->regmap, TIM_CR1, &cr1);
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
if (cr1 & TIM_CR1_CEN)
clk_disable(priv->clk);
}
/* Keep enabled state to properly handle low power states */
priv->enabled = enable;
return 0;
}
static int stm32_count_prescaler_read(struct counter_device *counter,
struct counter_count *count, u64 *prescaler)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 psc;
regmap_read(priv->regmap, TIM_PSC, &psc);
*prescaler = psc + 1;
return 0;
}
static int stm32_count_prescaler_write(struct counter_device *counter,
struct counter_count *count, u64 prescaler)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 psc;
if (!prescaler || prescaler > MAX_TIM_PSC + 1)
return -ERANGE;
psc = prescaler - 1;
return regmap_write(priv->regmap, TIM_PSC, psc);
}
static int stm32_count_cap_read(struct counter_device *counter,
struct counter_count *count,
size_t ch, u64 *cap)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 ccrx;
if (ch >= priv->nchannels)
return -EOPNOTSUPP;
switch (ch) {
case 0:
regmap_read(priv->regmap, TIM_CCR1, &ccrx);
break;
case 1:
regmap_read(priv->regmap, TIM_CCR2, &ccrx);
break;
case 2:
regmap_read(priv->regmap, TIM_CCR3, &ccrx);
break;
case 3:
regmap_read(priv->regmap, TIM_CCR4, &ccrx);
break;
default:
return -EINVAL;
}
dev_dbg(counter->parent, "CCR%zu: 0x%08x\n", ch + 1, ccrx);
*cap = ccrx;
return 0;
}
static int stm32_count_nb_ovf_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
unsigned long irqflags;
spin_lock_irqsave(&priv->lock, irqflags);
*val = priv->nb_ovf;
spin_unlock_irqrestore(&priv->lock, irqflags);
return 0;
}
static int stm32_count_nb_ovf_write(struct counter_device *counter,
struct counter_count *count, u64 val)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
unsigned long irqflags;
spin_lock_irqsave(&priv->lock, irqflags);
priv->nb_ovf = val;
spin_unlock_irqrestore(&priv->lock, irqflags);
return 0;
}
static DEFINE_COUNTER_ARRAY_CAPTURE(stm32_count_cap_array, 4);
static struct counter_comp stm32_count_ext[] = {
COUNTER_COMP_DIRECTION(stm32_count_direction_read),
COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write),
COUNTER_COMP_CEILING(stm32_count_ceiling_read,
stm32_count_ceiling_write),
COUNTER_COMP_COUNT_U64("prescaler", stm32_count_prescaler_read,
stm32_count_prescaler_write),
COUNTER_COMP_ARRAY_CAPTURE(stm32_count_cap_read, NULL, stm32_count_cap_array),
COUNTER_COMP_COUNT_U64("num_overflows", stm32_count_nb_ovf_read, stm32_count_nb_ovf_write),
};
static const enum counter_synapse_action stm32_clock_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
};
static const enum counter_synapse_action stm32_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_NONE,
COUNTER_SYNAPSE_ACTION_BOTH_EDGES
};
static int stm32_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
enum counter_function function;
int err;
err = stm32_count_function_read(counter, count, &function);
if (err)
return err;
switch (function) {
case COUNTER_FUNCTION_INCREASE:
/* counts on internal clock when CEN=1 */
if (synapse->signal->id == STM32_CLOCK_SIG)
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X2_A:
/* counts up/down on TI1FP1 edge depending on TI2FP2 level */
if (synapse->signal->id == STM32_CH1_SIG)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
/* counts up/down on TI2FP2 edge depending on TI1FP1 level */
if (synapse->signal->id == STM32_CH2_SIG)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X4:
/* counts up/down on both TI1FP1 and TI2FP2 edges */
if (synapse->signal->id == STM32_CH1_SIG || synapse->signal->id == STM32_CH2_SIG)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
default:
return -EINVAL;
}
}
struct stm32_count_cc_regs {
u32 ccmr_reg;
u32 ccmr_mask;
u32 ccmr_bits;
u32 ccer_bits;
};
static const struct stm32_count_cc_regs stm32_cc[] = {
{ TIM_CCMR1, TIM_CCMR_CC1S, TIM_CCMR_CC1S_TI1,
TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC1NP },
{ TIM_CCMR1, TIM_CCMR_CC2S, TIM_CCMR_CC2S_TI2,
TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC2NP },
{ TIM_CCMR2, TIM_CCMR_CC3S, TIM_CCMR_CC3S_TI3,
TIM_CCER_CC3E | TIM_CCER_CC3P | TIM_CCER_CC3NP },
{ TIM_CCMR2, TIM_CCMR_CC4S, TIM_CCMR_CC4S_TI4,
TIM_CCER_CC4E | TIM_CCER_CC4P | TIM_CCER_CC4NP },
};
static int stm32_count_capture_configure(struct counter_device *counter, unsigned int ch,
bool enable)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
const struct stm32_count_cc_regs *cc;
u32 ccmr, ccer;
if (ch >= ARRAY_SIZE(stm32_cc) || ch >= priv->nchannels) {
dev_err(counter->parent, "invalid ch: %d\n", ch);
return -EINVAL;
}
cc = &stm32_cc[ch];
/*
* configure channel in input capture mode, map channel 1 on TI1, channel2 on TI2...
* Select both edges / non-inverted to trigger a capture.
*/
if (enable) {
/* first clear possibly latched capture flag upon enabling */
if (!regmap_test_bits(priv->regmap, TIM_CCER, cc->ccer_bits))
regmap_write(priv->regmap, TIM_SR, ~TIM_SR_CC_IF(ch));
regmap_update_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask,
cc->ccmr_bits);
regmap_set_bits(priv->regmap, TIM_CCER, cc->ccer_bits);
} else {
regmap_clear_bits(priv->regmap, TIM_CCER, cc->ccer_bits);
regmap_clear_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask);
}
regmap_read(priv->regmap, cc->ccmr_reg, &ccmr);
regmap_read(priv->regmap, TIM_CCER, &ccer);
dev_dbg(counter->parent, "%s(%s) ch%d 0x%08x 0x%08x\n", __func__, enable ? "ena" : "dis",
ch, ccmr, ccer);
return 0;
}
static int stm32_count_events_configure(struct counter_device *counter)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
struct counter_event_node *event_node;
u32 dier = 0;
int i, ret;
list_for_each_entry(event_node, &counter->events_list, l) {
switch (event_node->event) {
case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
/* first clear possibly latched UIF before enabling */
if (!regmap_test_bits(priv->regmap, TIM_DIER, TIM_DIER_UIE))
regmap_write(priv->regmap, TIM_SR, (u32)~TIM_SR_UIF);
dier |= TIM_DIER_UIE;
break;
case COUNTER_EVENT_CAPTURE:
ret = stm32_count_capture_configure(counter, event_node->channel, true);
if (ret)
return ret;
dier |= TIM_DIER_CCxIE(event_node->channel + 1);
break;
default:
/* should never reach this path */
return -EINVAL;
}
}
/* Enable / disable all events at once, from events_list, so write all DIER bits */
regmap_write(priv->regmap, TIM_DIER, dier);
/* check for disabled capture events */
for (i = 0 ; i < priv->nchannels; i++) {
if (!(dier & TIM_DIER_CCxIE(i + 1))) {
ret = stm32_count_capture_configure(counter, i, false);
if (ret)
return ret;
}
}
return 0;
}
static int stm32_count_watch_validate(struct counter_device *counter,
const struct counter_watch *watch)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
/* Interrupts are optional */
if (!priv->nr_irqs)
return -EOPNOTSUPP;
switch (watch->event) {
case COUNTER_EVENT_CAPTURE:
if (watch->channel >= priv->nchannels) {
dev_err(counter->parent, "Invalid channel %d\n", watch->channel);
return -EINVAL;
}
return 0;
case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
return 0;
default:
return -EINVAL;
}
}
static const struct counter_ops stm32_timer_cnt_ops = {
.count_read = stm32_count_read,
.count_write = stm32_count_write,
.function_read = stm32_count_function_read,
.function_write = stm32_count_function_write,
.action_read = stm32_action_read,
.events_configure = stm32_count_events_configure,
.watch_validate = stm32_count_watch_validate,
};
static int stm32_count_clk_get_freq(struct counter_device *counter,
struct counter_signal *signal, u64 *freq)
{
struct stm32_timer_cnt *const priv = counter_priv(counter);
*freq = clk_get_rate(priv->clk);
return 0;
}
static struct counter_comp stm32_count_clock_ext[] = {
COUNTER_COMP_FREQUENCY(stm32_count_clk_get_freq),
};
static struct counter_signal stm32_signals[] = {
/*
* Need to declare all the signals as a static array, and keep the signals order here,
* even if they're unused or unexisting on some timer instances. It's an abstraction,
* e.g. high level view of the counter features.
*
* Userspace programs may rely on signal0 to be "Channel 1", signal1 to be "Channel 2",
* and so on. When a signal is unexisting, the COUNTER_SYNAPSE_ACTION_NONE can be used,
* to indicate that a signal doesn't affect the counter.
*/
{
.id = STM32_CH1_SIG,
.name = "Channel 1"
},
{
.id = STM32_CH2_SIG,
.name = "Channel 2"
},
{
.id = STM32_CLOCK_SIG,
.name = "Clock",
.ext = stm32_count_clock_ext,
.num_ext = ARRAY_SIZE(stm32_count_clock_ext),
},
{
.id = STM32_CH3_SIG,
.name = "Channel 3"
},
{
.id = STM32_CH4_SIG,
.name = "Channel 4"
},
};
static struct counter_synapse stm32_count_synapses[] = {
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
.signal = &stm32_signals[STM32_CH1_SIG]
},
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
.signal = &stm32_signals[STM32_CH2_SIG]
},
{
.actions_list = stm32_clock_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_clock_synapse_actions),
.signal = &stm32_signals[STM32_CLOCK_SIG]
},
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
.signal = &stm32_signals[STM32_CH3_SIG]
},
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
.signal = &stm32_signals[STM32_CH4_SIG]
},
};
static struct counter_count stm32_counts = {
.id = 0,
.name = "STM32 Timer Counter",
.functions_list = stm32_count_functions,
.num_functions = ARRAY_SIZE(stm32_count_functions),
.synapses = stm32_count_synapses,
.num_synapses = ARRAY_SIZE(stm32_count_synapses),
.ext = stm32_count_ext,
.num_ext = ARRAY_SIZE(stm32_count_ext)
};
static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr)
{
struct counter_device *counter = ptr;
struct stm32_timer_cnt *const priv = counter_priv(counter);
u32 clr = GENMASK(31, 0); /* SR flags can be cleared by writing 0 (wr 1 has no effect) */
u32 sr, dier;
int i;
regmap_read(priv->regmap, TIM_SR, &sr);
regmap_read(priv->regmap, TIM_DIER, &dier);
/*
* Some status bits in SR don't match with the enable bits in DIER. Only take care of
* the possibly enabled bits in DIER (that matches in between SR and DIER).
*/
dier &= (TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE);
sr &= dier;
if (sr & TIM_SR_UIF) {
spin_lock(&priv->lock);
priv->nb_ovf++;
spin_unlock(&priv->lock);
counter_push_event(counter, COUNTER_EVENT_OVERFLOW_UNDERFLOW, 0);
dev_dbg(counter->parent, "COUNTER_EVENT_OVERFLOW_UNDERFLOW\n");
/* SR flags can be cleared by writing 0, only clear relevant flag */
clr &= ~TIM_SR_UIF;
}
/* Check capture events */
for (i = 0 ; i < priv->nchannels; i++) {
if (sr & TIM_SR_CC_IF(i)) {
counter_push_event(counter, COUNTER_EVENT_CAPTURE, i);
clr &= ~TIM_SR_CC_IF(i);
dev_dbg(counter->parent, "COUNTER_EVENT_CAPTURE, %d\n", i);
}
}
regmap_write(priv->regmap, TIM_SR, clr);
return IRQ_HANDLED;
};
static void stm32_timer_cnt_detect_channels(struct device *dev,
struct stm32_timer_cnt *priv)
{
u32 ccer, ccer_backup;
regmap_read(priv->regmap, TIM_CCER, &ccer_backup);
regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CCXE);
regmap_read(priv->regmap, TIM_CCER, &ccer);
regmap_write(priv->regmap, TIM_CCER, ccer_backup);
priv->nchannels = hweight32(ccer & TIM_CCER_CCXE);
dev_dbg(dev, "has %d cc channels\n", priv->nchannels);
}
/* encoder supported on TIM1 TIM2 TIM3 TIM4 TIM5 TIM8 TIM20 */
#define STM32_TIM_ENCODER_SUPPORTED (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(7) | \
BIT(19))
static const char * const stm32_timer_trigger_compat[] = {
"st,stm32-timer-trigger",
"st,stm32h7-timer-trigger",
"st,stm32mp25-timer-trigger",
};
static int stm32_timer_cnt_probe_encoder(struct device *dev,
struct stm32_timer_cnt *priv)
{
struct device *parent = dev->parent;
struct device_node *tnode = NULL, *pnode = parent->of_node;
int i, ret;
u32 idx;
/*
* Need to retrieve the trigger node index from DT, to be able
* to determine if the counter supports encoder mode. It also
* enforce backward compatibility, and allow to support other
* counter modes in this driver (when the timer doesn't support
* encoder).
*/
for (i = 0; i < ARRAY_SIZE(stm32_timer_trigger_compat) && !tnode; i++)
tnode = of_get_compatible_child(pnode, stm32_timer_trigger_compat[i]);
if (!tnode) {
dev_err(dev, "Can't find trigger node\n");
return -ENODATA;
}
ret = of_property_read_u32(tnode, "reg", &idx);
of_node_put(tnode);
if (ret) {
dev_err(dev, "Can't get index (%d)\n", ret);
return ret;
}
priv->has_encoder = !!(STM32_TIM_ENCODER_SUPPORTED & BIT(idx));
dev_dbg(dev, "encoder support: %s\n", priv->has_encoder ? "yes" : "no");
return 0;
}
static int stm32_timer_cnt_probe(struct platform_device *pdev)
{
struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct stm32_timer_cnt *priv;
struct counter_device *counter;
int i, ret;
if (IS_ERR_OR_NULL(ddata))
return -EINVAL;
counter = devm_counter_alloc(dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
priv->max_arr = ddata->max_arr;
priv->nr_irqs = ddata->nr_irqs;
ret = stm32_timer_cnt_probe_encoder(dev, priv);
if (ret)
return ret;
stm32_timer_cnt_detect_channels(dev, priv);
counter->name = dev_name(dev);
counter->parent = dev;
counter->ops = &stm32_timer_cnt_ops;
counter->counts = &stm32_counts;
counter->num_counts = 1;
counter->signals = stm32_signals;
counter->num_signals = ARRAY_SIZE(stm32_signals);
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, priv);
/* STM32 Timers can have either 1 global, or 4 dedicated interrupts (optional) */
if (priv->nr_irqs == 1) {
/* All events reported through the global interrupt */
ret = devm_request_irq(&pdev->dev, ddata->irq[0], stm32_timer_cnt_isr,
0, dev_name(dev), counter);
if (ret) {
dev_err(dev, "Failed to request irq %d (err %d)\n",
ddata->irq[0], ret);
return ret;
}
} else {
for (i = 0; i < priv->nr_irqs; i++) {
/*
* Only take care of update IRQ for overflow events, and cc for
* capture events.
*/
if (i != STM32_TIMERS_IRQ_UP && i != STM32_TIMERS_IRQ_CC)
continue;
ret = devm_request_irq(&pdev->dev, ddata->irq[i], stm32_timer_cnt_isr,
0, dev_name(dev), counter);
if (ret) {
dev_err(dev, "Failed to request irq %d (err %d)\n",
ddata->irq[i], ret);
return ret;
}
}
}
/* Reset input selector to its default input */
regmap_write(priv->regmap, TIM_TISEL, 0x0);
/* Register Counter device */
ret = devm_counter_add(dev, counter);
if (ret < 0)
dev_err_probe(dev, ret, "Failed to add counter\n");
return ret;
}
static int __maybe_unused stm32_timer_cnt_suspend(struct device *dev)
{
struct stm32_timer_cnt *priv = dev_get_drvdata(dev);
/* Only take care of enabled counter: don't disturb other MFD child */
if (priv->enabled) {
/* Backup registers that may get lost in low power mode */
regmap_read(priv->regmap, TIM_SMCR, &priv->bak.smcr);
regmap_read(priv->regmap, TIM_ARR, &priv->bak.arr);
regmap_read(priv->regmap, TIM_CNT, &priv->bak.cnt);
regmap_read(priv->regmap, TIM_CR1, &priv->bak.cr1);
/* Disable the counter */
regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0);
clk_disable(priv->clk);
}
return pinctrl_pm_select_sleep_state(dev);
}
static int __maybe_unused stm32_timer_cnt_resume(struct device *dev)
{
struct stm32_timer_cnt *priv = dev_get_drvdata(dev);
int ret;
ret = pinctrl_pm_select_default_state(dev);
if (ret)
return ret;
if (priv->enabled) {
ret = clk_enable(priv->clk);
if (ret) {
dev_err(dev, "Cannot enable clock %d\n", ret);
return ret;
}
/* Restore registers that may have been lost */
regmap_write(priv->regmap, TIM_SMCR, priv->bak.smcr);
regmap_write(priv->regmap, TIM_ARR, priv->bak.arr);
regmap_write(priv->regmap, TIM_CNT, priv->bak.cnt);
/* Also re-enables the counter */
regmap_write(priv->regmap, TIM_CR1, priv->bak.cr1);
}
return 0;
}
static SIMPLE_DEV_PM_OPS(stm32_timer_cnt_pm_ops, stm32_timer_cnt_suspend,
stm32_timer_cnt_resume);
static const struct of_device_id stm32_timer_cnt_of_match[] = {
{ .compatible = "st,stm32-timer-counter", },
{ .compatible = "st,stm32mp25-timer-counter", },
{},
};
MODULE_DEVICE_TABLE(of, stm32_timer_cnt_of_match);
static struct platform_driver stm32_timer_cnt_driver = {
.probe = stm32_timer_cnt_probe,
.driver = {
.name = "stm32-timer-counter",
.of_match_table = stm32_timer_cnt_of_match,
.pm = &stm32_timer_cnt_pm_ops,
},
};
module_platform_driver(stm32_timer_cnt_driver);
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_ALIAS("platform:stm32-timer-counter");
MODULE_DESCRIPTION("STMicroelectronics STM32 TIMER counter driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("COUNTER");
@@ -0,0 +1,609 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ECAP Capture driver
*
* Copyright (C) 2022 Julien Panis <jpanis@baylibre.com>
*/
#include <linux/atomic.h>
#include <linux/clk.h>
#include <linux/counter.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#define ECAP_DRV_NAME "ecap"
/* ECAP event IDs */
#define ECAP_CEVT1 0
#define ECAP_CEVT2 1
#define ECAP_CEVT3 2
#define ECAP_CEVT4 3
#define ECAP_CNTOVF 4
#define ECAP_CEVT_LAST ECAP_CEVT4
#define ECAP_NB_CEVT (ECAP_CEVT_LAST + 1)
#define ECAP_EVT_LAST ECAP_CNTOVF
#define ECAP_NB_EVT (ECAP_EVT_LAST + 1)
/* Registers */
#define ECAP_TSCNT_REG 0x00
#define ECAP_CAP_REG(i) (((i) << 2) + 0x08)
#define ECAP_ECCTL_REG 0x28
#define ECAP_CAPPOL_BIT(i) BIT((i) << 1)
#define ECAP_EV_MODE_MASK GENMASK(7, 0)
#define ECAP_CAPLDEN_BIT BIT(8)
#define ECAP_CONT_ONESHT_BIT BIT(16)
#define ECAP_STOPVALUE_MASK GENMASK(18, 17)
#define ECAP_TSCNTSTP_BIT BIT(20)
#define ECAP_SYNCO_DIS_MASK GENMASK(23, 22)
#define ECAP_CAP_APWM_BIT BIT(25)
#define ECAP_ECCTL_EN_MASK (ECAP_CAPLDEN_BIT | ECAP_TSCNTSTP_BIT)
#define ECAP_ECCTL_CFG_MASK (ECAP_SYNCO_DIS_MASK | ECAP_STOPVALUE_MASK \
| ECAP_ECCTL_EN_MASK | ECAP_CAP_APWM_BIT \
| ECAP_CONT_ONESHT_BIT)
#define ECAP_ECINT_EN_FLG_REG 0x2c
#define ECAP_EVT_EN_MASK GENMASK(ECAP_NB_EVT, ECAP_NB_CEVT)
#define ECAP_EVT_FLG_BIT(i) BIT((i) + 17)
#define ECAP_ECINT_CLR_FRC_REG 0x30
#define ECAP_INT_CLR_BIT BIT(0)
#define ECAP_EVT_CLR_BIT(i) BIT((i) + 1)
#define ECAP_EVT_CLR_MASK GENMASK(ECAP_NB_EVT, 0)
#define ECAP_PID_REG 0x5c
/* ECAP signals */
#define ECAP_CLOCK_SIG 0
#define ECAP_INPUT_SIG 1
static const struct regmap_config ecap_cnt_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = ECAP_PID_REG,
};
/**
* struct ecap_cnt_dev - device private data structure
* @enabled: device state
* @lock: synchronization lock to prevent I/O race conditions
* @clk: device clock
* @regmap: device register map
* @nb_ovf: number of overflows since capture start
* @pm_ctx: device context for PM operations
* @pm_ctx.ev_mode: event mode bits
* @pm_ctx.time_cntr: timestamp counter value
*/
struct ecap_cnt_dev {
bool enabled;
struct mutex lock;
struct clk *clk;
struct regmap *regmap;
atomic_t nb_ovf;
struct {
u8 ev_mode;
u32 time_cntr;
} pm_ctx;
};
static u8 ecap_cnt_capture_get_evmode(struct counter_device *counter)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
unsigned int regval;
pm_runtime_get_sync(counter->parent);
regmap_read(ecap_dev->regmap, ECAP_ECCTL_REG, &regval);
pm_runtime_put_sync(counter->parent);
return regval;
}
static void ecap_cnt_capture_set_evmode(struct counter_device *counter, u8 ev_mode)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
pm_runtime_get_sync(counter->parent);
regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_EV_MODE_MASK, ev_mode);
pm_runtime_put_sync(counter->parent);
}
static void ecap_cnt_capture_enable(struct counter_device *counter)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
pm_runtime_get_sync(counter->parent);
/* Enable interrupts on events */
regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG,
ECAP_EVT_EN_MASK, ECAP_EVT_EN_MASK);
/* Run counter */
regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_ECCTL_CFG_MASK,
ECAP_SYNCO_DIS_MASK | ECAP_STOPVALUE_MASK | ECAP_ECCTL_EN_MASK);
}
static void ecap_cnt_capture_disable(struct counter_device *counter)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
/* Stop counter */
regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_ECCTL_EN_MASK, 0);
/* Disable interrupts on events */
regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG, ECAP_EVT_EN_MASK, 0);
pm_runtime_put_sync(counter->parent);
}
static u32 ecap_cnt_count_get_val(struct counter_device *counter, unsigned int reg)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
unsigned int regval;
pm_runtime_get_sync(counter->parent);
regmap_read(ecap_dev->regmap, reg, &regval);
pm_runtime_put_sync(counter->parent);
return regval;
}
static void ecap_cnt_count_set_val(struct counter_device *counter, unsigned int reg, u32 val)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
pm_runtime_get_sync(counter->parent);
regmap_write(ecap_dev->regmap, reg, val);
pm_runtime_put_sync(counter->parent);
}
static int ecap_cnt_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
*val = ecap_cnt_count_get_val(counter, ECAP_TSCNT_REG);
return 0;
}
static int ecap_cnt_count_write(struct counter_device *counter,
struct counter_count *count, u64 val)
{
if (val > U32_MAX)
return -ERANGE;
ecap_cnt_count_set_val(counter, ECAP_TSCNT_REG, val);
return 0;
}
static int ecap_cnt_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
*function = COUNTER_FUNCTION_INCREASE;
return 0;
}
static int ecap_cnt_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
*action = (synapse->signal->id == ECAP_CLOCK_SIG) ?
COUNTER_SYNAPSE_ACTION_RISING_EDGE :
COUNTER_SYNAPSE_ACTION_NONE;
return 0;
}
static int ecap_cnt_watch_validate(struct counter_device *counter,
const struct counter_watch *watch)
{
if (watch->channel > ECAP_CEVT_LAST)
return -EINVAL;
switch (watch->event) {
case COUNTER_EVENT_CAPTURE:
case COUNTER_EVENT_OVERFLOW:
return 0;
default:
return -EINVAL;
}
}
static int ecap_cnt_clk_get_freq(struct counter_device *counter,
struct counter_signal *signal, u64 *freq)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
*freq = clk_get_rate(ecap_dev->clk);
return 0;
}
static int ecap_cnt_pol_read(struct counter_device *counter,
struct counter_signal *signal,
size_t idx, enum counter_signal_polarity *pol)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
int bitval;
pm_runtime_get_sync(counter->parent);
bitval = regmap_test_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
pm_runtime_put_sync(counter->parent);
*pol = bitval ? COUNTER_SIGNAL_POLARITY_NEGATIVE : COUNTER_SIGNAL_POLARITY_POSITIVE;
return 0;
}
static int ecap_cnt_pol_write(struct counter_device *counter,
struct counter_signal *signal,
size_t idx, enum counter_signal_polarity pol)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
pm_runtime_get_sync(counter->parent);
if (pol == COUNTER_SIGNAL_POLARITY_NEGATIVE)
regmap_set_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
else
regmap_clear_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
pm_runtime_put_sync(counter->parent);
return 0;
}
static int ecap_cnt_cap_read(struct counter_device *counter,
struct counter_count *count,
size_t idx, u64 *cap)
{
*cap = ecap_cnt_count_get_val(counter, ECAP_CAP_REG(idx));
return 0;
}
static int ecap_cnt_cap_write(struct counter_device *counter,
struct counter_count *count,
size_t idx, u64 cap)
{
if (cap > U32_MAX)
return -ERANGE;
ecap_cnt_count_set_val(counter, ECAP_CAP_REG(idx), cap);
return 0;
}
static int ecap_cnt_nb_ovf_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
*val = atomic_read(&ecap_dev->nb_ovf);
return 0;
}
static int ecap_cnt_nb_ovf_write(struct counter_device *counter,
struct counter_count *count, u64 val)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
if (val > U32_MAX)
return -ERANGE;
atomic_set(&ecap_dev->nb_ovf, val);
return 0;
}
static int ecap_cnt_ceiling_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
*val = U32_MAX;
return 0;
}
static int ecap_cnt_enable_read(struct counter_device *counter,
struct counter_count *count, u8 *enable)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
*enable = ecap_dev->enabled;
return 0;
}
static int ecap_cnt_enable_write(struct counter_device *counter,
struct counter_count *count, u8 enable)
{
struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
mutex_lock(&ecap_dev->lock);
if (enable == ecap_dev->enabled)
goto out;
if (enable)
ecap_cnt_capture_enable(counter);
else
ecap_cnt_capture_disable(counter);
ecap_dev->enabled = enable;
out:
mutex_unlock(&ecap_dev->lock);
return 0;
}
static const struct counter_ops ecap_cnt_ops = {
.count_read = ecap_cnt_count_read,
.count_write = ecap_cnt_count_write,
.function_read = ecap_cnt_function_read,
.action_read = ecap_cnt_action_read,
.watch_validate = ecap_cnt_watch_validate,
};
static const enum counter_function ecap_cnt_functions[] = {
COUNTER_FUNCTION_INCREASE,
};
static const enum counter_synapse_action ecap_cnt_clock_actions[] = {
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
};
static const enum counter_synapse_action ecap_cnt_input_actions[] = {
COUNTER_SYNAPSE_ACTION_NONE,
};
static struct counter_comp ecap_cnt_clock_ext[] = {
COUNTER_COMP_FREQUENCY(ecap_cnt_clk_get_freq),
};
static const enum counter_signal_polarity ecap_cnt_pol_avail[] = {
COUNTER_SIGNAL_POLARITY_POSITIVE,
COUNTER_SIGNAL_POLARITY_NEGATIVE,
};
static DEFINE_COUNTER_AVAILABLE(ecap_cnt_pol_available, ecap_cnt_pol_avail);
static DEFINE_COUNTER_ARRAY_POLARITY(ecap_cnt_pol_array, ecap_cnt_pol_available, ECAP_NB_CEVT);
static struct counter_comp ecap_cnt_signal_ext[] = {
COUNTER_COMP_ARRAY_POLARITY(ecap_cnt_pol_read, ecap_cnt_pol_write, ecap_cnt_pol_array),
};
static struct counter_signal ecap_cnt_signals[] = {
{
.id = ECAP_CLOCK_SIG,
.name = "Clock Signal",
.ext = ecap_cnt_clock_ext,
.num_ext = ARRAY_SIZE(ecap_cnt_clock_ext),
},
{
.id = ECAP_INPUT_SIG,
.name = "Input Signal",
.ext = ecap_cnt_signal_ext,
.num_ext = ARRAY_SIZE(ecap_cnt_signal_ext),
},
};
static struct counter_synapse ecap_cnt_synapses[] = {
{
.actions_list = ecap_cnt_clock_actions,
.num_actions = ARRAY_SIZE(ecap_cnt_clock_actions),
.signal = &ecap_cnt_signals[ECAP_CLOCK_SIG],
},
{
.actions_list = ecap_cnt_input_actions,
.num_actions = ARRAY_SIZE(ecap_cnt_input_actions),
.signal = &ecap_cnt_signals[ECAP_INPUT_SIG],
},
};
static DEFINE_COUNTER_ARRAY_CAPTURE(ecap_cnt_cap_array, ECAP_NB_CEVT);
static struct counter_comp ecap_cnt_count_ext[] = {
COUNTER_COMP_ARRAY_CAPTURE(ecap_cnt_cap_read, ecap_cnt_cap_write, ecap_cnt_cap_array),
COUNTER_COMP_COUNT_U64("num_overflows", ecap_cnt_nb_ovf_read, ecap_cnt_nb_ovf_write),
COUNTER_COMP_CEILING(ecap_cnt_ceiling_read, NULL),
COUNTER_COMP_ENABLE(ecap_cnt_enable_read, ecap_cnt_enable_write),
};
static struct counter_count ecap_cnt_counts[] = {
{
.name = "Timestamp Counter",
.functions_list = ecap_cnt_functions,
.num_functions = ARRAY_SIZE(ecap_cnt_functions),
.synapses = ecap_cnt_synapses,
.num_synapses = ARRAY_SIZE(ecap_cnt_synapses),
.ext = ecap_cnt_count_ext,
.num_ext = ARRAY_SIZE(ecap_cnt_count_ext),
},
};
static irqreturn_t ecap_cnt_isr(int irq, void *dev_id)
{
struct counter_device *counter_dev = dev_id;
struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
unsigned int clr = 0;
unsigned int flg;
int i;
regmap_read(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG, &flg);
/* Check capture events */
for (i = 0 ; i < ECAP_NB_CEVT ; i++) {
if (flg & ECAP_EVT_FLG_BIT(i)) {
counter_push_event(counter_dev, COUNTER_EVENT_CAPTURE, i);
clr |= ECAP_EVT_CLR_BIT(i);
}
}
/* Check counter overflow */
if (flg & ECAP_EVT_FLG_BIT(ECAP_CNTOVF)) {
atomic_inc(&ecap_dev->nb_ovf);
for (i = 0 ; i < ECAP_NB_CEVT ; i++)
counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, i);
clr |= ECAP_EVT_CLR_BIT(ECAP_CNTOVF);
}
clr |= ECAP_INT_CLR_BIT;
regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_CLR_FRC_REG, ECAP_EVT_CLR_MASK, clr);
return IRQ_HANDLED;
}
static int ecap_cnt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ecap_cnt_dev *ecap_dev;
struct counter_device *counter_dev;
void __iomem *mmio_base;
unsigned long clk_rate;
int ret;
counter_dev = devm_counter_alloc(dev, sizeof(*ecap_dev));
if (!counter_dev)
return -ENOMEM;
counter_dev->name = ECAP_DRV_NAME;
counter_dev->parent = dev;
counter_dev->ops = &ecap_cnt_ops;
counter_dev->signals = ecap_cnt_signals;
counter_dev->num_signals = ARRAY_SIZE(ecap_cnt_signals);
counter_dev->counts = ecap_cnt_counts;
counter_dev->num_counts = ARRAY_SIZE(ecap_cnt_counts);
ecap_dev = counter_priv(counter_dev);
mutex_init(&ecap_dev->lock);
ecap_dev->clk = devm_clk_get_enabled(dev, "fck");
if (IS_ERR(ecap_dev->clk))
return dev_err_probe(dev, PTR_ERR(ecap_dev->clk), "failed to get clock\n");
clk_rate = clk_get_rate(ecap_dev->clk);
if (!clk_rate) {
dev_err(dev, "failed to get clock rate\n");
return -EINVAL;
}
mmio_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mmio_base))
return PTR_ERR(mmio_base);
ecap_dev->regmap = devm_regmap_init_mmio(dev, mmio_base, &ecap_cnt_regmap_config);
if (IS_ERR(ecap_dev->regmap))
return dev_err_probe(dev, PTR_ERR(ecap_dev->regmap), "failed to init regmap\n");
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get irq\n");
ret = devm_request_irq(dev, ret, ecap_cnt_isr, 0, pdev->name, counter_dev);
if (ret)
return dev_err_probe(dev, ret, "failed to request irq\n");
platform_set_drvdata(pdev, counter_dev);
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
ret = devm_counter_add(dev, counter_dev);
if (ret)
return dev_err_probe(dev, ret, "failed to add counter\n");
return 0;
}
static void ecap_cnt_remove(struct platform_device *pdev)
{
struct counter_device *counter_dev = platform_get_drvdata(pdev);
struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
if (ecap_dev->enabled)
ecap_cnt_capture_disable(counter_dev);
}
static int ecap_cnt_suspend(struct device *dev)
{
struct counter_device *counter_dev = dev_get_drvdata(dev);
struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
/* If eCAP is running, stop capture then save timestamp counter */
if (ecap_dev->enabled) {
/*
* Disabling capture has the following effects:
* - interrupts are disabled
* - loading of capture registers is disabled
* - timebase counter is stopped
*/
ecap_cnt_capture_disable(counter_dev);
ecap_dev->pm_ctx.time_cntr = ecap_cnt_count_get_val(counter_dev, ECAP_TSCNT_REG);
}
ecap_dev->pm_ctx.ev_mode = ecap_cnt_capture_get_evmode(counter_dev);
clk_disable(ecap_dev->clk);
return 0;
}
static int ecap_cnt_resume(struct device *dev)
{
struct counter_device *counter_dev = dev_get_drvdata(dev);
struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
int ret;
ret = clk_enable(ecap_dev->clk);
if (ret) {
dev_err(dev, "Cannot enable clock %d\n", ret);
return ret;
}
ecap_cnt_capture_set_evmode(counter_dev, ecap_dev->pm_ctx.ev_mode);
/* If eCAP was running, restore timestamp counter then run capture */
if (ecap_dev->enabled) {
ecap_cnt_count_set_val(counter_dev, ECAP_TSCNT_REG, ecap_dev->pm_ctx.time_cntr);
ecap_cnt_capture_enable(counter_dev);
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ecap_cnt_pm_ops, ecap_cnt_suspend, ecap_cnt_resume);
static const struct of_device_id ecap_cnt_of_match[] = {
{ .compatible = "ti,am62-ecap-capture" },
{},
};
MODULE_DEVICE_TABLE(of, ecap_cnt_of_match);
static struct platform_driver ecap_cnt_driver = {
.probe = ecap_cnt_probe,
.remove = ecap_cnt_remove,
.driver = {
.name = "ecap-capture",
.of_match_table = ecap_cnt_of_match,
.pm = pm_sleep_ptr(&ecap_cnt_pm_ops),
},
};
module_platform_driver(ecap_cnt_driver);
MODULE_DESCRIPTION("ECAP Capture driver");
MODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("COUNTER");
+593
View File
@@ -0,0 +1,593 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2019 David Lechner <david@lechnology.com>
*
* Counter driver for Texas Instruments Enhanced Quadrature Encoder Pulse (eQEP)
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/counter.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/types.h>
/* 32-bit registers */
#define QPOSCNT 0x0
#define QPOSINIT 0x4
#define QPOSMAX 0x8
#define QPOSCMP 0xc
#define QPOSILAT 0x10
#define QPOSSLAT 0x14
#define QPOSLAT 0x18
#define QUTMR 0x1c
#define QUPRD 0x20
/* 16-bit registers */
#define QWDTMR 0x0 /* 0x24 */
#define QWDPRD 0x2 /* 0x26 */
#define QDECCTL 0x4 /* 0x28 */
#define QEPCTL 0x6 /* 0x2a */
#define QCAPCTL 0x8 /* 0x2c */
#define QPOSCTL 0xa /* 0x2e */
#define QEINT 0xc /* 0x30 */
#define QFLG 0xe /* 0x32 */
#define QCLR 0x10 /* 0x34 */
#define QFRC 0x12 /* 0x36 */
#define QEPSTS 0x14 /* 0x38 */
#define QCTMR 0x16 /* 0x3a */
#define QCPRD 0x18 /* 0x3c */
#define QCTMRLAT 0x1a /* 0x3e */
#define QCPRDLAT 0x1c /* 0x40 */
#define QDECCTL_QSRC_SHIFT 14
#define QDECCTL_QSRC GENMASK(15, 14)
#define QDECCTL_SOEN BIT(13)
#define QDECCTL_SPSEL BIT(12)
#define QDECCTL_XCR BIT(11)
#define QDECCTL_SWAP BIT(10)
#define QDECCTL_IGATE BIT(9)
#define QDECCTL_QAP BIT(8)
#define QDECCTL_QBP BIT(7)
#define QDECCTL_QIP BIT(6)
#define QDECCTL_QSP BIT(5)
#define QEPCTL_FREE_SOFT GENMASK(15, 14)
#define QEPCTL_PCRM GENMASK(13, 12)
#define QEPCTL_SEI GENMASK(11, 10)
#define QEPCTL_IEI GENMASK(9, 8)
#define QEPCTL_SWI BIT(7)
#define QEPCTL_SEL BIT(6)
#define QEPCTL_IEL GENMASK(5, 4)
#define QEPCTL_PHEN BIT(3)
#define QEPCTL_QCLM BIT(2)
#define QEPCTL_UTE BIT(1)
#define QEPCTL_WDE BIT(0)
#define QEINT_UTO BIT(11)
#define QEINT_IEL BIT(10)
#define QEINT_SEL BIT(9)
#define QEINT_PCM BIT(8)
#define QEINT_PCR BIT(7)
#define QEINT_PCO BIT(6)
#define QEINT_PCU BIT(5)
#define QEINT_WTO BIT(4)
#define QEINT_QDC BIT(3)
#define QEINT_PHE BIT(2)
#define QEINT_PCE BIT(1)
#define QFLG_UTO BIT(11)
#define QFLG_IEL BIT(10)
#define QFLG_SEL BIT(9)
#define QFLG_PCM BIT(8)
#define QFLG_PCR BIT(7)
#define QFLG_PCO BIT(6)
#define QFLG_PCU BIT(5)
#define QFLG_WTO BIT(4)
#define QFLG_QDC BIT(3)
#define QFLG_PHE BIT(2)
#define QFLG_PCE BIT(1)
#define QFLG_INT BIT(0)
#define QCLR_UTO BIT(11)
#define QCLR_IEL BIT(10)
#define QCLR_SEL BIT(9)
#define QCLR_PCM BIT(8)
#define QCLR_PCR BIT(7)
#define QCLR_PCO BIT(6)
#define QCLR_PCU BIT(5)
#define QCLR_WTO BIT(4)
#define QCLR_QDC BIT(3)
#define QCLR_PHE BIT(2)
#define QCLR_PCE BIT(1)
#define QCLR_INT BIT(0)
#define QEPSTS_UPEVNT BIT(7)
#define QEPSTS_FDF BIT(6)
#define QEPSTS_QDF BIT(5)
#define QEPSTS_QDLF BIT(4)
#define QEPSTS_COEF BIT(3)
#define QEPSTS_CDEF BIT(2)
#define QEPSTS_FIMF BIT(1)
#define QEPSTS_PCEF BIT(0)
/* EQEP Inputs */
enum {
TI_EQEP_SIGNAL_QEPA, /* QEPA/XCLK */
TI_EQEP_SIGNAL_QEPB, /* QEPB/XDIR */
};
/* Position Counter Input Modes */
enum ti_eqep_count_func {
TI_EQEP_COUNT_FUNC_QUAD_COUNT,
TI_EQEP_COUNT_FUNC_DIR_COUNT,
TI_EQEP_COUNT_FUNC_UP_COUNT,
TI_EQEP_COUNT_FUNC_DOWN_COUNT,
};
struct ti_eqep_cnt {
struct regmap *regmap32;
struct regmap *regmap16;
};
static int ti_eqep_count_read(struct counter_device *counter,
struct counter_count *count, u64 *val)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 cnt;
regmap_read(priv->regmap32, QPOSCNT, &cnt);
*val = cnt;
return 0;
}
static int ti_eqep_count_write(struct counter_device *counter,
struct counter_count *count, u64 val)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 max;
regmap_read(priv->regmap32, QPOSMAX, &max);
if (val > max)
return -EINVAL;
return regmap_write(priv->regmap32, QPOSCNT, val);
}
static int ti_eqep_function_read(struct counter_device *counter,
struct counter_count *count,
enum counter_function *function)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 qdecctl;
regmap_read(priv->regmap16, QDECCTL, &qdecctl);
switch ((qdecctl & QDECCTL_QSRC) >> QDECCTL_QSRC_SHIFT) {
case TI_EQEP_COUNT_FUNC_QUAD_COUNT:
*function = COUNTER_FUNCTION_QUADRATURE_X4;
break;
case TI_EQEP_COUNT_FUNC_DIR_COUNT:
*function = COUNTER_FUNCTION_PULSE_DIRECTION;
break;
case TI_EQEP_COUNT_FUNC_UP_COUNT:
*function = COUNTER_FUNCTION_INCREASE;
break;
case TI_EQEP_COUNT_FUNC_DOWN_COUNT:
*function = COUNTER_FUNCTION_DECREASE;
break;
}
return 0;
}
static int ti_eqep_function_write(struct counter_device *counter,
struct counter_count *count,
enum counter_function function)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
enum ti_eqep_count_func qsrc;
switch (function) {
case COUNTER_FUNCTION_QUADRATURE_X4:
qsrc = TI_EQEP_COUNT_FUNC_QUAD_COUNT;
break;
case COUNTER_FUNCTION_PULSE_DIRECTION:
qsrc = TI_EQEP_COUNT_FUNC_DIR_COUNT;
break;
case COUNTER_FUNCTION_INCREASE:
qsrc = TI_EQEP_COUNT_FUNC_UP_COUNT;
break;
case COUNTER_FUNCTION_DECREASE:
qsrc = TI_EQEP_COUNT_FUNC_DOWN_COUNT;
break;
default:
/* should never reach this path */
return -EINVAL;
}
return regmap_write_bits(priv->regmap16, QDECCTL, QDECCTL_QSRC,
qsrc << QDECCTL_QSRC_SHIFT);
}
static int ti_eqep_action_read(struct counter_device *counter,
struct counter_count *count,
struct counter_synapse *synapse,
enum counter_synapse_action *action)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
enum counter_function function;
u32 qdecctl;
int err;
err = ti_eqep_function_read(counter, count, &function);
if (err)
return err;
switch (function) {
case COUNTER_FUNCTION_QUADRATURE_X4:
/* In quadrature mode, the rising and falling edge of both
* QEPA and QEPB trigger QCLK.
*/
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
return 0;
case COUNTER_FUNCTION_PULSE_DIRECTION:
/* In direction-count mode only rising edge of QEPA is counted
* and QEPB gives direction.
*/
switch (synapse->signal->id) {
case TI_EQEP_SIGNAL_QEPA:
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
return 0;
case TI_EQEP_SIGNAL_QEPB:
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
case COUNTER_FUNCTION_INCREASE:
case COUNTER_FUNCTION_DECREASE:
/* In up/down-count modes only QEPA is counted and QEPB is not
* used.
*/
switch (synapse->signal->id) {
case TI_EQEP_SIGNAL_QEPA:
err = regmap_read(priv->regmap16, QDECCTL, &qdecctl);
if (err)
return err;
if (qdecctl & QDECCTL_XCR)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
return 0;
case TI_EQEP_SIGNAL_QEPB:
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
default:
/* should never reach this path */
return -EINVAL;
}
default:
/* should never reach this path */
return -EINVAL;
}
}
static int ti_eqep_events_configure(struct counter_device *counter)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
struct counter_event_node *event_node;
u32 qeint = 0;
list_for_each_entry(event_node, &counter->events_list, l) {
switch (event_node->event) {
case COUNTER_EVENT_OVERFLOW:
qeint |= QEINT_PCO;
break;
case COUNTER_EVENT_UNDERFLOW:
qeint |= QEINT_PCU;
break;
case COUNTER_EVENT_DIRECTION_CHANGE:
qeint |= QEINT_QDC;
break;
}
}
return regmap_write(priv->regmap16, QEINT, qeint);
}
static int ti_eqep_watch_validate(struct counter_device *counter,
const struct counter_watch *watch)
{
switch (watch->event) {
case COUNTER_EVENT_OVERFLOW:
case COUNTER_EVENT_UNDERFLOW:
case COUNTER_EVENT_DIRECTION_CHANGE:
if (watch->channel != 0)
return -EINVAL;
return 0;
default:
return -EINVAL;
}
}
static const struct counter_ops ti_eqep_counter_ops = {
.count_read = ti_eqep_count_read,
.count_write = ti_eqep_count_write,
.function_read = ti_eqep_function_read,
.function_write = ti_eqep_function_write,
.action_read = ti_eqep_action_read,
.events_configure = ti_eqep_events_configure,
.watch_validate = ti_eqep_watch_validate,
};
static int ti_eqep_position_ceiling_read(struct counter_device *counter,
struct counter_count *count,
u64 *ceiling)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 qposmax;
regmap_read(priv->regmap32, QPOSMAX, &qposmax);
*ceiling = qposmax;
return 0;
}
static int ti_eqep_position_ceiling_write(struct counter_device *counter,
struct counter_count *count,
u64 ceiling)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
if (ceiling != (u32)ceiling)
return -ERANGE;
regmap_write(priv->regmap32, QPOSMAX, ceiling);
return 0;
}
static int ti_eqep_position_enable_read(struct counter_device *counter,
struct counter_count *count, u8 *enable)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 qepctl;
regmap_read(priv->regmap16, QEPCTL, &qepctl);
*enable = !!(qepctl & QEPCTL_PHEN);
return 0;
}
static int ti_eqep_position_enable_write(struct counter_device *counter,
struct counter_count *count, u8 enable)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
regmap_write_bits(priv->regmap16, QEPCTL, QEPCTL_PHEN, enable ? -1 : 0);
return 0;
}
static int ti_eqep_direction_read(struct counter_device *counter,
struct counter_count *count,
enum counter_count_direction *direction)
{
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 qepsts;
regmap_read(priv->regmap16, QEPSTS, &qepsts);
*direction = (qepsts & QEPSTS_QDF) ? COUNTER_COUNT_DIRECTION_FORWARD
: COUNTER_COUNT_DIRECTION_BACKWARD;
return 0;
}
static struct counter_comp ti_eqep_position_ext[] = {
COUNTER_COMP_CEILING(ti_eqep_position_ceiling_read,
ti_eqep_position_ceiling_write),
COUNTER_COMP_ENABLE(ti_eqep_position_enable_read,
ti_eqep_position_enable_write),
COUNTER_COMP_DIRECTION(ti_eqep_direction_read),
};
static struct counter_signal ti_eqep_signals[] = {
[TI_EQEP_SIGNAL_QEPA] = {
.id = TI_EQEP_SIGNAL_QEPA,
.name = "QEPA"
},
[TI_EQEP_SIGNAL_QEPB] = {
.id = TI_EQEP_SIGNAL_QEPB,
.name = "QEPB"
},
};
static const enum counter_function ti_eqep_position_functions[] = {
COUNTER_FUNCTION_QUADRATURE_X4,
COUNTER_FUNCTION_PULSE_DIRECTION,
COUNTER_FUNCTION_INCREASE,
COUNTER_FUNCTION_DECREASE,
};
static const enum counter_synapse_action ti_eqep_position_synapse_actions[] = {
COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
COUNTER_SYNAPSE_ACTION_RISING_EDGE,
COUNTER_SYNAPSE_ACTION_NONE,
};
static struct counter_synapse ti_eqep_position_synapses[] = {
{
.actions_list = ti_eqep_position_synapse_actions,
.num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions),
.signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPA],
},
{
.actions_list = ti_eqep_position_synapse_actions,
.num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions),
.signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPB],
},
};
static struct counter_count ti_eqep_counts[] = {
{
.id = 0,
.name = "QPOSCNT",
.functions_list = ti_eqep_position_functions,
.num_functions = ARRAY_SIZE(ti_eqep_position_functions),
.synapses = ti_eqep_position_synapses,
.num_synapses = ARRAY_SIZE(ti_eqep_position_synapses),
.ext = ti_eqep_position_ext,
.num_ext = ARRAY_SIZE(ti_eqep_position_ext),
},
};
static irqreturn_t ti_eqep_irq_handler(int irq, void *dev_id)
{
struct counter_device *counter = dev_id;
struct ti_eqep_cnt *priv = counter_priv(counter);
u32 qflg;
regmap_read(priv->regmap16, QFLG, &qflg);
if (qflg & QFLG_PCO)
counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);
if (qflg & QFLG_PCU)
counter_push_event(counter, COUNTER_EVENT_UNDERFLOW, 0);
if (qflg & QFLG_QDC)
counter_push_event(counter, COUNTER_EVENT_DIRECTION_CHANGE, 0);
regmap_write(priv->regmap16, QCLR, qflg);
return IRQ_HANDLED;
}
static const struct regmap_config ti_eqep_regmap32_config = {
.name = "32-bit",
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = QUPRD,
};
static const struct regmap_config ti_eqep_regmap16_config = {
.name = "16-bit",
.reg_bits = 16,
.val_bits = 16,
.reg_stride = 2,
.max_register = QCPRDLAT,
};
static int ti_eqep_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct counter_device *counter;
struct ti_eqep_cnt *priv;
void __iomem *base;
struct clk *clk;
int err, irq;
counter = devm_counter_alloc(dev, sizeof(*priv));
if (!counter)
return -ENOMEM;
priv = counter_priv(counter);
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap32 = devm_regmap_init_mmio(dev, base,
&ti_eqep_regmap32_config);
if (IS_ERR(priv->regmap32))
return PTR_ERR(priv->regmap32);
priv->regmap16 = devm_regmap_init_mmio(dev, base + 0x24,
&ti_eqep_regmap16_config);
if (IS_ERR(priv->regmap16))
return PTR_ERR(priv->regmap16);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
err = devm_request_threaded_irq(dev, irq, NULL, ti_eqep_irq_handler,
IRQF_ONESHOT, dev_name(dev), counter);
if (err < 0)
return dev_err_probe(dev, err, "failed to request IRQ\n");
counter->name = dev_name(dev);
counter->parent = dev;
counter->ops = &ti_eqep_counter_ops;
counter->counts = ti_eqep_counts;
counter->num_counts = ARRAY_SIZE(ti_eqep_counts);
counter->signals = ti_eqep_signals;
counter->num_signals = ARRAY_SIZE(ti_eqep_signals);
platform_set_drvdata(pdev, counter);
/*
* Need to make sure power is turned on. On AM33xx, this comes from the
* parent PWMSS bus driver. On AM17xx, this comes from the PSC power
* domain.
*/
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk), "failed to enable clock\n");
err = counter_add(counter);
if (err < 0) {
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
return err;
}
return 0;
}
static void ti_eqep_remove(struct platform_device *pdev)
{
struct counter_device *counter = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
counter_unregister(counter);
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
}
static const struct of_device_id ti_eqep_of_match[] = {
{ .compatible = "ti,am3352-eqep", },
{ .compatible = "ti,am62-eqep", },
{ },
};
MODULE_DEVICE_TABLE(of, ti_eqep_of_match);
static struct platform_driver ti_eqep_driver = {
.probe = ti_eqep_probe,
.remove = ti_eqep_remove,
.driver = {
.name = "ti-eqep-cnt",
.of_match_table = ti_eqep_of_match,
},
};
module_platform_driver(ti_eqep_driver);
MODULE_AUTHOR("David Lechner <david@lechnology.com>");
MODULE_DESCRIPTION("TI eQEP counter driver");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("COUNTER");