1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * arch/sh/boards/mach-x3proto/ilsel.c 4 * 5 * Helper routines for SH-X3 proto board ILSEL. 6 * 7 * Copyright (C) 2007 - 2010 Paul Mundt 8 */ 9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 11 #include <linux/init.h> 12 #include <linux/kernel.h> 13 #include <linux/module.h> 14 #include <linux/bitmap.h> 15 #include <linux/io.h> 16 #include <mach/ilsel.h> 17 18 /* 19 * ILSEL is split across: 20 * 21 * ILSEL0 - 0xb8100004 [ Levels 1 - 4 ] 22 * ILSEL1 - 0xb8100006 [ Levels 5 - 8 ] 23 * ILSEL2 - 0xb8100008 [ Levels 9 - 12 ] 24 * ILSEL3 - 0xb810000a [ Levels 13 - 15 ] 25 * 26 * With each level being relative to an ilsel_source_t. 27 */ 28 #define ILSEL_BASE 0xb8100004 29 #define ILSEL_LEVELS 15 30 31 /* 32 * ILSEL level map, in descending order from the highest level down. 33 * 34 * Supported levels are 1 - 15 spread across ILSEL0 - ILSEL4, mapping 35 * directly to IRLs. As the IRQs are numbered in reverse order relative 36 * to the interrupt level, the level map is carefully managed to ensure a 37 * 1:1 mapping between the bit position and the IRQ number. 38 * 39 * This careful constructions allows ilsel_enable*() to be referenced 40 * directly for hooking up an ILSEL set and getting back an IRQ which can 41 * subsequently be used for internal accounting in the (optional) disable 42 * path. 43 */ 44 static unsigned long ilsel_level_map; 45 46 static inline unsigned int ilsel_offset(unsigned int bit) 47 { 48 return ILSEL_LEVELS - bit - 1; 49 } 50 51 static inline unsigned long mk_ilsel_addr(unsigned int bit) 52 { 53 return ILSEL_BASE + ((ilsel_offset(bit) >> 1) & ~0x1); 54 } 55 56 static inline unsigned int mk_ilsel_shift(unsigned int bit) 57 { 58 return (ilsel_offset(bit) & 0x3) << 2; 59 } 60 61 static void __ilsel_enable(ilsel_source_t set, unsigned int bit) 62 { 63 unsigned int tmp, shift; 64 unsigned long addr; 65 66 pr_notice("enabling ILSEL set %d\n", set); 67 68 addr = mk_ilsel_addr(bit); 69 shift = mk_ilsel_shift(bit); 70 71 pr_debug("%s: bit#%d: addr - 0x%08lx (shift %d, set %d)\n", 72 __func__, bit, addr, shift, set); 73 74 tmp = __raw_readw(addr); 75 tmp &= ~(0xf << shift); 76 tmp |= set << shift; 77 __raw_writew(tmp, addr); 78 } 79 80 /** 81 * ilsel_enable - Enable an ILSEL set. 82 * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 83 * 84 * Enables a given non-aliased ILSEL source (<= ILSEL_KEY) at the highest 85 * available interrupt level. Callers should take care to order callsites 86 * noting descending interrupt levels. Aliasing FPGA and external board 87 * IRQs need to use ilsel_enable_fixed(). 88 * 89 * The return value is an IRQ number that can later be taken down with 90 * ilsel_disable(). 91 */ 92 int ilsel_enable(ilsel_source_t set) 93 { 94 unsigned int bit; 95 96 if (unlikely(set > ILSEL_KEY)) { 97 pr_err("Aliased sources must use ilsel_enable_fixed()\n"); 98 return -EINVAL; 99 } 100 101 do { 102 bit = find_first_zero_bit(&ilsel_level_map, ILSEL_LEVELS); 103 } while (test_and_set_bit(bit, &ilsel_level_map)); 104 105 __ilsel_enable(set, bit); 106 107 return bit; 108 } 109 EXPORT_SYMBOL_GPL(ilsel_enable); 110 111 /** 112 * ilsel_enable_fixed - Enable an ILSEL set at a fixed interrupt level 113 * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 114 * @level: Interrupt level (1 - 15) 115 * 116 * Enables a given ILSEL source at a fixed interrupt level. Necessary 117 * both for level reservation as well as for aliased sources that only 118 * exist on special ILSEL#s. 119 * 120 * Returns an IRQ number (as ilsel_enable()). 121 */ 122 int ilsel_enable_fixed(ilsel_source_t set, unsigned int level) 123 { 124 unsigned int bit = ilsel_offset(level - 1); 125 126 if (test_and_set_bit(bit, &ilsel_level_map)) 127 return -EBUSY; 128 129 __ilsel_enable(set, bit); 130 131 return bit; 132 } 133 EXPORT_SYMBOL_GPL(ilsel_enable_fixed); 134 135 /** 136 * ilsel_disable - Disable an ILSEL set 137 * @irq: Bit position for ILSEL set value (retval from enable routines) 138 * 139 * Disable a previously enabled ILSEL set. 140 */ 141 void ilsel_disable(unsigned int irq) 142 { 143 unsigned long addr; 144 unsigned int tmp; 145 146 pr_notice("disabling ILSEL set %d\n", irq); 147 148 addr = mk_ilsel_addr(irq); 149 150 tmp = __raw_readw(addr); 151 tmp &= ~(0xf << mk_ilsel_shift(irq)); 152 __raw_writew(tmp, addr); 153 154 clear_bit(irq, &ilsel_level_map); 155 } 156 EXPORT_SYMBOL_GPL(ilsel_disable); 157