From b60e51524fd095dfde705cb860fa2d27c01bdfc7 Mon Sep 17 00:00:00 2001 From: Janghyuck Kim Date: Mon, 16 May 2016 21:19:54 +0900 Subject: [PATCH] [COMMON] iommu/exynos: parsing DT for sysmmu properties 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 = , , ; Refer to include/dt-bindings/sysmmu/sysmmu.h for detailed definitions. Change-Id: Ibdca6081351e997ba3a01f0173c751d30bb69a1d Signed-off-by: Janghyuck Kim --- drivers/iommu/exynos-iommu.c | 223 ++++++++++++++++++++++++++-- drivers/iommu/exynos-iommu.h | 50 ++++++- include/dt-bindings/sysmmu/sysmmu.h | 100 +++++++++++++ 3 files changed, 360 insertions(+), 13 deletions(-) create mode 100644 include/dt-bindings/sysmmu/sysmmu.h diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index 509a44477179..e847cbd19ff3 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -28,6 +28,8 @@ #include #include +#include + #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); } diff --git a/drivers/iommu/exynos-iommu.h b/drivers/iommu/exynos-iommu.h index 955e66c7cfe9..c849dab1ae1e 100644 --- a/drivers/iommu/exynos-iommu.h +++ b/drivers/iommu/exynos-iommu.h @@ -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 index 000000000000..b972dc086fee --- /dev/null +++ b/include/dt-bindings/sysmmu/sysmmu.h @@ -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 */ -- 2.20.1