sysmmu.c 7.3 KB
Newer Older
D
Donguk Ryu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* linux/arch/arm/plat-s5p/sysmmu.c
 *
 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 *
 * 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.
 */

#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>

15 16
#include <asm/pgtable.h>

D
Donguk Ryu 已提交
17 18
#include <mach/map.h>
#include <mach/regs-sysmmu.h>
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#include <plat/sysmmu.h>

#define CTRL_ENABLE	0x5
#define CTRL_BLOCK	0x7
#define CTRL_DISABLE	0x0

static struct device *dev;

static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
	S5P_PAGE_FAULT_ADDR,
	S5P_AR_FAULT_ADDR,
	S5P_AW_FAULT_ADDR,
	S5P_DEFAULT_SLAVE_ADDR,
	S5P_AR_FAULT_ADDR,
	S5P_AR_FAULT_ADDR,
	S5P_AW_FAULT_ADDR,
	S5P_AW_FAULT_ADDR
};
D
Donguk Ryu 已提交
37

38 39 40 41 42 43 44 45 46 47
static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
	"PAGE FAULT",
	"AR MULTI-HIT FAULT",
	"AW MULTI-HIT FAULT",
	"BUS ERROR",
	"AR SECURITY PROTECTION FAULT",
	"AR ACCESS PROTECTION FAULT",
	"AW SECURITY PROTECTION FAULT",
	"AW ACCESS PROTECTION FAULT"
};
D
Donguk Ryu 已提交
48

49 50 51 52 53 54 55 56 57 58 59 60
static int (*fault_handlers[S5P_SYSMMU_TOTAL_IPNUM])(
		enum S5P_SYSMMU_INTERRUPT_TYPE itype,
		unsigned long pgtable_base,
		unsigned long fault_addr);

/*
 * If adjacent 2 bits are true, the system MMU is enabled.
 * The system MMU is disabled, otherwise.
 */
static unsigned long sysmmu_states;

static inline void set_sysmmu_active(sysmmu_ips ips)
D
Donguk Ryu 已提交
61
{
62
	sysmmu_states |= 3 << (ips * 2);
D
Donguk Ryu 已提交
63 64
}

65
static inline void set_sysmmu_inactive(sysmmu_ips ips)
D
Donguk Ryu 已提交
66
{
67
	sysmmu_states &= ~(3 << (ips * 2));
D
Donguk Ryu 已提交
68 69
}

70
static inline int is_sysmmu_active(sysmmu_ips ips)
D
Donguk Ryu 已提交
71
{
72 73
	return sysmmu_states & (3 << (ips * 2));
}
D
Donguk Ryu 已提交
74

75
static void __iomem *sysmmusfrs[S5P_SYSMMU_TOTAL_IPNUM];
D
Donguk Ryu 已提交
76

77 78 79 80
static inline void sysmmu_block(sysmmu_ips ips)
{
	__raw_writel(CTRL_BLOCK, sysmmusfrs[ips] + S5P_MMU_CTRL);
	dev_dbg(dev, "%s is blocked.\n", sysmmu_ips_name[ips]);
D
Donguk Ryu 已提交
81 82
}

83
static inline void sysmmu_unblock(sysmmu_ips ips)
D
Donguk Ryu 已提交
84
{
85 86 87
	__raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
	dev_dbg(dev, "%s is unblocked.\n", sysmmu_ips_name[ips]);
}
D
Donguk Ryu 已提交
88

89 90 91 92 93
static inline void __sysmmu_tlb_invalidate(sysmmu_ips ips)
{
	__raw_writel(0x1, sysmmusfrs[ips] + S5P_MMU_FLUSH);
	dev_dbg(dev, "TLB of %s is invalidated.\n", sysmmu_ips_name[ips]);
}
D
Donguk Ryu 已提交
94

95 96 97 98 99 100 101
static inline void __sysmmu_set_ptbase(sysmmu_ips ips, unsigned long pgd)
{
	if (unlikely(pgd == 0)) {
		pgd = (unsigned long)ZERO_PAGE(0);
		__raw_writel(0x20, sysmmusfrs[ips] + S5P_MMU_CFG); /* 4KB LV1 */
	} else {
		__raw_writel(0x0, sysmmusfrs[ips] + S5P_MMU_CFG); /* 16KB LV1 */
D
Donguk Ryu 已提交
102 103
	}

104
	__raw_writel(pgd, sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
D
Donguk Ryu 已提交
105

106 107 108 109
	dev_dbg(dev, "Page table base of %s is initialized with 0x%08lX.\n",
						sysmmu_ips_name[ips], pgd);
	__sysmmu_tlb_invalidate(ips);
}
D
Donguk Ryu 已提交
110

111 112 113 114 115 116 117
void sysmmu_set_fault_handler(sysmmu_ips ips,
			int (*handler)(enum S5P_SYSMMU_INTERRUPT_TYPE itype,
					unsigned long pgtable_base,
					unsigned long fault_addr))
{
	BUG_ON(!((ips >= SYSMMU_MDMA) && (ips < S5P_SYSMMU_TOTAL_IPNUM)));
	fault_handlers[ips] = handler;
D
Donguk Ryu 已提交
118 119
}

120
static irqreturn_t s5p_sysmmu_irq(int irq, void *dev_id)
D
Donguk Ryu 已提交
121
{
122 123 124 125
	/* SYSMMU is in blocked when interrupt occurred. */
	unsigned long base = 0;
	sysmmu_ips ips = (sysmmu_ips)dev_id;
	enum S5P_SYSMMU_INTERRUPT_TYPE itype;
D
Donguk Ryu 已提交
126

127 128
	itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
		__ffs(__raw_readl(sysmmusfrs[ips] + S5P_INT_STATUS));
D
Donguk Ryu 已提交
129

130
	BUG_ON(!((itype >= 0) && (itype < 8)));
D
Donguk Ryu 已提交
131

132 133
	dev_alert(dev, "%s occurred by %s.\n", sysmmu_fault_name[itype],
							sysmmu_ips_name[ips]);
D
Donguk Ryu 已提交
134

135 136
	if (fault_handlers[ips]) {
		unsigned long addr;
D
Donguk Ryu 已提交
137

138 139
		base = __raw_readl(sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
		addr = __raw_readl(sysmmusfrs[ips] + fault_reg_offset[itype]);
D
Donguk Ryu 已提交
140

141 142 143 144 145 146 147 148 149 150
		if (fault_handlers[ips](itype, base, addr)) {
			__raw_writel(1 << itype,
					sysmmusfrs[ips] + S5P_INT_CLEAR);
			dev_notice(dev, "%s from %s is resolved."
					" Retrying translation.\n",
				sysmmu_fault_name[itype], sysmmu_ips_name[ips]);
		} else {
			base = 0;
		}
	}
D
Donguk Ryu 已提交
151

152
	sysmmu_unblock(ips);
D
Donguk Ryu 已提交
153

154 155 156
	if (!base)
		dev_notice(dev, "%s from %s is not handled.\n",
			sysmmu_fault_name[itype], sysmmu_ips_name[ips]);
D
Donguk Ryu 已提交
157

158
	return IRQ_HANDLED;
D
Donguk Ryu 已提交
159 160
}

161
void s5p_sysmmu_set_tablebase_pgd(sysmmu_ips ips, unsigned long pgd)
D
Donguk Ryu 已提交
162
{
163 164 165 166 167 168 169 170
	if (is_sysmmu_active(ips)) {
		sysmmu_block(ips);
		__sysmmu_set_ptbase(ips, pgd);
		sysmmu_unblock(ips);
	} else {
		dev_dbg(dev, "%s is disabled. "
			"Skipping initializing page table base.\n",
						sysmmu_ips_name[ips]);
D
Donguk Ryu 已提交
171 172 173
	}
}

174
void s5p_sysmmu_enable(sysmmu_ips ips, unsigned long pgd)
D
Donguk Ryu 已提交
175
{
176
	if (!is_sysmmu_active(ips)) {
177 178
		sysmmu_clk_enable(ips);

179
		__sysmmu_set_ptbase(ips, pgd);
D
Donguk Ryu 已提交
180

181
		__raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
D
Donguk Ryu 已提交
182

183 184 185 186
		set_sysmmu_active(ips);
		dev_dbg(dev, "%s is enabled.\n", sysmmu_ips_name[ips]);
	} else {
		dev_dbg(dev, "%s is already enabled.\n", sysmmu_ips_name[ips]);
D
Donguk Ryu 已提交
187
	}
188
}
D
Donguk Ryu 已提交
189

190 191 192 193 194
void s5p_sysmmu_disable(sysmmu_ips ips)
{
	if (is_sysmmu_active(ips)) {
		__raw_writel(CTRL_DISABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
		set_sysmmu_inactive(ips);
195
		sysmmu_clk_disable(ips);
196 197 198 199 200
		dev_dbg(dev, "%s is disabled.\n", sysmmu_ips_name[ips]);
	} else {
		dev_dbg(dev, "%s is already disabled.\n", sysmmu_ips_name[ips]);
	}
}
D
Donguk Ryu 已提交
201

202 203 204 205 206 207 208 209 210 211
void s5p_sysmmu_tlb_invalidate(sysmmu_ips ips)
{
	if (is_sysmmu_active(ips)) {
		sysmmu_block(ips);
		__sysmmu_tlb_invalidate(ips);
		sysmmu_unblock(ips);
	} else {
		dev_dbg(dev, "%s is disabled. "
			"Skipping invalidating TLB.\n", sysmmu_ips_name[ips]);
	}
D
Donguk Ryu 已提交
212 213 214 215
}

static int s5p_sysmmu_probe(struct platform_device *pdev)
{
216 217
	int i, ret;
	struct resource *res, *mem;
D
Donguk Ryu 已提交
218

219
	dev = &pdev->dev;
D
Donguk Ryu 已提交
220

221 222
	for (i = 0; i < S5P_SYSMMU_TOTAL_IPNUM; i++) {
		int irq;
D
Donguk Ryu 已提交
223

224 225 226
		sysmmu_clk_init(dev, i);
		sysmmu_clk_disable(i);

D
Donguk Ryu 已提交
227 228
		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
		if (!res) {
229 230
			dev_err(dev, "Failed to get the resource of %s.\n",
							sysmmu_ips_name[i]);
D
Donguk Ryu 已提交
231 232 233 234
			ret = -ENODEV;
			goto err_res;
		}

235
		mem = request_mem_region(res->start,
D
Donguk Ryu 已提交
236
				((res->end) - (res->start)) + 1, pdev->name);
237 238 239
		if (!mem) {
			dev_err(dev, "Failed to request the memory region of %s.\n",
							sysmmu_ips_name[i]);
D
Donguk Ryu 已提交
240 241 242 243
			ret = -EBUSY;
			goto err_res;
		}

244 245 246 247
		sysmmusfrs[i] = ioremap(res->start, res->end - res->start + 1);
		if (!sysmmusfrs[i]) {
			dev_err(dev, "Failed to ioremap() for %s.\n",
							sysmmu_ips_name[i]);
D
Donguk Ryu 已提交
248 249 250 251
			ret = -ENXIO;
			goto err_reg;
		}

252 253 254 255
		irq = platform_get_irq(pdev, i);
		if (irq <= 0) {
			dev_err(dev, "Failed to get the IRQ resource of %s.\n",
							sysmmu_ips_name[i]);
D
Donguk Ryu 已提交
256 257 258 259
			ret = -ENOENT;
			goto err_map;
		}

260 261 262 263
		if (request_irq(irq, s5p_sysmmu_irq, IRQF_DISABLED,
						pdev->name, (void *)i)) {
			dev_err(dev, "Failed to request IRQ for %s.\n",
							sysmmu_ips_name[i]);
D
Donguk Ryu 已提交
264 265 266 267 268 269 270 271
			ret = -ENOENT;
			goto err_map;
		}
	}

	return 0;

err_map:
272 273 274
	iounmap(sysmmusfrs[i]);
err_reg:
	release_mem_region(mem->start, resource_size(mem));
D
Donguk Ryu 已提交
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
err_res:
	return ret;
}

static int s5p_sysmmu_remove(struct platform_device *pdev)
{
	return 0;
}
int s5p_sysmmu_runtime_suspend(struct device *dev)
{
	return 0;
}

int s5p_sysmmu_runtime_resume(struct device *dev)
{
	return 0;
}

const struct dev_pm_ops s5p_sysmmu_pm_ops = {
	.runtime_suspend	= s5p_sysmmu_runtime_suspend,
	.runtime_resume		= s5p_sysmmu_runtime_resume,
};

static struct platform_driver s5p_sysmmu_driver = {
	.probe		= s5p_sysmmu_probe,
	.remove		= s5p_sysmmu_remove,
	.driver		= {
		.owner		= THIS_MODULE,
		.name		= "s5p-sysmmu",
		.pm		= &s5p_sysmmu_pm_ops,
	}
};

static int __init s5p_sysmmu_init(void)
{
	return platform_driver_register(&s5p_sysmmu_driver);
}
arch_initcall(s5p_sysmmu_init);