root/drivers/memory/dfl-emif.c
// SPDX-License-Identifier: GPL-2.0
/*
 * DFL device driver for EMIF private feature
 *
 * Copyright (C) 2020 Intel Corporation, Inc.
 *
 */
#include <linux/bitfield.h>
#include <linux/dfl.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/types.h>

#define FME_FEATURE_ID_EMIF             0x9

#define EMIF_STAT                       0x8
#define EMIF_STAT_INIT_DONE_SFT         0
#define EMIF_STAT_CALC_FAIL_SFT         8
#define EMIF_STAT_CLEAR_BUSY_SFT        16
#define EMIF_CTRL                       0x10
#define EMIF_CTRL_CLEAR_EN_SFT          0
#define EMIF_CTRL_CLEAR_EN_MSK          GENMASK_ULL(7, 0)

#define EMIF_POLL_INVL                  10000 /* us */
#define EMIF_POLL_TIMEOUT               5000000 /* us */

/*
 * The Capability Register replaces the Control Register (at the same
 * offset) for EMIF feature revisions > 0. The bitmask that indicates
 * the presence of memory channels exists in both the Capability Register
 * and Control Register definitions. These can be thought of as a C union.
 * The Capability Register definitions are used to check for the existence
 * of a memory channel, and the Control Register definitions are used for
 * managing the memory-clear functionality in revision 0.
 */
#define EMIF_CAPABILITY_BASE            0x10
#define EMIF_CAPABILITY_CHN_MSK_V0      GENMASK_ULL(3, 0)
#define EMIF_CAPABILITY_CHN_MSK         GENMASK_ULL(7, 0)

struct dfl_emif {
        struct device *dev;
        void __iomem *base;
        spinlock_t lock;        /* Serialises access to EMIF_CTRL reg */
};

struct emif_attr {
        struct device_attribute attr;
        u32 shift;
        u32 index;
};

#define to_emif_attr(dev_attr) \
        container_of(dev_attr, struct emif_attr, attr)

static ssize_t emif_state_show(struct device *dev,
                               struct device_attribute *attr, char *buf)
{
        struct emif_attr *eattr = to_emif_attr(attr);
        struct dfl_emif *de = dev_get_drvdata(dev);
        u64 val;

        val = readq(de->base + EMIF_STAT);

        return sysfs_emit(buf, "%u\n",
                          !!(val & BIT_ULL(eattr->shift + eattr->index)));
}

static ssize_t emif_clear_store(struct device *dev,
                                struct device_attribute *attr,
                                const char *buf, size_t count)
{
        struct emif_attr *eattr = to_emif_attr(attr);
        struct dfl_emif *de = dev_get_drvdata(dev);
        u64 clear_busy_msk, clear_en_msk, val;
        void __iomem *base = de->base;

        if (!sysfs_streq(buf, "1"))
                return -EINVAL;

        clear_busy_msk = BIT_ULL(EMIF_STAT_CLEAR_BUSY_SFT + eattr->index);
        clear_en_msk = BIT_ULL(EMIF_CTRL_CLEAR_EN_SFT + eattr->index);

        spin_lock(&de->lock);
        /* The CLEAR_EN field is WO, but other fields are RW */
        val = readq(base + EMIF_CTRL);
        val &= ~EMIF_CTRL_CLEAR_EN_MSK;
        val |= clear_en_msk;
        writeq(val, base + EMIF_CTRL);
        spin_unlock(&de->lock);

        if (readq_poll_timeout(base + EMIF_STAT, val,
                               !(val & clear_busy_msk),
                               EMIF_POLL_INVL, EMIF_POLL_TIMEOUT)) {
                dev_err(de->dev, "timeout, fail to clear\n");
                return -ETIMEDOUT;
        }

        return count;
}

#define emif_state_attr(_name, _shift, _index)                          \
        static struct emif_attr emif_attr_##inf##_index##_##_name =     \
                { .attr = __ATTR(inf##_index##_##_name, 0444,           \
                                 emif_state_show, NULL),                \
                  .shift = (_shift), .index = (_index) }

#define emif_clear_attr(_index)                                         \
        static struct emif_attr emif_attr_##inf##_index##_clear =       \
                { .attr = __ATTR(inf##_index##_clear, 0200,             \
                                 NULL, emif_clear_store),               \
                  .index = (_index) }

emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 0);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 1);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 2);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 3);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 4);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 5);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 6);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 7);

emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 0);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 1);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 2);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 3);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 4);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 5);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 6);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 7);


emif_clear_attr(0);
emif_clear_attr(1);
emif_clear_attr(2);
emif_clear_attr(3);
emif_clear_attr(4);
emif_clear_attr(5);
emif_clear_attr(6);
emif_clear_attr(7);


static struct attribute *dfl_emif_attrs[] = {
        &emif_attr_inf0_init_done.attr.attr,
        &emif_attr_inf0_cal_fail.attr.attr,
        &emif_attr_inf0_clear.attr.attr,

        &emif_attr_inf1_init_done.attr.attr,
        &emif_attr_inf1_cal_fail.attr.attr,
        &emif_attr_inf1_clear.attr.attr,

        &emif_attr_inf2_init_done.attr.attr,
        &emif_attr_inf2_cal_fail.attr.attr,
        &emif_attr_inf2_clear.attr.attr,

        &emif_attr_inf3_init_done.attr.attr,
        &emif_attr_inf3_cal_fail.attr.attr,
        &emif_attr_inf3_clear.attr.attr,

        &emif_attr_inf4_init_done.attr.attr,
        &emif_attr_inf4_cal_fail.attr.attr,
        &emif_attr_inf4_clear.attr.attr,

        &emif_attr_inf5_init_done.attr.attr,
        &emif_attr_inf5_cal_fail.attr.attr,
        &emif_attr_inf5_clear.attr.attr,

        &emif_attr_inf6_init_done.attr.attr,
        &emif_attr_inf6_cal_fail.attr.attr,
        &emif_attr_inf6_clear.attr.attr,

        &emif_attr_inf7_init_done.attr.attr,
        &emif_attr_inf7_cal_fail.attr.attr,
        &emif_attr_inf7_clear.attr.attr,

        NULL,
};

static umode_t dfl_emif_visible(struct kobject *kobj,
                                struct attribute *attr, int n)
{
        struct dfl_emif *de = dev_get_drvdata(kobj_to_dev(kobj));
        struct emif_attr *eattr = container_of(attr, struct emif_attr,
                                               attr.attr);
        struct dfl_device *ddev = to_dfl_dev(de->dev);
        u64 val;

        /*
         * This device supports up to 8 memory interfaces, but not all
         * interfaces are used on different platforms. The read out value of
         * CAPABILITY_CHN_MSK field (which is a bitmap) indicates which
         * interfaces are available.
         */
        if (ddev->revision > 0 && strstr(attr->name, "_clear"))
                return 0;

        if (ddev->revision == 0)
                val = FIELD_GET(EMIF_CAPABILITY_CHN_MSK_V0,
                                readq(de->base + EMIF_CAPABILITY_BASE));
        else
                val = FIELD_GET(EMIF_CAPABILITY_CHN_MSK,
                                readq(de->base + EMIF_CAPABILITY_BASE));

        return (val & BIT_ULL(eattr->index)) ? attr->mode : 0;
}

static const struct attribute_group dfl_emif_group = {
        .is_visible = dfl_emif_visible,
        .attrs = dfl_emif_attrs,
};

static const struct attribute_group *dfl_emif_groups[] = {
        &dfl_emif_group,
        NULL,
};

static int dfl_emif_probe(struct dfl_device *ddev)
{
        struct device *dev = &ddev->dev;
        struct dfl_emif *de;

        de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL);
        if (!de)
                return -ENOMEM;

        de->base = devm_ioremap_resource(dev, &ddev->mmio_res);
        if (IS_ERR(de->base))
                return PTR_ERR(de->base);

        de->dev = dev;
        spin_lock_init(&de->lock);
        dev_set_drvdata(dev, de);

        return 0;
}

static const struct dfl_device_id dfl_emif_ids[] = {
        { FME_ID, FME_FEATURE_ID_EMIF },
        { }
};
MODULE_DEVICE_TABLE(dfl, dfl_emif_ids);

static struct dfl_driver dfl_emif_driver = {
        .drv    = {
                .name       = "dfl-emif",
                .dev_groups = dfl_emif_groups,
        },
        .id_table = dfl_emif_ids,
        .probe   = dfl_emif_probe,
};
module_dfl_driver(dfl_emif_driver);

MODULE_DESCRIPTION("DFL EMIF driver");
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");