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