| // 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"); |