162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * arch/sh/boards/mach-x3proto/ilsel.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Helper routines for SH-X3 proto board ILSEL. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2007 - 2010 Paul Mundt 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/bitmap.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci#include <mach/ilsel.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* 1962306a36Sopenharmony_ci * ILSEL is split across: 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * ILSEL0 - 0xb8100004 [ Levels 1 - 4 ] 2262306a36Sopenharmony_ci * ILSEL1 - 0xb8100006 [ Levels 5 - 8 ] 2362306a36Sopenharmony_ci * ILSEL2 - 0xb8100008 [ Levels 9 - 12 ] 2462306a36Sopenharmony_ci * ILSEL3 - 0xb810000a [ Levels 13 - 15 ] 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * With each level being relative to an ilsel_source_t. 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_ci#define ILSEL_BASE 0xb8100004 2962306a36Sopenharmony_ci#define ILSEL_LEVELS 15 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* 3262306a36Sopenharmony_ci * ILSEL level map, in descending order from the highest level down. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Supported levels are 1 - 15 spread across ILSEL0 - ILSEL4, mapping 3562306a36Sopenharmony_ci * directly to IRLs. As the IRQs are numbered in reverse order relative 3662306a36Sopenharmony_ci * to the interrupt level, the level map is carefully managed to ensure a 3762306a36Sopenharmony_ci * 1:1 mapping between the bit position and the IRQ number. 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci * This careful constructions allows ilsel_enable*() to be referenced 4062306a36Sopenharmony_ci * directly for hooking up an ILSEL set and getting back an IRQ which can 4162306a36Sopenharmony_ci * subsequently be used for internal accounting in the (optional) disable 4262306a36Sopenharmony_ci * path. 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_cistatic unsigned long ilsel_level_map; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic inline unsigned int ilsel_offset(unsigned int bit) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci return ILSEL_LEVELS - bit - 1; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic inline unsigned long mk_ilsel_addr(unsigned int bit) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci return ILSEL_BASE + ((ilsel_offset(bit) >> 1) & ~0x1); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic inline unsigned int mk_ilsel_shift(unsigned int bit) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return (ilsel_offset(bit) & 0x3) << 2; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void __ilsel_enable(ilsel_source_t set, unsigned int bit) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci unsigned int tmp, shift; 6462306a36Sopenharmony_ci unsigned long addr; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci pr_notice("enabling ILSEL set %d\n", set); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci addr = mk_ilsel_addr(bit); 6962306a36Sopenharmony_ci shift = mk_ilsel_shift(bit); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci pr_debug("%s: bit#%d: addr - 0x%08lx (shift %d, set %d)\n", 7262306a36Sopenharmony_ci __func__, bit, addr, shift, set); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci tmp = __raw_readw(addr); 7562306a36Sopenharmony_ci tmp &= ~(0xf << shift); 7662306a36Sopenharmony_ci tmp |= set << shift; 7762306a36Sopenharmony_ci __raw_writew(tmp, addr); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/** 8162306a36Sopenharmony_ci * ilsel_enable - Enable an ILSEL set. 8262306a36Sopenharmony_ci * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 8362306a36Sopenharmony_ci * 8462306a36Sopenharmony_ci * Enables a given non-aliased ILSEL source (<= ILSEL_KEY) at the highest 8562306a36Sopenharmony_ci * available interrupt level. Callers should take care to order callsites 8662306a36Sopenharmony_ci * noting descending interrupt levels. Aliasing FPGA and external board 8762306a36Sopenharmony_ci * IRQs need to use ilsel_enable_fixed(). 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * The return value is an IRQ number that can later be taken down with 9062306a36Sopenharmony_ci * ilsel_disable(). 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ciint ilsel_enable(ilsel_source_t set) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci unsigned int bit; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (unlikely(set > ILSEL_KEY)) { 9762306a36Sopenharmony_ci pr_err("Aliased sources must use ilsel_enable_fixed()\n"); 9862306a36Sopenharmony_ci return -EINVAL; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci do { 10262306a36Sopenharmony_ci bit = find_first_zero_bit(&ilsel_level_map, ILSEL_LEVELS); 10362306a36Sopenharmony_ci } while (test_and_set_bit(bit, &ilsel_level_map)); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci __ilsel_enable(set, bit); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci return bit; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_enable); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/** 11262306a36Sopenharmony_ci * ilsel_enable_fixed - Enable an ILSEL set at a fixed interrupt level 11362306a36Sopenharmony_ci * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 11462306a36Sopenharmony_ci * @level: Interrupt level (1 - 15) 11562306a36Sopenharmony_ci * 11662306a36Sopenharmony_ci * Enables a given ILSEL source at a fixed interrupt level. Necessary 11762306a36Sopenharmony_ci * both for level reservation as well as for aliased sources that only 11862306a36Sopenharmony_ci * exist on special ILSEL#s. 11962306a36Sopenharmony_ci * 12062306a36Sopenharmony_ci * Returns an IRQ number (as ilsel_enable()). 12162306a36Sopenharmony_ci */ 12262306a36Sopenharmony_ciint ilsel_enable_fixed(ilsel_source_t set, unsigned int level) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci unsigned int bit = ilsel_offset(level - 1); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (test_and_set_bit(bit, &ilsel_level_map)) 12762306a36Sopenharmony_ci return -EBUSY; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci __ilsel_enable(set, bit); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci return bit; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_enable_fixed); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/** 13662306a36Sopenharmony_ci * ilsel_disable - Disable an ILSEL set 13762306a36Sopenharmony_ci * @irq: Bit position for ILSEL set value (retval from enable routines) 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * Disable a previously enabled ILSEL set. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_civoid ilsel_disable(unsigned int irq) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci unsigned long addr; 14462306a36Sopenharmony_ci unsigned int tmp; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci pr_notice("disabling ILSEL set %d\n", irq); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci addr = mk_ilsel_addr(irq); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci tmp = __raw_readw(addr); 15162306a36Sopenharmony_ci tmp &= ~(0xf << mk_ilsel_shift(irq)); 15262306a36Sopenharmony_ci __raw_writew(tmp, addr); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci clear_bit(irq, &ilsel_level_map); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_disable); 157