18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * arch/sh/boards/mach-x3proto/ilsel.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Helper routines for SH-X3 proto board ILSEL. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Copyright (C) 2007 - 2010 Paul Mundt 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/bitmap.h> 158c2ecf20Sopenharmony_ci#include <linux/io.h> 168c2ecf20Sopenharmony_ci#include <mach/ilsel.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* 198c2ecf20Sopenharmony_ci * ILSEL is split across: 208c2ecf20Sopenharmony_ci * 218c2ecf20Sopenharmony_ci * ILSEL0 - 0xb8100004 [ Levels 1 - 4 ] 228c2ecf20Sopenharmony_ci * ILSEL1 - 0xb8100006 [ Levels 5 - 8 ] 238c2ecf20Sopenharmony_ci * ILSEL2 - 0xb8100008 [ Levels 9 - 12 ] 248c2ecf20Sopenharmony_ci * ILSEL3 - 0xb810000a [ Levels 13 - 15 ] 258c2ecf20Sopenharmony_ci * 268c2ecf20Sopenharmony_ci * With each level being relative to an ilsel_source_t. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci#define ILSEL_BASE 0xb8100004 298c2ecf20Sopenharmony_ci#define ILSEL_LEVELS 15 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci/* 328c2ecf20Sopenharmony_ci * ILSEL level map, in descending order from the highest level down. 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * Supported levels are 1 - 15 spread across ILSEL0 - ILSEL4, mapping 358c2ecf20Sopenharmony_ci * directly to IRLs. As the IRQs are numbered in reverse order relative 368c2ecf20Sopenharmony_ci * to the interrupt level, the level map is carefully managed to ensure a 378c2ecf20Sopenharmony_ci * 1:1 mapping between the bit position and the IRQ number. 388c2ecf20Sopenharmony_ci * 398c2ecf20Sopenharmony_ci * This careful constructions allows ilsel_enable*() to be referenced 408c2ecf20Sopenharmony_ci * directly for hooking up an ILSEL set and getting back an IRQ which can 418c2ecf20Sopenharmony_ci * subsequently be used for internal accounting in the (optional) disable 428c2ecf20Sopenharmony_ci * path. 438c2ecf20Sopenharmony_ci */ 448c2ecf20Sopenharmony_cistatic unsigned long ilsel_level_map; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic inline unsigned int ilsel_offset(unsigned int bit) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci return ILSEL_LEVELS - bit - 1; 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic inline unsigned long mk_ilsel_addr(unsigned int bit) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci return ILSEL_BASE + ((ilsel_offset(bit) >> 1) & ~0x1); 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic inline unsigned int mk_ilsel_shift(unsigned int bit) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci return (ilsel_offset(bit) & 0x3) << 2; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic void __ilsel_enable(ilsel_source_t set, unsigned int bit) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci unsigned int tmp, shift; 648c2ecf20Sopenharmony_ci unsigned long addr; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci pr_notice("enabling ILSEL set %d\n", set); 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci addr = mk_ilsel_addr(bit); 698c2ecf20Sopenharmony_ci shift = mk_ilsel_shift(bit); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci pr_debug("%s: bit#%d: addr - 0x%08lx (shift %d, set %d)\n", 728c2ecf20Sopenharmony_ci __func__, bit, addr, shift, set); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci tmp = __raw_readw(addr); 758c2ecf20Sopenharmony_ci tmp &= ~(0xf << shift); 768c2ecf20Sopenharmony_ci tmp |= set << shift; 778c2ecf20Sopenharmony_ci __raw_writew(tmp, addr); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci/** 818c2ecf20Sopenharmony_ci * ilsel_enable - Enable an ILSEL set. 828c2ecf20Sopenharmony_ci * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 838c2ecf20Sopenharmony_ci * 848c2ecf20Sopenharmony_ci * Enables a given non-aliased ILSEL source (<= ILSEL_KEY) at the highest 858c2ecf20Sopenharmony_ci * available interrupt level. Callers should take care to order callsites 868c2ecf20Sopenharmony_ci * noting descending interrupt levels. Aliasing FPGA and external board 878c2ecf20Sopenharmony_ci * IRQs need to use ilsel_enable_fixed(). 888c2ecf20Sopenharmony_ci * 898c2ecf20Sopenharmony_ci * The return value is an IRQ number that can later be taken down with 908c2ecf20Sopenharmony_ci * ilsel_disable(). 918c2ecf20Sopenharmony_ci */ 928c2ecf20Sopenharmony_ciint ilsel_enable(ilsel_source_t set) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci unsigned int bit; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (unlikely(set > ILSEL_KEY)) { 978c2ecf20Sopenharmony_ci pr_err("Aliased sources must use ilsel_enable_fixed()\n"); 988c2ecf20Sopenharmony_ci return -EINVAL; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci do { 1028c2ecf20Sopenharmony_ci bit = find_first_zero_bit(&ilsel_level_map, ILSEL_LEVELS); 1038c2ecf20Sopenharmony_ci } while (test_and_set_bit(bit, &ilsel_level_map)); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci __ilsel_enable(set, bit); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return bit; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_enable); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci/** 1128c2ecf20Sopenharmony_ci * ilsel_enable_fixed - Enable an ILSEL set at a fixed interrupt level 1138c2ecf20Sopenharmony_ci * @set: ILSEL source (see ilsel_source_t enum in include/asm-sh/ilsel.h). 1148c2ecf20Sopenharmony_ci * @level: Interrupt level (1 - 15) 1158c2ecf20Sopenharmony_ci * 1168c2ecf20Sopenharmony_ci * Enables a given ILSEL source at a fixed interrupt level. Necessary 1178c2ecf20Sopenharmony_ci * both for level reservation as well as for aliased sources that only 1188c2ecf20Sopenharmony_ci * exist on special ILSEL#s. 1198c2ecf20Sopenharmony_ci * 1208c2ecf20Sopenharmony_ci * Returns an IRQ number (as ilsel_enable()). 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_ciint ilsel_enable_fixed(ilsel_source_t set, unsigned int level) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci unsigned int bit = ilsel_offset(level - 1); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci if (test_and_set_bit(bit, &ilsel_level_map)) 1278c2ecf20Sopenharmony_ci return -EBUSY; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci __ilsel_enable(set, bit); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci return bit; 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_enable_fixed); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci/** 1368c2ecf20Sopenharmony_ci * ilsel_disable - Disable an ILSEL set 1378c2ecf20Sopenharmony_ci * @irq: Bit position for ILSEL set value (retval from enable routines) 1388c2ecf20Sopenharmony_ci * 1398c2ecf20Sopenharmony_ci * Disable a previously enabled ILSEL set. 1408c2ecf20Sopenharmony_ci */ 1418c2ecf20Sopenharmony_civoid ilsel_disable(unsigned int irq) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci unsigned long addr; 1448c2ecf20Sopenharmony_ci unsigned int tmp; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci pr_notice("disabling ILSEL set %d\n", irq); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci addr = mk_ilsel_addr(irq); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci tmp = __raw_readw(addr); 1518c2ecf20Sopenharmony_ci tmp &= ~(0xf << mk_ilsel_shift(irq)); 1528c2ecf20Sopenharmony_ci __raw_writew(tmp, addr); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci clear_bit(irq, &ilsel_level_map); 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ilsel_disable); 157