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