Message ID | 20210527135838.3853285-1-jarkko.nikula@linux.intel.com (mailing list archive) |
---|---|
State | Superseded, archived |
Headers | show |
Series | [v3] counter: Add support for Intel Quadrature Encoder Peripheral | expand |
On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: > Add support for Intel Quadrature Encoder Peripheral found on Intel > Elkhart Lake platform. > > Initial implementation was done by Felipe Balbi while he was working at > Intel with later changes from Raymond Tan and me. > > Co-developed-by: Felipe Balbi (Intel) <balbi@kernel.org> > Signed-off-by: Felipe Balbi (Intel) <balbi@kernel.org> > Co-developed-by: Raymond Tan <raymond.tan@intel.com> > Signed-off-by: Raymond Tan <raymond.tan@intel.com> > Signed-off-by: Jarkko Nikula <jarkko.nikula@linux.intel.com> Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> > --- > v3: > - Support for Quadrature x4 with swapped inputs and inverted inputs > removed. It turned out in review discussion both are board specific > features and questionable should they be even exposed to userspace. > Both features are postponed to future contribution if such need arises. > Patch 1/2 removed becaused of this. > - Error out if trying to set 1 clock period long spike filter. Previous > version silently disabled the filter and also sysfs behavior in that > case was inconsistent: write 10 but read returns 0. > - Line-continuation characters in INTEL_QEP_COUNTER_EXT_RW() aligned the > same way than others. > > v2: https://marc.info/?l=linux-iio&m=162204156231555&w=2 > - counter_to_qep() macro -> counter->priv > - Use sysfs_emit for user space returned values > - Use kstrbool for boolean values from userspace > - enable_write() reworked to be more readable > - Reworked synapse action control and new sysfs attribute "invert" > * Action control before was wrong - what HW does is signal inversion. > Implemented "invert" sysfs attribute for it and read-only action > mode sysfs returning constant "both edges" > - Renamed sysfs attribe "noise" as "spike_filter_ns" and define > programmable spike filter in terms of nanoseconds instead of raw > register value > - Above and "ceiling" sysfs attribe changed as count extensions instead > of device extensions > - Signal IDs rearranged to be zero based in order to prepare for counter > character device interface patches in order to ensure same userspace > sysfs paths > - Initializer macros for counter_signal and counter_synapse > initialization > - Grouping intel_qep_counter_ops, intel_qep_signal_ext and enums near to > their callback functions and use > - "invert" and "spike_filter_ns" sysfs attributes documented > - Other minor changes like local variable and empty line removal, etc > > v1: https://www.spinics.net/lists/linux-iio/msg59652.html > --- > Documentation/ABI/testing/sysfs-bus-counter | 9 + > drivers/counter/Kconfig | 10 + > drivers/counter/Makefile | 1 + > drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ > 4 files changed, 566 insertions(+) > create mode 100644 drivers/counter/intel-qep.c > > diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter > index 566bd99fe0a5..e9d9e50f03be 100644 > --- a/Documentation/ABI/testing/sysfs-bus-counter > +++ b/Documentation/ABI/testing/sysfs-bus-counter > @@ -193,6 +193,15 @@ Description: > both edges: > Any state transition. > > +What: /sys/bus/counter/devices/counterX/countY/spike_filter_ns > +KernelVersion: 5.14 > +Contact: linux-iio@vger.kernel.org > +Description: > + If the counter device supports programmable spike filter this > + attribute indicates the value in nanoseconds where noise pulses > + shorter or equal to configured value are ignored. Value 0 means > + filter is disabled. > + > What: /sys/bus/counter/devices/counterX/name > KernelVersion: 5.2 > Contact: linux-iio@vger.kernel.org > diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig > index 5328705aa09c..d5d2540b30c2 100644 > --- a/drivers/counter/Kconfig > +++ b/drivers/counter/Kconfig > @@ -91,4 +91,14 @@ config MICROCHIP_TCB_CAPTURE > To compile this driver as a module, choose M here: the > module will be called microchip-tcb-capture. > > +config INTEL_QEP > + tristate "Intel Quadrature Encoder Peripheral driver" > + 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. > + > endif # COUNTER > diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile > index cb646ed2f039..19742e6f5e3e 100644 > --- a/drivers/counter/Makefile > +++ b/drivers/counter/Makefile > @@ -12,3 +12,4 @@ 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 > diff --git a/drivers/counter/intel-qep.c b/drivers/counter/intel-qep.c > new file mode 100644 > index 000000000000..ab10ba33f46a > --- /dev/null > +++ b/drivers/counter/intel-qep.c > @@ -0,0 +1,546 @@ > +// 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/bitops.h> > +#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 > + > +#define INTEL_QEP_COUNTER_EXT_RW(_name) \ > +{ \ > + .name = #_name, \ > + .read = _name##_read, \ > + .write = _name##_write, \ > +} > + > +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, > + unsigned long *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_count_function intel_qep_count_functions[] = { > + COUNTER_COUNT_FUNCTION_QUADRATURE_X4, > +}; > + > +static int intel_qep_function_get(struct counter_device *counter, > + struct counter_count *count, > + size_t *function) > +{ > + *function = 0; > + > + return 0; > +} > + > +static const enum counter_synapse_action intel_qep_synapse_actions[] = { > + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, > +}; > + > +static int intel_qep_action_get(struct counter_device *counter, > + struct counter_count *count, > + struct counter_synapse *synapse, > + size_t *action) > +{ > + *action = 0; > + return 0; > +} > + > +static const struct counter_ops intel_qep_counter_ops = { > + .count_read = intel_qep_count_read, > + .function_get = intel_qep_function_get, > + .action_get = intel_qep_action_get, > +}; > + > +#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 ssize_t ceiling_read(struct counter_device *counter, > + struct counter_count *count, > + void *priv, char *buf) > +{ > + struct intel_qep *qep = counter->priv; > + u32 reg; > + > + pm_runtime_get_sync(qep->dev); > + reg = intel_qep_readl(qep, INTEL_QEPMAX); > + pm_runtime_put(qep->dev); > + > + return sysfs_emit(buf, "%u\n", reg); > +} > + > +static ssize_t ceiling_write(struct counter_device *counter, > + struct counter_count *count, > + void *priv, const char *buf, size_t len) > +{ > + struct intel_qep *qep = counter->priv; > + u32 max; > + int ret; > + > + ret = kstrtou32(buf, 0, &max); > + if (ret < 0) > + return ret; > + > + 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); > + ret = len; > + > +out: > + mutex_unlock(&qep->lock); > + return ret; > +} > + > +static ssize_t enable_read(struct counter_device *counter, > + struct counter_count *count, > + void *priv, char *buf) > +{ > + struct intel_qep *qep = counter->priv; > + > + return sysfs_emit(buf, "%u\n", qep->enabled); > +} > + > +static ssize_t enable_write(struct counter_device *counter, > + struct counter_count *count, > + void *priv, const char *buf, size_t len) > +{ > + struct intel_qep *qep = counter->priv; > + u32 reg; > + bool val, changed; > + int ret; > + > + ret = kstrtobool(buf, &val); > + if (ret) > + return ret; > + > + 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 len; > +} > + > +static ssize_t spike_filter_ns_read(struct counter_device *counter, > + struct counter_count *count, > + void *priv, char *buf) > +{ > + 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 sysfs_emit(buf, "0\n"); > + } > + reg = INTEL_QEPFLT_MAX_COUNT(intel_qep_readl(qep, INTEL_QEPFLT)); > + pm_runtime_put(qep->dev); > + > + return sysfs_emit(buf, "%u\n", (reg + 2) * INTEL_QEP_CLK_PERIOD_NS); > +} > + > +static ssize_t spike_filter_ns_write(struct counter_device *counter, > + struct counter_count *count, > + void *priv, const char *buf, size_t len) > +{ > + struct intel_qep *qep = counter->priv; > + u32 reg, length; > + bool enable; > + int ret; > + > + ret = kstrtou32(buf, 0, &length); > + if (ret < 0) > + return ret; > + > + /* > + * Spike filter length is (MAX_COUNT + 2) clock periods. > + * Disable filter when userspace writes 0, enable for valid > + * nanoseconds values and error out otherwise. > + */ > + 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 -EINVAL; > + > + 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); > + ret = len; > + > +out: > + mutex_unlock(&qep->lock); > + return ret; > +} > + > +static ssize_t preset_enable_read(struct counter_device *counter, > + struct counter_count *count, > + void *priv, char *buf) > +{ > + 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); > + return sysfs_emit(buf, "%u\n", !(reg & INTEL_QEPCON_COUNT_RST_MODE)); > +} > + > +static ssize_t preset_enable_write(struct counter_device *counter, > + struct counter_count *count, > + void *priv, const char *buf, size_t len) > +{ > + struct intel_qep *qep = counter->priv; > + u32 reg; > + bool val; > + int ret; > + > + ret = kstrtobool(buf, &val); > + if (ret) > + return ret; > + > + 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); > + ret = len; > + > +out: > + mutex_unlock(&qep->lock); > + > + return ret; > +} > + > +static const struct counter_count_ext intel_qep_count_ext[] = { > + INTEL_QEP_COUNTER_EXT_RW(ceiling), > + INTEL_QEP_COUNTER_EXT_RW(enable), > + INTEL_QEP_COUNTER_EXT_RW(spike_filter_ns), > + INTEL_QEP_COUNTER_EXT_RW(preset_enable) > +}; > + > +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); > +} > + > +#ifdef CONFIG_PM > +static int intel_qep_suspend(struct device *dev) > +{ > + struct pci_dev *pdev = container_of(dev, struct 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 intel_qep_resume(struct device *dev) > +{ > + struct pci_dev *pdev = container_of(dev, struct 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; > +} > +#endif > + > +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"); > -- > 2.30.2 >
On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: > Add support for Intel Quadrature Encoder Peripheral found on Intel > Elkhart Lake platform. > > Initial implementation was done by Felipe Balbi while he was working at > Intel with later changes from Raymond Tan and me. > > Co-developed-by: Felipe Balbi (Intel) <balbi@kernel.org> > Signed-off-by: Felipe Balbi (Intel) <balbi@kernel.org> > Co-developed-by: Raymond Tan <raymond.tan@intel.com> > Signed-off-by: Raymond Tan <raymond.tan@intel.com> > Signed-off-by: Jarkko Nikula <jarkko.nikula@linux.intel.com> > --- > v3: > - Support for Quadrature x4 with swapped inputs and inverted inputs > removed. It turned out in review discussion both are board specific > features and questionable should they be even exposed to userspace. > Both features are postponed to future contribution if such need arises. > Patch 1/2 removed becaused of this. > - Error out if trying to set 1 clock period long spike filter. Previous > version silently disabled the filter and also sysfs behavior in that > case was inconsistent: write 10 but read returns 0. > - Line-continuation characters in INTEL_QEP_COUNTER_EXT_RW() aligned the > same way than others. > > v2: https://marc.info/?l=linux-iio&m=162204156231555&w=2 > - counter_to_qep() macro -> counter->priv > - Use sysfs_emit for user space returned values > - Use kstrbool for boolean values from userspace > - enable_write() reworked to be more readable > - Reworked synapse action control and new sysfs attribute "invert" > * Action control before was wrong - what HW does is signal inversion. > Implemented "invert" sysfs attribute for it and read-only action > mode sysfs returning constant "both edges" > - Renamed sysfs attribe "noise" as "spike_filter_ns" and define > programmable spike filter in terms of nanoseconds instead of raw > register value > - Above and "ceiling" sysfs attribe changed as count extensions instead > of device extensions > - Signal IDs rearranged to be zero based in order to prepare for counter > character device interface patches in order to ensure same userspace > sysfs paths > - Initializer macros for counter_signal and counter_synapse > initialization > - Grouping intel_qep_counter_ops, intel_qep_signal_ext and enums near to > their callback functions and use > - "invert" and "spike_filter_ns" sysfs attributes documented > - Other minor changes like local variable and empty line removal, etc > > v1: https://www.spinics.net/lists/linux-iio/msg59652.html > --- > Documentation/ABI/testing/sysfs-bus-counter | 9 + > drivers/counter/Kconfig | 10 + > drivers/counter/Makefile | 1 + > drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ > 4 files changed, 566 insertions(+) > create mode 100644 drivers/counter/intel-qep.c Hi Jarkko, I noticed the intel-qep.c file is missing an entry in the MAINTAINERS file. Would you be able to resubmit this patch with a proper entry added to MAINTAINERS so that users have the relevant contact info to reach the maintainers of this driver? You can keep my Signed-off tag on as well so that we know the code has already been reviewed. Thanks, William Breathitt Gray
Hi On 6/2/21 7:03 AM, William Breathitt Gray wrote: > On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: >> Documentation/ABI/testing/sysfs-bus-counter | 9 + >> drivers/counter/Kconfig | 10 + >> drivers/counter/Makefile | 1 + >> drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ >> 4 files changed, 566 insertions(+) >> create mode 100644 drivers/counter/intel-qep.c > > Hi Jarkko, > > I noticed the intel-qep.c file is missing an entry in the MAINTAINERS > file. Would you be able to resubmit this patch with a proper entry added > to MAINTAINERS so that users have the relevant contact info to reach the > maintainers of this driver? You can keep my Signed-off tag on as well so > that we know the code has already been reviewed. > Ah, added now. I sent the update and didn't dare to add your signature due the sidenote below :-) Sidenote, to my understanding Signed-off-by requires patch went through that person and Acked-by is used when maintainer accepts the patch but another person commits it with his/her Signed-off-by. Jarkko
On Wed, Jun 02, 2021 at 02:41:29PM +0300, Jarkko Nikula wrote: > Hi > > On 6/2/21 7:03 AM, William Breathitt Gray wrote: > > On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: > >> Documentation/ABI/testing/sysfs-bus-counter | 9 + > >> drivers/counter/Kconfig | 10 + > >> drivers/counter/Makefile | 1 + > >> drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ > >> 4 files changed, 566 insertions(+) > >> create mode 100644 drivers/counter/intel-qep.c > > > > Hi Jarkko, > > > > I noticed the intel-qep.c file is missing an entry in the MAINTAINERS > > file. Would you be able to resubmit this patch with a proper entry added > > to MAINTAINERS so that users have the relevant contact info to reach the > > maintainers of this driver? You can keep my Signed-off tag on as well so > > that we know the code has already been reviewed. > > > Ah, added now. I sent the update and didn't dare to add your signature > due the sidenote below :-) > > Sidenote, to my understanding Signed-off-by requires patch went through > that person and Acked-by is used when maintainer accepts the patch but > another person commits it with his/her Signed-off-by. > > Jarkko Is that so? I'm not really sure myself of the particular nuances -- I wonder if there's an explanation somewhere in the Documentation files, or if someone else can explain it more in-depth. Regardless you've made all the changes I've requested so here's my Ack for your troubles. :-) Acked-by: William Breathitt Gray <vilhelm.gray@gmail.com>
On Wed, 2 Jun 2021 23:23:44 +0900 William Breathitt Gray <vilhelm.gray@gmail.com> wrote: > On Wed, Jun 02, 2021 at 02:41:29PM +0300, Jarkko Nikula wrote: > > Hi > > > > On 6/2/21 7:03 AM, William Breathitt Gray wrote: > > > On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: > > >> Documentation/ABI/testing/sysfs-bus-counter | 9 + > > >> drivers/counter/Kconfig | 10 + > > >> drivers/counter/Makefile | 1 + > > >> drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ > > >> 4 files changed, 566 insertions(+) > > >> create mode 100644 drivers/counter/intel-qep.c > > > > > > Hi Jarkko, > > > > > > I noticed the intel-qep.c file is missing an entry in the MAINTAINERS > > > file. Would you be able to resubmit this patch with a proper entry added > > > to MAINTAINERS so that users have the relevant contact info to reach the > > > maintainers of this driver? You can keep my Signed-off tag on as well so > > > that we know the code has already been reviewed. > > > > > Ah, added now. I sent the update and didn't dare to add your signature > > due the sidenote below :-) > > > > Sidenote, to my understanding Signed-off-by requires patch went through > > that person and Acked-by is used when maintainer accepts the patch but > > another person commits it with his/her Signed-off-by. > > > > Jarkko > > Is that so? I'm not really sure myself of the particular nuances -- I > wonder if there's an explanation somewhere in the Documentation files, > or if someone else can explain it more in-depth. Regardless you've made > all the changes I've requested so here's my Ack for your troubles. :-) There is quite a it in https://www.kernel.org/doc/html/latest/process/submitting-patches.html > > Acked-by: William Breathitt Gray <vilhelm.gray@gmail.com> > Reviewed-by also fine for this case given I know you are the maintainer and will only give that if happy. Exactly what the meaning of Acked-by / Reviewed-by when given by a maintainer is a question that has been hotly debated a few times in the past. Jonathan
On Wed, Jun 02, 2021 at 03:37:06PM +0100, Jonathan Cameron wrote: > On Wed, 2 Jun 2021 23:23:44 +0900 > William Breathitt Gray <vilhelm.gray@gmail.com> wrote: > > > On Wed, Jun 02, 2021 at 02:41:29PM +0300, Jarkko Nikula wrote: > > > Hi > > > > > > On 6/2/21 7:03 AM, William Breathitt Gray wrote: > > > > On Thu, May 27, 2021 at 04:58:38PM +0300, Jarkko Nikula wrote: > > > >> Documentation/ABI/testing/sysfs-bus-counter | 9 + > > > >> drivers/counter/Kconfig | 10 + > > > >> drivers/counter/Makefile | 1 + > > > >> drivers/counter/intel-qep.c | 546 ++++++++++++++++++++ > > > >> 4 files changed, 566 insertions(+) > > > >> create mode 100644 drivers/counter/intel-qep.c > > > > > > > > Hi Jarkko, > > > > > > > > I noticed the intel-qep.c file is missing an entry in the MAINTAINERS > > > > file. Would you be able to resubmit this patch with a proper entry added > > > > to MAINTAINERS so that users have the relevant contact info to reach the > > > > maintainers of this driver? You can keep my Signed-off tag on as well so > > > > that we know the code has already been reviewed. > > > > > > > Ah, added now. I sent the update and didn't dare to add your signature > > > due the sidenote below :-) > > > > > > Sidenote, to my understanding Signed-off-by requires patch went through > > > that person and Acked-by is used when maintainer accepts the patch but > > > another person commits it with his/her Signed-off-by. > > > > > > Jarkko > > > > Is that so? I'm not really sure myself of the particular nuances -- I > > wonder if there's an explanation somewhere in the Documentation files, > > or if someone else can explain it more in-depth. Regardless you've made > > all the changes I've requested so here's my Ack for your troubles. :-) > There is quite a it in https://www.kernel.org/doc/html/latest/process/submitting-patches.html > > > > > Acked-by: William Breathitt Gray <vilhelm.gray@gmail.com> > > > > Reviewed-by also fine for this case given I know you are the maintainer > and will only give that if happy. Exactly what the meaning of > Acked-by / Reviewed-by when given by a maintainer is a question that > has been hotly debated a few times in the past. > > Jonathan Thanks for clearing that up for me! William Breathitt Gray
diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter index 566bd99fe0a5..e9d9e50f03be 100644 --- a/Documentation/ABI/testing/sysfs-bus-counter +++ b/Documentation/ABI/testing/sysfs-bus-counter @@ -193,6 +193,15 @@ Description: both edges: Any state transition. +What: /sys/bus/counter/devices/counterX/countY/spike_filter_ns +KernelVersion: 5.14 +Contact: linux-iio@vger.kernel.org +Description: + If the counter device supports programmable spike filter this + attribute indicates the value in nanoseconds where noise pulses + shorter or equal to configured value are ignored. Value 0 means + filter is disabled. + What: /sys/bus/counter/devices/counterX/name KernelVersion: 5.2 Contact: linux-iio@vger.kernel.org diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig index 5328705aa09c..d5d2540b30c2 100644 --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -91,4 +91,14 @@ config MICROCHIP_TCB_CAPTURE To compile this driver as a module, choose M here: the module will be called microchip-tcb-capture. +config INTEL_QEP + tristate "Intel Quadrature Encoder Peripheral driver" + 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. + endif # COUNTER diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile index cb646ed2f039..19742e6f5e3e 100644 --- a/drivers/counter/Makefile +++ b/drivers/counter/Makefile @@ -12,3 +12,4 @@ 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 diff --git a/drivers/counter/intel-qep.c b/drivers/counter/intel-qep.c new file mode 100644 index 000000000000..ab10ba33f46a --- /dev/null +++ b/drivers/counter/intel-qep.c @@ -0,0 +1,546 @@ +// 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/bitops.h> +#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 + +#define INTEL_QEP_COUNTER_EXT_RW(_name) \ +{ \ + .name = #_name, \ + .read = _name##_read, \ + .write = _name##_write, \ +} + +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, + unsigned long *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_count_function intel_qep_count_functions[] = { + COUNTER_COUNT_FUNCTION_QUADRATURE_X4, +}; + +static int intel_qep_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + *function = 0; + + return 0; +} + +static const enum counter_synapse_action intel_qep_synapse_actions[] = { + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, +}; + +static int intel_qep_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + *action = 0; + return 0; +} + +static const struct counter_ops intel_qep_counter_ops = { + .count_read = intel_qep_count_read, + .function_get = intel_qep_function_get, + .action_get = intel_qep_action_get, +}; + +#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 ssize_t ceiling_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + + pm_runtime_get_sync(qep->dev); + reg = intel_qep_readl(qep, INTEL_QEPMAX); + pm_runtime_put(qep->dev); + + return sysfs_emit(buf, "%u\n", reg); +} + +static ssize_t ceiling_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 max; + int ret; + + ret = kstrtou32(buf, 0, &max); + if (ret < 0) + return ret; + + 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); + ret = len; + +out: + mutex_unlock(&qep->lock); + return ret; +} + +static ssize_t enable_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + struct intel_qep *qep = counter->priv; + + return sysfs_emit(buf, "%u\n", qep->enabled); +} + +static ssize_t enable_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + bool val, changed; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + 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 len; +} + +static ssize_t spike_filter_ns_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + 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 sysfs_emit(buf, "0\n"); + } + reg = INTEL_QEPFLT_MAX_COUNT(intel_qep_readl(qep, INTEL_QEPFLT)); + pm_runtime_put(qep->dev); + + return sysfs_emit(buf, "%u\n", (reg + 2) * INTEL_QEP_CLK_PERIOD_NS); +} + +static ssize_t spike_filter_ns_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg, length; + bool enable; + int ret; + + ret = kstrtou32(buf, 0, &length); + if (ret < 0) + return ret; + + /* + * Spike filter length is (MAX_COUNT + 2) clock periods. + * Disable filter when userspace writes 0, enable for valid + * nanoseconds values and error out otherwise. + */ + 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 -EINVAL; + + 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); + ret = len; + +out: + mutex_unlock(&qep->lock); + return ret; +} + +static ssize_t preset_enable_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + 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); + return sysfs_emit(buf, "%u\n", !(reg & INTEL_QEPCON_COUNT_RST_MODE)); +} + +static ssize_t preset_enable_write(struct counter_device *counter, + struct counter_count *count, + void *priv, const char *buf, size_t len) +{ + struct intel_qep *qep = counter->priv; + u32 reg; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + 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); + ret = len; + +out: + mutex_unlock(&qep->lock); + + return ret; +} + +static const struct counter_count_ext intel_qep_count_ext[] = { + INTEL_QEP_COUNTER_EXT_RW(ceiling), + INTEL_QEP_COUNTER_EXT_RW(enable), + INTEL_QEP_COUNTER_EXT_RW(spike_filter_ns), + INTEL_QEP_COUNTER_EXT_RW(preset_enable) +}; + +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); +} + +#ifdef CONFIG_PM +static int intel_qep_suspend(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct 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 intel_qep_resume(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct 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; +} +#endif + +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");