| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 1 | /* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 2 |  * | 
 | 3 |  * This program is free software; you can redistribute it and/or modify | 
 | 4 |  * it under the terms of the GNU General Public License version 2 and | 
 | 5 |  * only version 2 as published by the Free Software Foundation. | 
 | 6 |  * | 
 | 7 |  * This program is distributed in the hope that it will be useful, | 
 | 8 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 9 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 10 |  * GNU General Public License for more details. | 
 | 11 |  * | 
 | 12 |  * You should have received a copy of the GNU General Public License | 
 | 13 |  * along with this program; if not, write to the Free Software | 
 | 14 |  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | 
 | 15 |  * 02110-1301, USA. | 
 | 16 |  */ | 
 | 17 |  | 
 | 18 | #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt | 
 | 19 | #include <linux/kernel.h> | 
 | 20 | #include <linux/module.h> | 
 | 21 | #include <linux/platform_device.h> | 
 | 22 | #include <linux/errno.h> | 
 | 23 | #include <linux/io.h> | 
 | 24 | #include <linux/interrupt.h> | 
 | 25 | #include <linux/list.h> | 
 | 26 | #include <linux/spinlock.h> | 
 | 27 | #include <linux/slab.h> | 
 | 28 | #include <linux/iommu.h> | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 29 | #include <linux/clk.h> | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 30 |  | 
 | 31 | #include <asm/cacheflush.h> | 
 | 32 | #include <asm/sizes.h> | 
 | 33 |  | 
 | 34 | #include <mach/iommu_hw-8xxx.h> | 
 | 35 | #include <mach/iommu.h> | 
 | 36 |  | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 37 | #define MRC(reg, processor, op1, crn, crm, op2)				\ | 
 | 38 | __asm__ __volatile__ (							\ | 
 | 39 | "   mrc   "   #processor "," #op1 ", %0,"  #crn "," #crm "," #op2 "\n"  \ | 
 | 40 | : "=r" (reg)) | 
 | 41 |  | 
 | 42 | #define RCP15_PRRR(reg)		MRC(reg, p15, 0, c10, c2, 0) | 
 | 43 | #define RCP15_NMRR(reg)		MRC(reg, p15, 0, c10, c2, 1) | 
 | 44 |  | 
 | 45 | static int msm_iommu_tex_class[4]; | 
 | 46 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 47 | DEFINE_SPINLOCK(msm_iommu_lock); | 
 | 48 |  | 
 | 49 | struct msm_priv { | 
 | 50 | 	unsigned long *pgtable; | 
 | 51 | 	struct list_head list_attached; | 
 | 52 | }; | 
 | 53 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 54 | static int __enable_clocks(struct msm_iommu_drvdata *drvdata) | 
 | 55 | { | 
 | 56 | 	int ret; | 
 | 57 |  | 
 | 58 | 	ret = clk_enable(drvdata->pclk); | 
 | 59 | 	if (ret) | 
 | 60 | 		goto fail; | 
 | 61 |  | 
 | 62 | 	if (drvdata->clk) { | 
 | 63 | 		ret = clk_enable(drvdata->clk); | 
 | 64 | 		if (ret) | 
 | 65 | 			clk_disable(drvdata->pclk); | 
 | 66 | 	} | 
 | 67 | fail: | 
 | 68 | 	return ret; | 
 | 69 | } | 
 | 70 |  | 
 | 71 | static void __disable_clocks(struct msm_iommu_drvdata *drvdata) | 
 | 72 | { | 
 | 73 | 	if (drvdata->clk) | 
 | 74 | 		clk_disable(drvdata->clk); | 
 | 75 | 	clk_disable(drvdata->pclk); | 
 | 76 | } | 
 | 77 |  | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 78 | static int __flush_iotlb(struct iommu_domain *domain) | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 79 | { | 
 | 80 | 	struct msm_priv *priv = domain->priv; | 
 | 81 | 	struct msm_iommu_drvdata *iommu_drvdata; | 
 | 82 | 	struct msm_iommu_ctx_drvdata *ctx_drvdata; | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 83 | 	int ret = 0; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 84 | #ifndef CONFIG_IOMMU_PGTABLES_L2 | 
 | 85 | 	unsigned long *fl_table = priv->pgtable; | 
 | 86 | 	int i; | 
 | 87 |  | 
| Stepan Moskovchenko | f6f41eb | 2010-11-12 19:29:54 -0800 | [diff] [blame] | 88 | 	if (!list_empty(&priv->list_attached)) { | 
 | 89 | 		dmac_flush_range(fl_table, fl_table + SZ_16K); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 90 |  | 
| Stepan Moskovchenko | f6f41eb | 2010-11-12 19:29:54 -0800 | [diff] [blame] | 91 | 		for (i = 0; i < NUM_FL_PTE; i++) | 
 | 92 | 			if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) { | 
 | 93 | 				void *sl_table = __va(fl_table[i] & | 
 | 94 | 								FL_BASE_MASK); | 
 | 95 | 				dmac_flush_range(sl_table, sl_table + SZ_4K); | 
 | 96 | 			} | 
 | 97 | 	} | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 98 | #endif | 
 | 99 |  | 
 | 100 | 	list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { | 
 | 101 | 		if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent) | 
 | 102 | 			BUG(); | 
 | 103 |  | 
 | 104 | 		iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 105 | 		BUG_ON(!iommu_drvdata); | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 106 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 107 | 		ret = __enable_clocks(iommu_drvdata); | 
 | 108 | 		if (ret) | 
 | 109 | 			goto fail; | 
 | 110 |  | 
 | 111 | 		SET_CTX_TLBIALL(iommu_drvdata->base, ctx_drvdata->num, 0); | 
 | 112 | 		__disable_clocks(iommu_drvdata); | 
 | 113 | 	} | 
 | 114 | fail: | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 115 | 	return ret; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 116 | } | 
 | 117 |  | 
 | 118 | static void __reset_context(void __iomem *base, int ctx) | 
 | 119 | { | 
 | 120 | 	SET_BPRCOSH(base, ctx, 0); | 
 | 121 | 	SET_BPRCISH(base, ctx, 0); | 
 | 122 | 	SET_BPRCNSH(base, ctx, 0); | 
 | 123 | 	SET_BPSHCFG(base, ctx, 0); | 
 | 124 | 	SET_BPMTCFG(base, ctx, 0); | 
 | 125 | 	SET_ACTLR(base, ctx, 0); | 
 | 126 | 	SET_SCTLR(base, ctx, 0); | 
 | 127 | 	SET_FSRRESTORE(base, ctx, 0); | 
 | 128 | 	SET_TTBR0(base, ctx, 0); | 
 | 129 | 	SET_TTBR1(base, ctx, 0); | 
 | 130 | 	SET_TTBCR(base, ctx, 0); | 
 | 131 | 	SET_BFBCR(base, ctx, 0); | 
 | 132 | 	SET_PAR(base, ctx, 0); | 
 | 133 | 	SET_FAR(base, ctx, 0); | 
 | 134 | 	SET_CTX_TLBIALL(base, ctx, 0); | 
 | 135 | 	SET_TLBFLPTER(base, ctx, 0); | 
 | 136 | 	SET_TLBSLPTER(base, ctx, 0); | 
 | 137 | 	SET_TLBLKCR(base, ctx, 0); | 
 | 138 | 	SET_PRRR(base, ctx, 0); | 
 | 139 | 	SET_NMRR(base, ctx, 0); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 140 | } | 
 | 141 |  | 
 | 142 | static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) | 
 | 143 | { | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 144 | 	unsigned int prrr, nmrr; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 145 | 	__reset_context(base, ctx); | 
 | 146 |  | 
 | 147 | 	/* Set up HTW mode */ | 
 | 148 | 	/* TLB miss configuration: perform HTW on miss */ | 
 | 149 | 	SET_TLBMCFG(base, ctx, 0x3); | 
 | 150 |  | 
 | 151 | 	/* V2P configuration: HTW for access */ | 
 | 152 | 	SET_V2PCFG(base, ctx, 0x3); | 
 | 153 |  | 
 | 154 | 	SET_TTBCR(base, ctx, 0); | 
 | 155 | 	SET_TTBR0_PA(base, ctx, (pgtable >> 14)); | 
 | 156 |  | 
 | 157 | 	/* Invalidate the TLB for this context */ | 
 | 158 | 	SET_CTX_TLBIALL(base, ctx, 0); | 
 | 159 |  | 
 | 160 | 	/* Set interrupt number to "secure" interrupt */ | 
 | 161 | 	SET_IRPTNDX(base, ctx, 0); | 
 | 162 |  | 
 | 163 | 	/* Enable context fault interrupt */ | 
 | 164 | 	SET_CFEIE(base, ctx, 1); | 
 | 165 |  | 
 | 166 | 	/* Stall access on a context fault and let the handler deal with it */ | 
 | 167 | 	SET_CFCFG(base, ctx, 1); | 
 | 168 |  | 
 | 169 | 	/* Redirect all cacheable requests to L2 slave port. */ | 
 | 170 | 	SET_RCISH(base, ctx, 1); | 
 | 171 | 	SET_RCOSH(base, ctx, 1); | 
 | 172 | 	SET_RCNSH(base, ctx, 1); | 
 | 173 |  | 
 | 174 | 	/* Turn on TEX Remap */ | 
 | 175 | 	SET_TRE(base, ctx, 1); | 
 | 176 |  | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 177 | 	/* Set TEX remap attributes */ | 
 | 178 | 	RCP15_PRRR(prrr); | 
 | 179 | 	RCP15_NMRR(nmrr); | 
 | 180 | 	SET_PRRR(base, ctx, prrr); | 
 | 181 | 	SET_NMRR(base, ctx, nmrr); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 182 |  | 
 | 183 | 	/* Turn on BFB prefetch */ | 
 | 184 | 	SET_BFBDFE(base, ctx, 1); | 
 | 185 |  | 
 | 186 | #ifdef CONFIG_IOMMU_PGTABLES_L2 | 
 | 187 | 	/* Configure page tables as inner-cacheable and shareable to reduce | 
 | 188 | 	 * the TLB miss penalty. | 
 | 189 | 	 */ | 
 | 190 | 	SET_TTBR0_SH(base, ctx, 1); | 
 | 191 | 	SET_TTBR1_SH(base, ctx, 1); | 
 | 192 |  | 
 | 193 | 	SET_TTBR0_NOS(base, ctx, 1); | 
 | 194 | 	SET_TTBR1_NOS(base, ctx, 1); | 
 | 195 |  | 
 | 196 | 	SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */ | 
 | 197 | 	SET_TTBR0_IRGNL(base, ctx, 1); | 
 | 198 |  | 
 | 199 | 	SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */ | 
 | 200 | 	SET_TTBR1_IRGNL(base, ctx, 1); | 
 | 201 |  | 
 | 202 | 	SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */ | 
 | 203 | 	SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */ | 
 | 204 | #endif | 
 | 205 |  | 
 | 206 | 	/* Enable the MMU */ | 
 | 207 | 	SET_M(base, ctx, 1); | 
 | 208 | } | 
 | 209 |  | 
 | 210 | static int msm_iommu_domain_init(struct iommu_domain *domain) | 
 | 211 | { | 
 | 212 | 	struct msm_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
 | 213 |  | 
 | 214 | 	if (!priv) | 
 | 215 | 		goto fail_nomem; | 
 | 216 |  | 
 | 217 | 	INIT_LIST_HEAD(&priv->list_attached); | 
 | 218 | 	priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL, | 
 | 219 | 							  get_order(SZ_16K)); | 
 | 220 |  | 
 | 221 | 	if (!priv->pgtable) | 
 | 222 | 		goto fail_nomem; | 
 | 223 |  | 
 | 224 | 	memset(priv->pgtable, 0, SZ_16K); | 
 | 225 | 	domain->priv = priv; | 
 | 226 | 	return 0; | 
 | 227 |  | 
 | 228 | fail_nomem: | 
 | 229 | 	kfree(priv); | 
 | 230 | 	return -ENOMEM; | 
 | 231 | } | 
 | 232 |  | 
 | 233 | static void msm_iommu_domain_destroy(struct iommu_domain *domain) | 
 | 234 | { | 
 | 235 | 	struct msm_priv *priv; | 
 | 236 | 	unsigned long flags; | 
 | 237 | 	unsigned long *fl_table; | 
 | 238 | 	int i; | 
 | 239 |  | 
 | 240 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
 | 241 | 	priv = domain->priv; | 
 | 242 | 	domain->priv = NULL; | 
 | 243 |  | 
 | 244 | 	if (priv) { | 
 | 245 | 		fl_table = priv->pgtable; | 
 | 246 |  | 
 | 247 | 		for (i = 0; i < NUM_FL_PTE; i++) | 
 | 248 | 			if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) | 
 | 249 | 				free_page((unsigned long) __va(((fl_table[i]) & | 
 | 250 | 								FL_BASE_MASK))); | 
 | 251 |  | 
 | 252 | 		free_pages((unsigned long)priv->pgtable, get_order(SZ_16K)); | 
 | 253 | 		priv->pgtable = NULL; | 
 | 254 | 	} | 
 | 255 |  | 
 | 256 | 	kfree(priv); | 
 | 257 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 258 | } | 
 | 259 |  | 
 | 260 | static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) | 
 | 261 | { | 
 | 262 | 	struct msm_priv *priv; | 
 | 263 | 	struct msm_iommu_ctx_dev *ctx_dev; | 
 | 264 | 	struct msm_iommu_drvdata *iommu_drvdata; | 
 | 265 | 	struct msm_iommu_ctx_drvdata *ctx_drvdata; | 
 | 266 | 	struct msm_iommu_ctx_drvdata *tmp_drvdata; | 
 | 267 | 	int ret = 0; | 
 | 268 | 	unsigned long flags; | 
 | 269 |  | 
 | 270 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
 | 271 |  | 
 | 272 | 	priv = domain->priv; | 
 | 273 |  | 
 | 274 | 	if (!priv || !dev) { | 
 | 275 | 		ret = -EINVAL; | 
 | 276 | 		goto fail; | 
 | 277 | 	} | 
 | 278 |  | 
 | 279 | 	iommu_drvdata = dev_get_drvdata(dev->parent); | 
 | 280 | 	ctx_drvdata = dev_get_drvdata(dev); | 
 | 281 | 	ctx_dev = dev->platform_data; | 
 | 282 |  | 
 | 283 | 	if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) { | 
 | 284 | 		ret = -EINVAL; | 
 | 285 | 		goto fail; | 
 | 286 | 	} | 
 | 287 |  | 
| Stepan Moskovchenko | 00d4b2b | 2010-11-12 19:29:56 -0800 | [diff] [blame] | 288 | 	if (!list_empty(&ctx_drvdata->attached_elm)) { | 
 | 289 | 		ret = -EBUSY; | 
 | 290 | 		goto fail; | 
 | 291 | 	} | 
 | 292 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 293 | 	list_for_each_entry(tmp_drvdata, &priv->list_attached, attached_elm) | 
 | 294 | 		if (tmp_drvdata == ctx_drvdata) { | 
 | 295 | 			ret = -EBUSY; | 
 | 296 | 			goto fail; | 
 | 297 | 		} | 
 | 298 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 299 | 	ret = __enable_clocks(iommu_drvdata); | 
 | 300 | 	if (ret) | 
 | 301 | 		goto fail; | 
 | 302 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 303 | 	__program_context(iommu_drvdata->base, ctx_dev->num, | 
 | 304 | 			  __pa(priv->pgtable)); | 
 | 305 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 306 | 	__disable_clocks(iommu_drvdata); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 307 | 	list_add(&(ctx_drvdata->attached_elm), &priv->list_attached); | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 308 | 	ret = __flush_iotlb(domain); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 309 |  | 
 | 310 | fail: | 
 | 311 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 312 | 	return ret; | 
 | 313 | } | 
 | 314 |  | 
 | 315 | static void msm_iommu_detach_dev(struct iommu_domain *domain, | 
 | 316 | 				 struct device *dev) | 
 | 317 | { | 
 | 318 | 	struct msm_priv *priv; | 
 | 319 | 	struct msm_iommu_ctx_dev *ctx_dev; | 
 | 320 | 	struct msm_iommu_drvdata *iommu_drvdata; | 
 | 321 | 	struct msm_iommu_ctx_drvdata *ctx_drvdata; | 
 | 322 | 	unsigned long flags; | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 323 | 	int ret; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 324 |  | 
 | 325 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
 | 326 | 	priv = domain->priv; | 
 | 327 |  | 
 | 328 | 	if (!priv || !dev) | 
 | 329 | 		goto fail; | 
 | 330 |  | 
 | 331 | 	iommu_drvdata = dev_get_drvdata(dev->parent); | 
 | 332 | 	ctx_drvdata = dev_get_drvdata(dev); | 
 | 333 | 	ctx_dev = dev->platform_data; | 
 | 334 |  | 
 | 335 | 	if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) | 
 | 336 | 		goto fail; | 
 | 337 |  | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 338 | 	ret = __flush_iotlb(domain); | 
 | 339 | 	if (ret) | 
 | 340 | 		goto fail; | 
 | 341 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 342 | 	ret = __enable_clocks(iommu_drvdata); | 
 | 343 | 	if (ret) | 
 | 344 | 		goto fail; | 
 | 345 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 346 | 	__reset_context(iommu_drvdata->base, ctx_dev->num); | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 347 | 	__disable_clocks(iommu_drvdata); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 348 | 	list_del_init(&ctx_drvdata->attached_elm); | 
 | 349 |  | 
 | 350 | fail: | 
 | 351 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 352 | } | 
 | 353 |  | 
 | 354 | static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, | 
 | 355 | 			 phys_addr_t pa, int order, int prot) | 
 | 356 | { | 
 | 357 | 	struct msm_priv *priv; | 
 | 358 | 	unsigned long flags; | 
 | 359 | 	unsigned long *fl_table; | 
 | 360 | 	unsigned long *fl_pte; | 
 | 361 | 	unsigned long fl_offset; | 
 | 362 | 	unsigned long *sl_table; | 
 | 363 | 	unsigned long *sl_pte; | 
 | 364 | 	unsigned long sl_offset; | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 365 | 	unsigned int pgprot; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 366 | 	size_t len = 0x1000UL << order; | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 367 | 	int ret = 0, tex, sh; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 368 |  | 
 | 369 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 370 |  | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 371 | 	sh = (prot & MSM_IOMMU_ATTR_SH) ? 1 : 0; | 
 | 372 | 	tex = msm_iommu_tex_class[prot & MSM_IOMMU_CP_MASK]; | 
 | 373 |  | 
 | 374 | 	if (tex < 0 || tex > NUM_TEX_CLASS - 1) { | 
 | 375 | 		ret = -EINVAL; | 
 | 376 | 		goto fail; | 
 | 377 | 	} | 
 | 378 |  | 
 | 379 | 	priv = domain->priv; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 380 | 	if (!priv) { | 
 | 381 | 		ret = -EINVAL; | 
 | 382 | 		goto fail; | 
 | 383 | 	} | 
 | 384 |  | 
 | 385 | 	fl_table = priv->pgtable; | 
 | 386 |  | 
 | 387 | 	if (len != SZ_16M && len != SZ_1M && | 
 | 388 | 	    len != SZ_64K && len != SZ_4K) { | 
 | 389 | 		pr_debug("Bad size: %d\n", len); | 
 | 390 | 		ret = -EINVAL; | 
 | 391 | 		goto fail; | 
 | 392 | 	} | 
 | 393 |  | 
 | 394 | 	if (!fl_table) { | 
 | 395 | 		pr_debug("Null page table\n"); | 
 | 396 | 		ret = -EINVAL; | 
 | 397 | 		goto fail; | 
 | 398 | 	} | 
 | 399 |  | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 400 | 	if (len == SZ_16M || len == SZ_1M) { | 
 | 401 | 		pgprot = sh ? FL_SHARED : 0; | 
 | 402 | 		pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0; | 
 | 403 | 		pgprot |= tex & 0x02 ? FL_CACHEABLE : 0; | 
 | 404 | 		pgprot |= tex & 0x04 ? FL_TEX0 : 0; | 
 | 405 | 	} else	{ | 
 | 406 | 		pgprot = sh ? SL_SHARED : 0; | 
 | 407 | 		pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0; | 
 | 408 | 		pgprot |= tex & 0x02 ? SL_CACHEABLE : 0; | 
 | 409 | 		pgprot |= tex & 0x04 ? SL_TEX0 : 0; | 
 | 410 | 	} | 
 | 411 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 412 | 	fl_offset = FL_OFFSET(va);	/* Upper 12 bits */ | 
 | 413 | 	fl_pte = fl_table + fl_offset;	/* int pointers, 4 bytes */ | 
 | 414 |  | 
 | 415 | 	if (len == SZ_16M) { | 
 | 416 | 		int i = 0; | 
 | 417 | 		for (i = 0; i < 16; i++) | 
 | 418 | 			*(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION | | 
 | 419 | 				  FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT | | 
| Stepan Moskovchenko | 2e8c8ba | 2011-02-24 18:00:41 -0800 | [diff] [blame] | 420 | 				  FL_SHARED | FL_NG | pgprot; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 421 | 	} | 
 | 422 |  | 
 | 423 | 	if (len == SZ_1M) | 
| Stepan Moskovchenko | 2e8c8ba | 2011-02-24 18:00:41 -0800 | [diff] [blame] | 424 | 		*fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG | | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 425 | 					    FL_TYPE_SECT | FL_SHARED | pgprot; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 426 |  | 
 | 427 | 	/* Need a 2nd level table */ | 
 | 428 | 	if ((len == SZ_4K || len == SZ_64K) && (*fl_pte) == 0) { | 
 | 429 | 		unsigned long *sl; | 
| Stepan Moskovchenko | 294b2de | 2010-12-10 14:12:03 -0800 | [diff] [blame] | 430 | 		sl = (unsigned long *) __get_free_pages(GFP_ATOMIC, | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 431 | 							get_order(SZ_4K)); | 
 | 432 |  | 
 | 433 | 		if (!sl) { | 
 | 434 | 			pr_debug("Could not allocate second level table\n"); | 
 | 435 | 			ret = -ENOMEM; | 
 | 436 | 			goto fail; | 
 | 437 | 		} | 
 | 438 |  | 
 | 439 | 		memset(sl, 0, SZ_4K); | 
 | 440 | 		*fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | FL_TYPE_TABLE); | 
 | 441 | 	} | 
 | 442 |  | 
 | 443 | 	sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); | 
 | 444 | 	sl_offset = SL_OFFSET(va); | 
 | 445 | 	sl_pte = sl_table + sl_offset; | 
 | 446 |  | 
 | 447 |  | 
 | 448 | 	if (len == SZ_4K) | 
| Stepan Moskovchenko | 2e8c8ba | 2011-02-24 18:00:41 -0800 | [diff] [blame] | 449 | 		*sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_AP0 | SL_AP1 | SL_NG | | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 450 | 					  SL_SHARED | SL_TYPE_SMALL | pgprot; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 451 |  | 
 | 452 | 	if (len == SZ_64K) { | 
 | 453 | 		int i; | 
 | 454 |  | 
 | 455 | 		for (i = 0; i < 16; i++) | 
 | 456 | 			*(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_AP0 | | 
| Stepan Moskovchenko | 2e8c8ba | 2011-02-24 18:00:41 -0800 | [diff] [blame] | 457 | 			    SL_NG | SL_AP1 | SL_SHARED | SL_TYPE_LARGE | pgprot; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 458 | 	} | 
 | 459 |  | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 460 | 	ret = __flush_iotlb(domain); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 461 | fail: | 
 | 462 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 463 | 	return ret; | 
 | 464 | } | 
 | 465 |  | 
 | 466 | static int msm_iommu_unmap(struct iommu_domain *domain, unsigned long va, | 
 | 467 | 			    int order) | 
 | 468 | { | 
 | 469 | 	struct msm_priv *priv; | 
 | 470 | 	unsigned long flags; | 
 | 471 | 	unsigned long *fl_table; | 
 | 472 | 	unsigned long *fl_pte; | 
 | 473 | 	unsigned long fl_offset; | 
 | 474 | 	unsigned long *sl_table; | 
 | 475 | 	unsigned long *sl_pte; | 
 | 476 | 	unsigned long sl_offset; | 
 | 477 | 	size_t len = 0x1000UL << order; | 
 | 478 | 	int i, ret = 0; | 
 | 479 |  | 
 | 480 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
 | 481 |  | 
 | 482 | 	priv = domain->priv; | 
 | 483 |  | 
 | 484 | 	if (!priv) { | 
 | 485 | 		ret = -ENODEV; | 
 | 486 | 		goto fail; | 
 | 487 | 	} | 
 | 488 |  | 
 | 489 | 	fl_table = priv->pgtable; | 
 | 490 |  | 
 | 491 | 	if (len != SZ_16M && len != SZ_1M && | 
 | 492 | 	    len != SZ_64K && len != SZ_4K) { | 
 | 493 | 		pr_debug("Bad length: %d\n", len); | 
 | 494 | 		ret = -EINVAL; | 
 | 495 | 		goto fail; | 
 | 496 | 	} | 
 | 497 |  | 
 | 498 | 	if (!fl_table) { | 
 | 499 | 		pr_debug("Null page table\n"); | 
 | 500 | 		ret = -EINVAL; | 
 | 501 | 		goto fail; | 
 | 502 | 	} | 
 | 503 |  | 
 | 504 | 	fl_offset = FL_OFFSET(va);	/* Upper 12 bits */ | 
 | 505 | 	fl_pte = fl_table + fl_offset;	/* int pointers, 4 bytes */ | 
 | 506 |  | 
 | 507 | 	if (*fl_pte == 0) { | 
 | 508 | 		pr_debug("First level PTE is 0\n"); | 
 | 509 | 		ret = -ENODEV; | 
 | 510 | 		goto fail; | 
 | 511 | 	} | 
 | 512 |  | 
 | 513 | 	/* Unmap supersection */ | 
 | 514 | 	if (len == SZ_16M) | 
 | 515 | 		for (i = 0; i < 16; i++) | 
 | 516 | 			*(fl_pte+i) = 0; | 
 | 517 |  | 
 | 518 | 	if (len == SZ_1M) | 
 | 519 | 		*fl_pte = 0; | 
 | 520 |  | 
 | 521 | 	sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); | 
 | 522 | 	sl_offset = SL_OFFSET(va); | 
 | 523 | 	sl_pte = sl_table + sl_offset; | 
 | 524 |  | 
 | 525 | 	if (len == SZ_64K) { | 
 | 526 | 		for (i = 0; i < 16; i++) | 
 | 527 | 			*(sl_pte+i) = 0; | 
 | 528 | 	} | 
 | 529 |  | 
 | 530 | 	if (len == SZ_4K) | 
 | 531 | 		*sl_pte = 0; | 
 | 532 |  | 
 | 533 | 	if (len == SZ_4K || len == SZ_64K) { | 
 | 534 | 		int used = 0; | 
 | 535 |  | 
 | 536 | 		for (i = 0; i < NUM_SL_PTE; i++) | 
 | 537 | 			if (sl_table[i]) | 
 | 538 | 				used = 1; | 
 | 539 | 		if (!used) { | 
 | 540 | 			free_page((unsigned long)sl_table); | 
 | 541 | 			*fl_pte = 0; | 
 | 542 | 		} | 
 | 543 | 	} | 
 | 544 |  | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 545 | 	ret = __flush_iotlb(domain); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 546 | fail: | 
 | 547 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 548 | 	return ret; | 
 | 549 | } | 
 | 550 |  | 
 | 551 | static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, | 
 | 552 | 					  unsigned long va) | 
 | 553 | { | 
 | 554 | 	struct msm_priv *priv; | 
 | 555 | 	struct msm_iommu_drvdata *iommu_drvdata; | 
 | 556 | 	struct msm_iommu_ctx_drvdata *ctx_drvdata; | 
 | 557 | 	unsigned int par; | 
 | 558 | 	unsigned long flags; | 
 | 559 | 	void __iomem *base; | 
 | 560 | 	phys_addr_t ret = 0; | 
 | 561 | 	int ctx; | 
 | 562 |  | 
 | 563 | 	spin_lock_irqsave(&msm_iommu_lock, flags); | 
 | 564 |  | 
 | 565 | 	priv = domain->priv; | 
 | 566 | 	if (list_empty(&priv->list_attached)) | 
 | 567 | 		goto fail; | 
 | 568 |  | 
 | 569 | 	ctx_drvdata = list_entry(priv->list_attached.next, | 
 | 570 | 				 struct msm_iommu_ctx_drvdata, attached_elm); | 
 | 571 | 	iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent); | 
 | 572 |  | 
 | 573 | 	base = iommu_drvdata->base; | 
 | 574 | 	ctx = ctx_drvdata->num; | 
 | 575 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 576 | 	ret = __enable_clocks(iommu_drvdata); | 
 | 577 | 	if (ret) | 
 | 578 | 		goto fail; | 
 | 579 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 580 | 	/* Invalidate context TLB */ | 
 | 581 | 	SET_CTX_TLBIALL(base, ctx, 0); | 
| Stepan Moskovchenko | b0e7808 | 2011-02-28 16:04:55 -0800 | [diff] [blame] | 582 | 	SET_V2PPR(base, ctx, va & V2Pxx_VA); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 583 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 584 | 	par = GET_PAR(base, ctx); | 
 | 585 |  | 
 | 586 | 	/* We are dealing with a supersection */ | 
 | 587 | 	if (GET_NOFAULT_SS(base, ctx)) | 
 | 588 | 		ret = (par & 0xFF000000) | (va & 0x00FFFFFF); | 
 | 589 | 	else	/* Upper 20 bits from PAR, lower 12 from VA */ | 
 | 590 | 		ret = (par & 0xFFFFF000) | (va & 0x00000FFF); | 
 | 591 |  | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 592 | 	if (GET_FAULT(base, ctx)) | 
 | 593 | 		ret = 0; | 
 | 594 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 595 | 	__disable_clocks(iommu_drvdata); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 596 | fail: | 
 | 597 | 	spin_unlock_irqrestore(&msm_iommu_lock, flags); | 
 | 598 | 	return ret; | 
 | 599 | } | 
 | 600 |  | 
 | 601 | static int msm_iommu_domain_has_cap(struct iommu_domain *domain, | 
 | 602 | 				    unsigned long cap) | 
 | 603 | { | 
 | 604 | 	return 0; | 
 | 605 | } | 
 | 606 |  | 
 | 607 | static void print_ctx_regs(void __iomem *base, int ctx) | 
 | 608 | { | 
 | 609 | 	unsigned int fsr = GET_FSR(base, ctx); | 
 | 610 | 	pr_err("FAR    = %08x    PAR    = %08x\n", | 
 | 611 | 	       GET_FAR(base, ctx), GET_PAR(base, ctx)); | 
 | 612 | 	pr_err("FSR    = %08x [%s%s%s%s%s%s%s%s%s%s]\n", fsr, | 
 | 613 | 			(fsr & 0x02) ? "TF " : "", | 
 | 614 | 			(fsr & 0x04) ? "AFF " : "", | 
 | 615 | 			(fsr & 0x08) ? "APF " : "", | 
 | 616 | 			(fsr & 0x10) ? "TLBMF " : "", | 
 | 617 | 			(fsr & 0x20) ? "HTWDEEF " : "", | 
 | 618 | 			(fsr & 0x40) ? "HTWSEEF " : "", | 
 | 619 | 			(fsr & 0x80) ? "MHF " : "", | 
 | 620 | 			(fsr & 0x10000) ? "SL " : "", | 
 | 621 | 			(fsr & 0x40000000) ? "SS " : "", | 
 | 622 | 			(fsr & 0x80000000) ? "MULTI " : ""); | 
 | 623 |  | 
 | 624 | 	pr_err("FSYNR0 = %08x    FSYNR1 = %08x\n", | 
 | 625 | 	       GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx)); | 
 | 626 | 	pr_err("TTBR0  = %08x    TTBR1  = %08x\n", | 
 | 627 | 	       GET_TTBR0(base, ctx), GET_TTBR1(base, ctx)); | 
 | 628 | 	pr_err("SCTLR  = %08x    ACTLR  = %08x\n", | 
 | 629 | 	       GET_SCTLR(base, ctx), GET_ACTLR(base, ctx)); | 
 | 630 | 	pr_err("PRRR   = %08x    NMRR   = %08x\n", | 
 | 631 | 	       GET_PRRR(base, ctx), GET_NMRR(base, ctx)); | 
 | 632 | } | 
 | 633 |  | 
 | 634 | irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id) | 
 | 635 | { | 
 | 636 | 	struct msm_iommu_drvdata *drvdata = dev_id; | 
 | 637 | 	void __iomem *base; | 
| Stepan Moskovchenko | 3306973 | 2010-11-12 19:30:00 -0800 | [diff] [blame] | 638 | 	unsigned int fsr; | 
| Stepan Moskovchenko | a43d8c1 | 2011-02-24 18:00:42 -0800 | [diff] [blame] | 639 | 	int i, ret; | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 640 |  | 
 | 641 | 	spin_lock(&msm_iommu_lock); | 
 | 642 |  | 
 | 643 | 	if (!drvdata) { | 
 | 644 | 		pr_err("Invalid device ID in context interrupt handler\n"); | 
 | 645 | 		goto fail; | 
 | 646 | 	} | 
 | 647 |  | 
 | 648 | 	base = drvdata->base; | 
 | 649 |  | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 650 | 	pr_err("Unexpected IOMMU page fault!\n"); | 
 | 651 | 	pr_err("base = %08x\n", (unsigned int) base); | 
 | 652 |  | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 653 | 	ret = __enable_clocks(drvdata); | 
 | 654 | 	if (ret) | 
 | 655 | 		goto fail; | 
 | 656 |  | 
| Stepan Moskovchenko | a43d8c1 | 2011-02-24 18:00:42 -0800 | [diff] [blame] | 657 | 	for (i = 0; i < drvdata->ncb; i++) { | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 658 | 		fsr = GET_FSR(base, i); | 
 | 659 | 		if (fsr) { | 
 | 660 | 			pr_err("Fault occurred in context %d.\n", i); | 
 | 661 | 			pr_err("Interesting registers:\n"); | 
 | 662 | 			print_ctx_regs(base, i); | 
 | 663 | 			SET_FSR(base, i, 0x4000000F); | 
 | 664 | 		} | 
 | 665 | 	} | 
| Stepan Moskovchenko | 41f3f51 | 2011-02-24 18:00:39 -0800 | [diff] [blame] | 666 | 	__disable_clocks(drvdata); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 667 | fail: | 
 | 668 | 	spin_unlock(&msm_iommu_lock); | 
 | 669 | 	return 0; | 
 | 670 | } | 
 | 671 |  | 
 | 672 | static struct iommu_ops msm_iommu_ops = { | 
 | 673 | 	.domain_init = msm_iommu_domain_init, | 
 | 674 | 	.domain_destroy = msm_iommu_domain_destroy, | 
 | 675 | 	.attach_dev = msm_iommu_attach_dev, | 
 | 676 | 	.detach_dev = msm_iommu_detach_dev, | 
 | 677 | 	.map = msm_iommu_map, | 
 | 678 | 	.unmap = msm_iommu_unmap, | 
 | 679 | 	.iova_to_phys = msm_iommu_iova_to_phys, | 
 | 680 | 	.domain_has_cap = msm_iommu_domain_has_cap | 
 | 681 | }; | 
 | 682 |  | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 683 | static int __init get_tex_class(int icp, int ocp, int mt, int nos) | 
 | 684 | { | 
 | 685 | 	int i = 0; | 
 | 686 | 	unsigned int prrr = 0; | 
 | 687 | 	unsigned int nmrr = 0; | 
 | 688 | 	int c_icp, c_ocp, c_mt, c_nos; | 
 | 689 |  | 
 | 690 | 	RCP15_PRRR(prrr); | 
 | 691 | 	RCP15_NMRR(nmrr); | 
 | 692 |  | 
 | 693 | 	for (i = 0; i < NUM_TEX_CLASS; i++) { | 
 | 694 | 		c_nos = PRRR_NOS(prrr, i); | 
 | 695 | 		c_mt = PRRR_MT(prrr, i); | 
 | 696 | 		c_icp = NMRR_ICP(nmrr, i); | 
 | 697 | 		c_ocp = NMRR_OCP(nmrr, i); | 
 | 698 |  | 
 | 699 | 		if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos) | 
 | 700 | 			return i; | 
 | 701 | 	} | 
 | 702 |  | 
 | 703 | 	return -ENODEV; | 
 | 704 | } | 
 | 705 |  | 
 | 706 | static void __init setup_iommu_tex_classes(void) | 
 | 707 | { | 
 | 708 | 	msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED] = | 
 | 709 | 			get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1); | 
 | 710 |  | 
 | 711 | 	msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_WA] = | 
 | 712 | 			get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1); | 
 | 713 |  | 
 | 714 | 	msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_NWA] = | 
 | 715 | 			get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1); | 
 | 716 |  | 
 | 717 | 	msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WT] = | 
 | 718 | 			get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1); | 
 | 719 | } | 
 | 720 |  | 
| Stepan Moskovchenko | 516cbc7 | 2010-11-12 19:29:53 -0800 | [diff] [blame] | 721 | static int __init msm_iommu_init(void) | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 722 | { | 
| Stepan Moskovchenko | 100832c | 2010-11-15 18:20:08 -0800 | [diff] [blame] | 723 | 	setup_iommu_tex_classes(); | 
| Stepan Moskovchenko | 0720d1f | 2010-08-24 18:31:10 -0700 | [diff] [blame] | 724 | 	register_iommu(&msm_iommu_ops); | 
 | 725 | 	return 0; | 
 | 726 | } | 
 | 727 |  | 
 | 728 | subsys_initcall(msm_iommu_init); | 
 | 729 |  | 
 | 730 | MODULE_LICENSE("GPL v2"); | 
 | 731 | MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>"); |