522 строки
13 KiB
C
522 строки
13 KiB
C
// 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 counter_device counter;
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
int ret = 0;
|
|
|
|
/* Intel QEP ceiling configuration only supports 32-bit values */
|
|
if (max != (u32)max)
|
|
return -ERANGE;
|
|
|
|
mutex_lock(&qep->lock);
|
|
if (qep->enabled) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
pm_runtime_get_sync(qep->dev);
|
|
intel_qep_writel(qep, INTEL_QEPMAX, max);
|
|
pm_runtime_put(qep->dev);
|
|
|
|
out:
|
|
mutex_unlock(&qep->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int intel_qep_enable_read(struct counter_device *counter,
|
|
struct counter_count *count, u8 *enable)
|
|
{
|
|
struct intel_qep *qep = counter->priv;
|
|
|
|
*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;
|
|
u32 reg;
|
|
bool changed;
|
|
|
|
mutex_lock(&qep->lock);
|
|
changed = val ^ qep->enabled;
|
|
if (!changed)
|
|
goto out;
|
|
|
|
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;
|
|
|
|
out:
|
|
mutex_unlock(&qep->lock);
|
|
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;
|
|
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;
|
|
u32 reg;
|
|
bool enable;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
mutex_lock(&qep->lock);
|
|
if (qep->enabled) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
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);
|
|
|
|
out:
|
|
mutex_unlock(&qep->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int intel_qep_preset_enable_read(struct counter_device *counter,
|
|
struct counter_count *count,
|
|
u8 *preset_enable)
|
|
{
|
|
struct intel_qep *qep = counter->priv;
|
|
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;
|
|
u32 reg;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&qep->lock);
|
|
if (qep->enabled) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
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);
|
|
|
|
out:
|
|
mutex_unlock(&qep->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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 intel_qep *qep;
|
|
struct device *dev = &pci->dev;
|
|
void __iomem *regs;
|
|
int ret;
|
|
|
|
qep = devm_kzalloc(dev, sizeof(*qep), GFP_KERNEL);
|
|
if (!qep)
|
|
return -ENOMEM;
|
|
|
|
ret = pcim_enable_device(pci);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pci_set_master(pci);
|
|
|
|
ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci));
|
|
if (ret)
|
|
return ret;
|
|
|
|
regs = pcim_iomap_table(pci)[0];
|
|
if (!regs)
|
|
return -ENOMEM;
|
|
|
|
qep->dev = dev;
|
|
qep->regs = regs;
|
|
mutex_init(&qep->lock);
|
|
|
|
intel_qep_init(qep);
|
|
pci_set_drvdata(pci, qep);
|
|
|
|
qep->counter.name = pci_name(pci);
|
|
qep->counter.parent = dev;
|
|
qep->counter.ops = &intel_qep_counter_ops;
|
|
qep->counter.counts = intel_qep_counter_count;
|
|
qep->counter.num_counts = ARRAY_SIZE(intel_qep_counter_count);
|
|
qep->counter.signals = intel_qep_signals;
|
|
qep->counter.num_signals = ARRAY_SIZE(intel_qep_signals);
|
|
qep->counter.priv = qep;
|
|
qep->enabled = false;
|
|
|
|
pm_runtime_put(dev);
|
|
pm_runtime_allow(dev);
|
|
|
|
return devm_counter_register(&pci->dev, &qep->counter);
|
|
}
|
|
|
|
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");
|