|  | // 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(3, 0) | 
|  |  | 
|  | #define EMIF_POLL_INVL			10000 /* us */ | 
|  | #define EMIF_POLL_TIMEOUT		5000000 /* us */ | 
|  |  | 
|  | 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(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_clear_attr(0); | 
|  | emif_clear_attr(1); | 
|  | emif_clear_attr(2); | 
|  | emif_clear_attr(3); | 
|  |  | 
|  | 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, | 
|  |  | 
|  | 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); | 
|  | u64 val; | 
|  |  | 
|  | /* | 
|  | * This device supports upto 4 memory interfaces, but not all | 
|  | * interfaces are used on different platforms. The read out value of | 
|  | * CLEAN_EN field (which is a bitmap) could tell how many interfaces | 
|  | * are available. | 
|  | */ | 
|  | val = FIELD_GET(EMIF_CTRL_CLEAR_EN_MSK, readq(de->base + EMIF_CTRL)); | 
|  |  | 
|  | 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"); |