[COMMON] iommu/exynos: parsing DT for sysmmu properties
authorJanghyuck Kim <janghyuck.kim@samsung.com>
Mon, 16 May 2016 12:19:54 +0000 (21:19 +0900)
committerSangwook Ju <sw.ju@samsung.com>
Mon, 14 May 2018 10:45:19 +0000 (19:45 +0900)
Sysmmu properties that read from DT are parsed and stored into driver
data.

1) QoS property is defined like below in DT.
sysmmu_node:
qos = <4>;

2) TLB properties are defined like below in DT.
sysmmu_node:
sysmmu,tlb_property =
<PRIV_WAY_CFG ID SYSMMU_ID(X)>,
<PRIV_WAY_CFG ID SYSMMU_ID(X)>,
<PUBLIC_WAY_CFG SYSMMU_NOID>;
Refer to include/dt-bindings/sysmmu/sysmmu.h for detailed definitions.

Change-Id: Ibdca6081351e997ba3a01f0173c751d30bb69a1d
Signed-off-by: Janghyuck Kim <janghyuck.kim@samsung.com>
drivers/iommu/exynos-iommu.c
drivers/iommu/exynos-iommu.h
include/dt-bindings/sysmmu/sysmmu.h [new file with mode: 0644]

index 509a444771799eeea0fc6574573e417247973d72..e847cbd19ff3c440f119e6a648c0ac32755dcc32 100644 (file)
@@ -28,6 +28,8 @@
 #include <asm/cacheflush.h>
 #include <asm/pgtable.h>
 
+#include <dt-bindings/sysmmu/sysmmu.h>
+
 #include "exynos-iommu.h"
 
 /* Default IOVA region: [0x1000000, 0xD0000000) */
@@ -412,6 +414,122 @@ static int get_hw_version(struct device *dev, void __iomem *sfrbase)
        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)
 {
@@ -462,12 +580,6 @@ 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);
 
@@ -475,7 +587,18 @@ static int __init exynos_sysmmu_probe(struct platform_device *pdev)
 
        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);
@@ -546,21 +669,101 @@ static void sysmmu_disable_from_master(struct device *master)
        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);
 }
index 955e66c7cfe995f78a85134ee3d8cfab859f402a..c849dab1ae1ee79d6058909af1fbc4cbbde96f60 100644 (file)
@@ -105,9 +105,26 @@ typedef u32 sysmmu_pte_t;
 #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
@@ -130,8 +147,10 @@ typedef u32 sysmmu_pte_t;
 /* 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
@@ -170,6 +189,8 @@ typedef u32 sysmmu_pte_t;
 #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)
@@ -218,6 +239,27 @@ struct exynos_iommu_owner {
        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
@@ -235,7 +277,9 @@ struct sysmmu_drvdata {
        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;
 };
 
diff --git a/include/dt-bindings/sysmmu/sysmmu.h b/include/dt-bindings/sysmmu/sysmmu.h
new file mode 100644 (file)
index 0000000..b972dc0
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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 */