Loading...
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 | // SPDX-License-Identifier: GPL-2.0 /* * arch/sh/boards/mach-x3proto/ilsel.c * * Helper routines for SH-X3 proto board ILSEL. * * Copyright (C) 2007 - 2010 Paul Mundt */ #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); |