#include <asm/cacheflush.h>
#include <asm/pgtable.h>
+#include <dt-bindings/sysmmu/sysmmu.h>
+
#include "exynos-iommu.h"
/* Default IOVA region: [0x1000000, 0xD0000000) */
return ret;
}
+static int __init sysmmu_parse_dt(struct device *sysmmu,
+ struct sysmmu_drvdata *drvdata)
+{
+ const char *props_name = "sysmmu,tlb_property";
+ struct tlb_props *tlb_props = &drvdata->tlb_props;
+ struct tlb_priv_id *priv_id_cfg = NULL;
+ struct tlb_priv_addr *priv_addr_cfg = NULL;
+ unsigned int qos = DEFAULT_QOS_VALUE;
+ unsigned int prop;
+ int ret;
+ int i, cnt, priv_id_cnt = 0, priv_addr_cnt = 0;
+ unsigned int priv_id_idx = 0, priv_addr_idx = 0;
+
+ /* Parsing QoS */
+ ret = of_property_read_u32_index(sysmmu->of_node, "qos", 0, &qos);
+ if (!ret && (qos > 15)) {
+ dev_err(sysmmu, "Invalid QoS value %d, use default.\n", qos);
+ qos = DEFAULT_QOS_VALUE;
+ }
+ drvdata->qos = qos;
+
+ /* Parsing TLB properties */
+ cnt = of_property_count_u32_elems(sysmmu->of_node, props_name);
+ for (i = 0; i < cnt; i+=2) {
+ ret = of_property_read_u32_index(sysmmu->of_node,
+ props_name, i, &prop);
+ if (ret) {
+ dev_err(sysmmu, "failed to get property."
+ "cnt = %d, ret = %d\n", i, ret);
+ return -EINVAL;
+ }
+
+ switch (prop & WAY_TYPE_MASK) {
+ case _PRIVATE_WAY_ID:
+ priv_id_cnt++;
+ tlb_props->flags |= TLB_WAY_PRIVATE_ID;
+ break;
+ case _PRIVATE_WAY_ADDR:
+ priv_addr_cnt++;
+ tlb_props->flags |= TLB_WAY_PRIVATE_ADDR;
+ break;
+ case _PUBLIC_WAY:
+ tlb_props->flags |= TLB_WAY_PUBLIC;
+ tlb_props->public_cfg = (prop & ~WAY_TYPE_MASK);
+ break;
+ default:
+ dev_err(sysmmu, "Undefined properties!: %#x\n", prop);
+ break;
+ }
+ }
+
+ if (priv_id_cnt) {
+ priv_id_cfg = kzalloc(sizeof(*priv_id_cfg) * priv_id_cnt,
+ GFP_KERNEL);
+ if (!priv_id_cfg)
+ return -ENOMEM;
+ }
+
+ if (priv_addr_cnt) {
+ priv_addr_cfg = kzalloc(sizeof(*priv_addr_cfg) * priv_addr_cnt,
+ GFP_KERNEL);
+ if (!priv_addr_cfg) {
+ ret = -ENOMEM;
+ goto err_priv_id;
+ }
+ }
+
+ for (i = 0; i < cnt; i+=2) {
+ ret = of_property_read_u32_index(sysmmu->of_node,
+ props_name, i, &prop);
+ if (ret) {
+ dev_err(sysmmu, "failed to get property again? "
+ "cnt = %d, ret = %d\n", i, ret);
+ ret = -EINVAL;
+ goto err_priv_addr;
+ }
+
+ switch (prop & WAY_TYPE_MASK) {
+ case _PRIVATE_WAY_ID:
+ priv_id_cfg[priv_id_idx].cfg = prop & ~WAY_TYPE_MASK;
+ ret = of_property_read_u32_index(sysmmu->of_node,
+ props_name, i+1, &priv_id_cfg[priv_id_idx].id);
+ if (ret) {
+ dev_err(sysmmu, "failed to get id property"
+ "cnt = %d, ret = %d\n", i, ret);
+ goto err_priv_addr;
+ }
+ priv_id_idx++;
+ break;
+ case _PRIVATE_WAY_ADDR:
+ priv_addr_cfg[priv_addr_idx].cfg = prop & ~WAY_TYPE_MASK;
+ priv_addr_idx++;
+ break;
+ case _PUBLIC_WAY:
+ break;
+ }
+ }
+
+ tlb_props->priv_id_cfg = priv_id_cfg;
+ tlb_props->priv_id_cnt = priv_id_cnt;
+
+ tlb_props->priv_addr_cfg = priv_addr_cfg;
+ tlb_props->priv_addr_cnt = priv_addr_cnt;
+
+ return 0;
+
+err_priv_addr:
+ if (priv_addr_cfg)
+ kfree(priv_addr_cfg);
+err_priv_id:
+ if (priv_id_cfg)
+ kfree(priv_id_cfg);
+
+ return ret;
+}
+
static struct iommu_ops exynos_iommu_ops;
static int __init exynos_sysmmu_probe(struct platform_device *pdev)
{
data->sysmmu = dev;
spin_lock_init(&data->lock);
ATOMIC_INIT_NOTIFIER_HEAD(&data->fault_notifiers);
- if (!sysmmu_drvdata_list) {
- sysmmu_drvdata_list = data;
- } else {
- data->next = sysmmu_drvdata_list->next;
- sysmmu_drvdata_list->next = data;
- }
platform_set_drvdata(pdev, data);
data->version = get_hw_version(dev, data->sfrbase);
- /* TODO: Parsing Device Tree for properties */
+ ret = sysmmu_parse_dt(data->sysmmu, data);
+ if (ret) {
+ dev_err(dev, "Failed to parse DT\n");
+ return ret;
+ }
+
+ if (!sysmmu_drvdata_list) {
+ sysmmu_drvdata_list = data;
+ } else {
+ data->next = sysmmu_drvdata_list->next;
+ sysmmu_drvdata_list->next = data;
+ }
iommu_device_set_ops(&data->iommu, &exynos_iommu_ops);
iommu_device_set_fwnode(&data->iommu, &dev->of_node->fwnode);
spin_unlock_irqrestore(&owner->lock, flags);
}
+static void __sysmmu_set_public_way(struct sysmmu_drvdata *drvdata,
+ unsigned int public_cfg)
+{
+ u32 cfg = __raw_readl(drvdata->sfrbase + REG_PUBLIC_WAY_CFG);
+ cfg &= ~MMU_PUBLIC_WAY_MASK;
+ cfg |= public_cfg;
+
+ writel_relaxed(cfg, drvdata->sfrbase + REG_PUBLIC_WAY_CFG);
+
+ dev_dbg(drvdata->sysmmu, "public_cfg : %#x\n", cfg);
+}
+
+static void __sysmmu_set_private_way_id(struct sysmmu_drvdata *drvdata,
+ unsigned int way_idx)
+{
+ struct tlb_priv_id *priv_cfg = drvdata->tlb_props.priv_id_cfg;
+ u32 cfg = __raw_readl(drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx));
+
+ cfg &= ~MMU_PRIVATE_WAY_MASK;
+ cfg |= MMU_WAY_CFG_ID_MATCHING | MMU_WAY_CFG_PRIVATE_ENABLE |
+ priv_cfg[way_idx].cfg;
+
+ writel_relaxed(cfg, drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx));
+ writel_relaxed(priv_cfg[way_idx].id,
+ drvdata->sfrbase + REG_PRIVATE_ID(way_idx));
+
+ dev_dbg(drvdata->sysmmu, "priv ID way[%d] cfg : %#x, id : %#x\n",
+ way_idx, cfg, priv_cfg[way_idx].id);
+}
+
+static void __sysmmu_set_private_way_addr(struct sysmmu_drvdata *drvdata,
+ unsigned int priv_addr_idx)
+{
+ struct tlb_priv_addr *priv_cfg = drvdata->tlb_props.priv_addr_cfg;
+ unsigned int way_idx = drvdata->tlb_props.priv_id_cnt + priv_addr_idx;
+ u32 cfg = __raw_readl(drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx));
+
+ cfg &= ~MMU_PRIVATE_WAY_MASK;
+ cfg |= MMU_WAY_CFG_ADDR_MATCHING | MMU_WAY_CFG_PRIVATE_ENABLE |
+ priv_cfg[priv_addr_idx].cfg;
+
+ writel_relaxed(cfg, drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx));
+
+ dev_dbg(drvdata->sysmmu, "priv ADDR way[%d] cfg : %#x\n", way_idx, cfg);
+}
+
static void __sysmmu_init_config(struct sysmmu_drvdata *drvdata)
{
- unsigned long cfg;
+ unsigned long cfg = 0;
writel_relaxed(CTRL_BLOCK, drvdata->sfrbase + REG_MMU_CTRL);
+ if (drvdata->qos != DEFAULT_QOS_VALUE)
+ cfg |= CFG_QOS_OVRRIDE | CFG_QOS(drvdata->qos);
+
if (MMU_MAJ_VER(drvdata->version) >= 7) {
- /* TODO: implement later */
+ u32 cfg = __raw_readl(drvdata->sfrbase + REG_MMU_CAPA_V7);
+ u32 tlb_way_num = MMU_CAPA_NUM_TLB_WAY(cfg);
+ u32 set_cnt = 0;
+ struct tlb_props *tlb_props = &drvdata->tlb_props;
+ unsigned int i;
+ int priv_id_cnt = tlb_props->priv_id_cnt;
+ int priv_addr_cnt = tlb_props->priv_addr_cnt;
+
+ if (tlb_props->flags & TLB_WAY_PUBLIC)
+ __sysmmu_set_public_way(drvdata,
+ tlb_props->public_cfg);
+
+ if (tlb_props->flags & TLB_WAY_PRIVATE_ID) {
+ for (i = 0; i < priv_id_cnt &&
+ set_cnt < tlb_way_num; i++, set_cnt++)
+ __sysmmu_set_private_way_id(drvdata, i);
+ }
+
+ if (tlb_props->flags & TLB_WAY_PRIVATE_ADDR) {
+ for (i = 0; i < priv_addr_cnt &&
+ set_cnt < tlb_way_num; i++, set_cnt++)
+ __sysmmu_set_private_way_addr(drvdata, i);
+ }
+
+ if (priv_id_cnt + priv_addr_cnt > tlb_way_num) {
+ dev_warn(drvdata->sysmmu,
+ "Too many values than TLB way count %d,"
+ " so ignored!\n", tlb_way_num);
+ dev_warn(drvdata->sysmmu,
+ "Number of private way id/addr = %d/%d\n",
+ priv_id_cnt, priv_addr_cnt);
+ }
} else {
__exynos_sysmmu_set_prefbuf_axi_id(drvdata);
if (has_sysmmu_set_associative_tlb(drvdata->sfrbase))
__sysmmu_set_tlb_line_size(drvdata->sfrbase);
+ cfg |= CFG_FLPDCACHE | CFG_ACGEN;
}
- cfg = CFG_FLPDCACHE | CFG_ACGEN;
cfg |= __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & ~CFG_MASK;
writel_relaxed(cfg, drvdata->sfrbase + REG_MMU_CFG);
}
#define CTRL_DISABLE 0x0
#define CTRL_BLOCK_DISABLE 0x3
-#define CFG_MASK 0x01101FBC /* Selecting bit 24, 20, 12-7, 5-2 */
+#define CFG_MASK 0x301F1F8C /* Bit 29-28, 20-16, 12-7, 3-2 */
#define CFG_ACGEN (1 << 24)
#define CFG_FLPDCACHE (1 << 20)
+#define CFG_QOS_OVRRIDE (1 << 11)
+#define CFG_QOS(n) (((n) & 0xF) << 7)
+
+#define MMU_WAY_CFG_MASK_PREFETCH (1 << 1)
+#define MMU_WAY_CFG_MASK_PREFETCH_DIR (3 << 2)
+#define MMU_WAY_CFG_MASK_MATCH_METHOD (1 << 4)
+#define MMU_WAY_CFG_MASK_FETCH_SIZE (7 << 5)
+#define MMU_WAY_CFG_MASK_TARGET_CH (3 << 8)
+
+#define MMU_WAY_CFG_ID_MATCHING (1 << 4)
+#define MMU_WAY_CFG_ADDR_MATCHING (0 << 4)
+#define MMU_WAY_CFG_PRIVATE_ENABLE (1 << 0)
+
+#define MMU_PUBLIC_WAY_MASK (MMU_WAY_CFG_MASK_PREFETCH | \
+ MMU_WAY_CFG_MASK_PREFETCH_DIR | MMU_WAY_CFG_MASK_FETCH_SIZE)
+#define MMU_PRIVATE_WAY_MASK (MMU_PUBLIC_WAY_MASK | \
+ MMU_WAY_CFG_MASK_MATCH_METHOD | MMU_WAY_CFG_MASK_TARGET_CH)
#define REG_PT_BASE_PPN 0x00C
#define REG_MMU_FLUSH 0x010
/* For SysMMU v7.x */
#define REG_MMU_CAPA_V7 0x870
#define REG_PUBLIC_WAY_CFG 0x120
-#define REG_PRIVATE_WAY_CFG(n) (0x200 + ((n) * 0x10))
-#define REG_PRIVATE_ID(n) (0x20C + ((n) * 0x10))
+#define REG_PRIVATE_WAY_CFG(n) (0x200 + ((n) * 0x10))
+#define REG_PRIVATE_ADDR_START(n) (0x204 + ((n) * 0x10))
+#define REG_PRIVATE_ADDR_END(n) (0x208 + ((n) * 0x10))
+#define REG_PRIVATE_ID(n) (0x20C + ((n) * 0x10))
#define REG_FAULT_ADDR 0x070
#define REG_FAULT_TRANS_INFO 0x078
#define REG_TLB_READ 0x1000
#define MAKE_MMU_VER(maj, min) ((((maj) & 0xF) << 11) | \
(((min) & 0x7F) << 4))
+#define DEFAULT_QOS_VALUE -1
+
#define SYSMMU_FAULT_BITS 4
#define SYSMMU_FAULT_SHIFT 16
#define SYSMMU_FAULT_MASK ((1 << SYSMMU_FAULT_BITS) - 1)
void *token;
};
+struct tlb_priv_addr {
+ unsigned int cfg;
+};
+
+struct tlb_priv_id {
+ unsigned int cfg;
+ unsigned int id;
+};
+
+#define TLB_WAY_PRIVATE_ID (1 << 0)
+#define TLB_WAY_PRIVATE_ADDR (1 << 1)
+#define TLB_WAY_PUBLIC (1 << 2)
+struct tlb_props {
+ int flags;
+ int priv_id_cnt;
+ int priv_addr_cnt;
+ unsigned int public_cfg;
+ struct tlb_priv_id *priv_id_cfg;
+ struct tlb_priv_addr *priv_addr_cfg;
+};
+
/*
* This structure hold all data of a single SYSMMU controller, this includes
* hw resources like registers and clocks, pointers and list nodes to connect
spinlock_t lock; /* lock for modyfying state */
phys_addr_t pgtable; /* assigned page table structure */
int version; /* our version */
+ int qos;
struct atomic_notifier_head fault_notifiers;
+ struct tlb_props tlb_props;
bool is_suspended;
};
--- /dev/null
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Device Tree binding constants for Exynos System MMU.
+ */
+
+#ifndef _DT_BINDINGS_EXYNOS_SYSTEM_MMU_H
+#define _DT_BINDINGS_EXYNOS_SYSTEM_MMU_H
+
+/*
+ * RESERVED field is used to define properties between DT and driver.
+ *
+ * CFG[13:12]
+ * - 0x0 = Private way, ID matching
+ * - 0x1 = Private way, Address matching
+ * - 0x2 = Public way
+ */
+
+#define WAY_TYPE_MASK (0x3 << 12)
+#define _PRIVATE_WAY_ID (0x0 << 12)
+#define _PRIVATE_WAY_ADDR (0x1 << 12)
+#define _PUBLIC_WAY (0x2 << 12)
+
+#define _TARGET_NONE (0x0 << 8)
+#define _TARGET_READ (0x1 << 8)
+#define _TARGET_WRITE (0x2 << 8)
+#define _TARGET_READWRITE (0x3 << 8)
+
+#define _DIR_DESCENDING (0x0 << 2)
+#define _DIR_ASCENDING (0x1 << 2)
+#define _DIR_PREDICTION (0x2 << 2)
+
+#define _PREFETCH_ENABLE (0x1 << 1)
+#define _PREFETCH_DISABLE (0x0 << 1)
+
+/*
+ * Use below definitions for TLB properties setting.
+ *
+ * Each definition is combination of "configuration" and "ID".
+ *
+ * "configuration" is consist of public/private type and BL(Burst Length).
+ * It should be expressed like (WAY_TYPE_DEFINE | BL_DEFINE).
+ *
+ * "ID" is meaningful for private ID matching only.
+ * For public way and private addr matching, use SYSMMU_NOID.
+ *
+ * Here is an example.
+ * In device tree, it is described like below.
+ * sysmmu,tlb_property =
+ * <(SYSMMU_PRIV_ID_PREFETCH_ASCENDING_READ | SYSMMU_BL32) SYSMMU_ID(0x1)>,
+ * <(SYSMMU_PRIV_ADDR_NO_PREFETCH_READ | SYSMMU_BL16) SYSMMU_NOID>,
+ * <(SYSMMU_PUBLIC_NO_PREFETCH | SYSMMU_BL16) SYSMMU_NOID>;
+ */
+
+/* Definitions for "ID" */
+#define SYSMMU_ID(id) (0xFFFF << 16 | (id))
+#define SYSMMU_ID_MASK(id,mask) ((mask) << 16 | (id))
+#define SYSMMU_NOID 0
+
+/* BL_DEFINE: Definitions for burst length "configuration". */
+#define SYSMMU_BL1 (0x0 << 5)
+#define SYSMMU_BL2 (0x1 << 5)
+#define SYSMMU_BL4 (0x2 << 5)
+#define SYSMMU_BL8 (0x3 << 5)
+#define SYSMMU_BL16 (0x4 << 5)
+#define SYSMMU_BL32 (0x5 << 5)
+
+/* WAY_TYPE_DEFINE: Definitions for public way "configuration". */
+#define SYSMMU_PUBLIC_NO_PREFETCH (_PUBLIC_WAY | _PREFETCH_DISABLE)
+#define SYSMMU_PUBLIC_PREFETCH_ASCENDING \
+ (_PUBLIC_WAY | _PREFETCH_ENABLE | _DIR_ASCENDING)
+
+/* WAY_TYPE_DEFINE: Definitions for private ID matching "configuration". */
+#define SYSMMU_PRIV_ID_NO_PREFETCH_READ \
+ (_PRIVATE_WAY_ID | _TARGET_READ | _PREFETCH_DISABLE)
+#define SYSMMU_PRIV_ID_NO_PREFETCH_WRITE \
+ (_PRIVATE_WAY_ID | _TARGET_WRITE | _PREFETCH_DISABLE)
+#define SYSMMU_PRIV_ID_PREFETCH_ASCENDING_READ \
+ (_PRIVATE_WAY_ID | _TARGET_READ | _DIR_ASCENDING | _PREFETCH_ENABLE)
+#define SYSMMU_PRIV_ID_PREFETCH_ASCENDING_WRITE \
+ (_PRIVATE_WAY_ID | _TARGET_WRITE | _DIR_ASCENDING | _PREFETCH_ENABLE)
+#define SYSMMU_PRIV_ID_PREFETCH_PREDICTION_READ \
+ (_PRIVATE_WAY_ID | _TARGET_READ | _DIR_PREDICTION | _PREFETCH_ENABLE)
+#define SYSMMU_PRIV_ID_PREFETCH_PREDICTION_WRITE \
+ (_PRIVATE_WAY_ID | _TARGET_WRITE | _DIR_PREDICTION | _PREFETCH_ENABLE)
+
+/* WAY_TYPE_DEFINE: Definitions for private address matching "configuration". */
+#define SYSMMU_PRIV_ADDR_NO_PREFETCH_READ \
+ (_PRIVATE_WAY_ADDR | _TARGET_READ | _PREFETCH_DISABLE)
+#define SYSMMU_PRIV_ADDR_NO_PREFETCH_WRITE \
+ (_PRIVATE_WAY_ADDR | _TARGET_WRITE | _PREFETCH_DISABLE)
+#define SYSMMU_PRIV_ADDR_NO_PREFETCH_READWRITE \
+ (_PRIVATE_WAY_ADDR | _TARGET_READWRITE | _PREFETCH_DISABLE)
+#define SYSMMU_PRIV_ADDR_PREFETCH_ASCENDING_READ \
+ (_PRIVATE_WAY_ADDR | _TARGET_READ | _DIR_ASCENDING | _PREFETCH_ENABLE)
+#endif /* _DT_BINDINGS_EXYNOS_SYSTEM_MMU_H */