root/drivers/counter/intel-qep.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 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);
        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(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;

        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(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;
        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(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;
        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 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;
        mutex_init(&qep->lock);

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