forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.3 KiB
207 lines
5.3 KiB
// 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");
|
|
|