summaryrefslogtreecommitdiffstats
path: root/arch/sh/boards/mach-x3proto/ilsel.c
blob: 95e34613951550d6a66c5d47164505bc093c3420 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * arch/sh/boards/mach-x3proto/ilsel.c
 *
 * Helper routines for SH-X3 proto board ILSEL.
 *
 * Copyright (C) 2007 - 2010  Paul Mundt
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/bitmap.h>
#include <linux/io.h>
#include <mach/ilsel.h>

/*
 * ILSEL is split across:
 *
 *	ILSEL0 - 0xb8100004 [ Levels  1 -  4 ]
 *	ILSEL1 - 0xb8100006 [ Levels  5 -  8 ]
 *	ILSEL2 - 0xb8100008 [ Levels  9 - 12 ]
 *	ILSEL3 - 0xb810000a [ Levels 13 - 15 ]
 *
 * With each level being relative to an ilsel_source_t.
 */
#define ILSEL_BASE	0xb8100004
#define ILSEL_LEVELS	15

/*
 * ILSEL level map, in descending order from the highest level down.
 *
 * Supported levels are 1 - 15 spread across ILSEL0 - ILSEL4, mapping
 * directly to IRLs. As the IRQs are numbered in reverse order relative
 * to the interrupt level, the level map is carefully managed to ensure a
 * 1:1 mapping between the bit position and the IRQ number.
 *
 * This careful constructions allows ilsel_enable*() to be referenced
 * directly for hooking up an ILSEL set and getting back an IRQ which can
 * subsequently be used for internal accounting in the (optional) disable
 * path.
 */
static unsigned long ilsel_level_map;

static inline unsigned int ilsel_offset(unsigned int bit)
{
	return ILSEL_LEVELS - bit - 1;
}

static inline unsigned long mk_ilsel_addr(unsigned int bit)
{
	return ILSEL_BASE + ((ilsel_offset(bit) >> 1) & ~0x1);
}

static inline unsigned int mk_ilsel_shift(unsigned int bit)
{
	return (ilsel_offset(bit) & 0x3) << 2;
}

static void __ilsel_enable(ilsel_source_t set, unsigned int bit)
{
	unsigned int tmp, shift;
	unsigned long addr;

	pr_notice("enabling ILSEL set %d\n", set);

	addr = mk_ilsel_addr(bit);
	shift = mk_ilsel_shift(bit);

	pr_debug("%s: bit#%d: addr - 0x%08lx (shift %d, set %d)\n",
		 __func__, bit, addr, shift, set);

	tmp = __raw_readw(addr);
	tmp &= ~(0xf << shift);
	tmp |= set << shift;
	__raw_writew(tmp, addr);
}

/**
 * ilsel_enable - Enable an ILSEL set.
 * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h).
 *
 * Enables a given non-aliased ILSEL source (<= ILSEL_KEY) at the highest
 * available interrupt level. Callers should take care to order callsites
 * noting descending interrupt levels. Aliasing FPGA and external board
 * IRQs need to use ilsel_enable_fixed().
 *
 * The return value is an IRQ number that can later be taken down with
 * ilsel_disable().
 */
int ilsel_enable(ilsel_source_t set)
{
	unsigned int bit;

	if (unlikely(set > ILSEL_KEY)) {
		pr_err("Aliased sources must use ilsel_enable_fixed()\n");
		return -EINVAL;
	}

	do {
		bit = find_first_zero_bit(&ilsel_level_map, ILSEL_LEVELS);
	} while (test_and_set_bit(bit, &ilsel_level_map));

	__ilsel_enable(set, bit);

	return bit;
}
EXPORT_SYMBOL_GPL(ilsel_enable);

/**
 * ilsel_enable_fixed - Enable an ILSEL set at a fixed interrupt level
 * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h).
 * @level: Interrupt level (1 - 15)
 *
 * Enables a given ILSEL source at a fixed interrupt level. Necessary
 * both for level reservation as well as for aliased sources that only
 * exist on special ILSEL#s.
 *
 * Returns an IRQ number (as ilsel_enable()).
 */
int ilsel_enable_fixed(ilsel_source_t set, unsigned int level)
{
	unsigned int bit = ilsel_offset(level - 1);

	if (test_and_set_bit(bit, &ilsel_level_map))
		return -EBUSY;

	__ilsel_enable(set, bit);

	return bit;
}
EXPORT_SYMBOL_GPL(ilsel_enable_fixed);

/**
 * ilsel_disable - Disable an ILSEL set
 * @irq: Bit position for ILSEL set value (retval from enable routines)
 *
 * Disable a previously enabled ILSEL set.
 */
void ilsel_disable(unsigned int irq)
{
	unsigned long addr;
	unsigned int tmp;

	pr_notice("disabling ILSEL set %d\n", irq);

	addr = mk_ilsel_addr(irq);

	tmp = __raw_readw(addr);
	tmp &= ~(0xf << mk_ilsel_shift(irq));
	__raw_writew(tmp, addr);

	clear_bit(irq, &ilsel_level_map);
}
EXPORT_SYMBOL_GPL(ilsel_disable);