aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'arch/sh/kernel')
-rw-r--r--arch/sh/kernel/Makefile2
-rw-r--r--arch/sh/kernel/cpu/Makefile11
-rw-r--r--arch/sh/kernel/cpu/init.c2
-rw-r--r--arch/sh/kernel/cpu/irq/imask.c5
-rw-r--r--arch/sh/kernel/cpu/irq/ipr.c16
-rw-r--r--arch/sh/kernel/cpu/sh2/Makefile3
-rw-r--r--arch/sh/kernel/cpu/sh2/clock-sh7619.c81
-rw-r--r--arch/sh/kernel/cpu/sh2/probe.c16
-rw-r--r--arch/sh/kernel/cpu/sh2/setup-sh7619.c53
-rw-r--r--arch/sh/kernel/cpu/sh2a/Makefile10
-rw-r--r--arch/sh/kernel/cpu/sh2a/clock-sh7206.c85
-rw-r--r--arch/sh/kernel/cpu/sh2a/probe.c39
-rw-r--r--arch/sh/kernel/cpu/sh2a/setup-sh7206.c58
-rw-r--r--arch/sh/kernel/setup.c1
-rw-r--r--arch/sh/kernel/signal.c10
-rw-r--r--arch/sh/kernel/timers/Makefile2
-rw-r--r--arch/sh/kernel/timers/timer-cmt.c256
-rw-r--r--arch/sh/kernel/timers/timer-mtu2.c260
-rw-r--r--arch/sh/kernel/timers/timer.c6
19 files changed, 900 insertions, 16 deletions
diff --git a/arch/sh/kernel/Makefile b/arch/sh/kernel/Makefile
index 5da88a43d350..50d54c24d76a 100644
--- a/arch/sh/kernel/Makefile
+++ b/arch/sh/kernel/Makefile
@@ -4,7 +4,7 @@
4 4
5extra-y := head.o init_task.o vmlinux.lds 5extra-y := head.o init_task.o vmlinux.lds
6 6
7obj-y := process.o signal.o entry.o traps.o irq.o \ 7obj-y := process.o signal.o traps.o irq.o \
8 ptrace.o setup.o time.o sys_sh.o semaphore.o \ 8 ptrace.o setup.o time.o sys_sh.o semaphore.o \
9 io.o io_generic.o sh_ksyms.o syscalls.o 9 io.o io_generic.o sh_ksyms.o syscalls.o
10 10
diff --git a/arch/sh/kernel/cpu/Makefile b/arch/sh/kernel/cpu/Makefile
index fb5dac069382..0582e6712b79 100644
--- a/arch/sh/kernel/cpu/Makefile
+++ b/arch/sh/kernel/cpu/Makefile
@@ -2,11 +2,12 @@
2# Makefile for the Linux/SuperH CPU-specifc backends. 2# Makefile for the Linux/SuperH CPU-specifc backends.
3# 3#
4 4
5obj-y += irq/ init.o clock.o 5obj-$(CONFIG_CPU_SH2) = sh2/
6 6obj-$(CONFIG_CPU_SH2A) = sh2a/
7obj-$(CONFIG_CPU_SH2) += sh2/ 7obj-$(CONFIG_CPU_SH3) = sh3/
8obj-$(CONFIG_CPU_SH3) += sh3/ 8obj-$(CONFIG_CPU_SH4) = sh4/
9obj-$(CONFIG_CPU_SH4) += sh4/
10 9
11obj-$(CONFIG_UBC_WAKEUP) += ubc.o 10obj-$(CONFIG_UBC_WAKEUP) += ubc.o
12obj-$(CONFIG_SH_ADC) += adc.o 11obj-$(CONFIG_SH_ADC) += adc.o
12
13obj-y += irq/ init.o clock.o
diff --git a/arch/sh/kernel/cpu/init.c b/arch/sh/kernel/cpu/init.c
index bfb90eb0b7a6..48121766e8d2 100644
--- a/arch/sh/kernel/cpu/init.c
+++ b/arch/sh/kernel/cpu/init.c
@@ -68,12 +68,14 @@ static void __init cache_init(void)
68 68
69 waysize = cpu_data->dcache.sets; 69 waysize = cpu_data->dcache.sets;
70 70
71#ifdef CCR_CACHE_ORA
71 /* 72 /*
72 * If the OC is already in RAM mode, we only have 73 * If the OC is already in RAM mode, we only have
73 * half of the entries to flush.. 74 * half of the entries to flush..
74 */ 75 */
75 if (ccr & CCR_CACHE_ORA) 76 if (ccr & CCR_CACHE_ORA)
76 waysize >>= 1; 77 waysize >>= 1;
78#endif
77 79
78 waysize <<= cpu_data->dcache.entry_shift; 80 waysize <<= cpu_data->dcache.entry_shift;
79 81
diff --git a/arch/sh/kernel/cpu/irq/imask.c b/arch/sh/kernel/cpu/irq/imask.c
index a33ae3e0a5a5..301b505c4278 100644
--- a/arch/sh/kernel/cpu/irq/imask.c
+++ b/arch/sh/kernel/cpu/irq/imask.c
@@ -53,7 +53,10 @@ void static inline set_interrupt_registers(int ip)
53{ 53{
54 unsigned long __dummy; 54 unsigned long __dummy;
55 55
56 asm volatile("ldc %2, r6_bank\n\t" 56 asm volatile(
57#ifdef CONFIG_CPU_HAS_SR_RB
58 "ldc %2, r6_bank\n\t"
59#endif
57 "stc sr, %0\n\t" 60 "stc sr, %0\n\t"
58 "and #0xf0, %0\n\t" 61 "and #0xf0, %0\n\t"
59 "shlr2 %0\n\t" 62 "shlr2 %0\n\t"
diff --git a/arch/sh/kernel/cpu/irq/ipr.c b/arch/sh/kernel/cpu/irq/ipr.c
index a0089563cbfc..f7a2bae1df94 100644
--- a/arch/sh/kernel/cpu/irq/ipr.c
+++ b/arch/sh/kernel/cpu/irq/ipr.c
@@ -62,6 +62,10 @@ void make_ipr_irq(struct ipr_data *table, unsigned int nr_irqs)
62} 62}
63EXPORT_SYMBOL(make_ipr_irq); 63EXPORT_SYMBOL(make_ipr_irq);
64 64
65/*
66 * XXX: Move this garbage in to the drivers, and kill off the ridiculous CPU
67 * subtype checks.
68 */
65static struct ipr_data sys_ipr_map[] = { 69static struct ipr_data sys_ipr_map[] = {
66#ifndef CONFIG_CPU_SUBTYPE_SH7780 70#ifndef CONFIG_CPU_SUBTYPE_SH7780
67 { TIMER_IRQ, TIMER_IPR_ADDR, TIMER_IPR_POS, TIMER_PRIORITY }, 71 { TIMER_IRQ, TIMER_IPR_ADDR, TIMER_IPR_POS, TIMER_PRIORITY },
@@ -80,6 +84,18 @@ static struct ipr_data sys_ipr_map[] = {
80 { SCIF1_BRI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY }, 84 { SCIF1_BRI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY },
81 { SCIF1_TXI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY }, 85 { SCIF1_TXI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY },
82#endif 86#endif
87#ifdef SCIF2_ERI_IRQ
88 { SCIF2_ERI_IRQ, SCIF2_IPR_ADDR, SCIF2_IPR_POS, SCIF2_PRIORITY },
89 { SCIF2_RXI_IRQ, SCIF2_IPR_ADDR, SCIF2_IPR_POS, SCIF2_PRIORITY },
90 { SCIF2_BRI_IRQ, SCIF2_IPR_ADDR, SCIF2_IPR_POS, SCIF2_PRIORITY },
91 { SCIF2_TXI_IRQ, SCIF2_IPR_ADDR, SCIF2_IPR_POS, SCIF2_PRIORITY },
92#endif
93#ifdef SCIF3_ERI_IRQ
94 { SCIF3_ERI_IRQ, SCIF3_IPR_ADDR, SCIF3_IPR_POS, SCIF3_PRIORITY },
95 { SCIF3_RXI_IRQ, SCIF3_IPR_ADDR, SCIF3_IPR_POS, SCIF3_PRIORITY },
96 { SCIF3_BRI_IRQ, SCIF3_IPR_ADDR, SCIF3_IPR_POS, SCIF3_PRIORITY },
97 { SCIF3_TXI_IRQ, SCIF3_IPR_ADDR, SCIF3_IPR_POS, SCIF3_PRIORITY },
98#endif
83#if defined(CONFIG_CPU_SUBTYPE_SH7300) 99#if defined(CONFIG_CPU_SUBTYPE_SH7300)
84 { SCIF0_IRQ, SCIF0_IPR_ADDR, SCIF0_IPR_POS, SCIF0_PRIORITY }, 100 { SCIF0_IRQ, SCIF0_IPR_ADDR, SCIF0_IPR_POS, SCIF0_PRIORITY },
85 { DMTE2_IRQ, DMA1_IPR_ADDR, DMA1_IPR_POS, DMA1_PRIORITY }, 101 { DMTE2_IRQ, DMA1_IPR_ADDR, DMA1_IPR_POS, DMA1_PRIORITY },
diff --git a/arch/sh/kernel/cpu/sh2/Makefile b/arch/sh/kernel/cpu/sh2/Makefile
index 389353fba608..f0f059acfcfb 100644
--- a/arch/sh/kernel/cpu/sh2/Makefile
+++ b/arch/sh/kernel/cpu/sh2/Makefile
@@ -2,5 +2,6 @@
2# Makefile for the Linux/SuperH SH-2 backends. 2# Makefile for the Linux/SuperH SH-2 backends.
3# 3#
4 4
5obj-y := probe.o 5obj-y := ex.o probe.o entry.o
6 6
7obj-$(CONFIG_CPU_SUBTYPE_SH7619) += setup-sh7619.o clock-sh7619.o
diff --git a/arch/sh/kernel/cpu/sh2/clock-sh7619.c b/arch/sh/kernel/cpu/sh2/clock-sh7619.c
new file mode 100644
index 000000000000..d0440b269702
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2/clock-sh7619.c
@@ -0,0 +1,81 @@
1/*
2 * arch/sh/kernel/cpu/sh2/clock-sh7619.c
3 *
4 * SH7619 support for the clock framework
5 *
6 * Copyright (C) 2006 Yoshinori Sato
7 *
8 * Based on clock-sh4.c
9 * Copyright (C) 2005 Paul Mundt
10 *
11 * This file is subject to the terms and conditions of the GNU General Public
12 * License. See the file "COPYING" in the main directory of this archive
13 * for more details.
14 */
15#include <linux/init.h>
16#include <linux/kernel.h>
17#include <asm/clock.h>
18#include <asm/freq.h>
19#include <asm/io.h>
20
21const static int pll1rate[]={1,2};
22const static int pfc_divisors[]={1,2,0,4};
23
24#if (CONFIG_SH_CLK_MD == 1) || (CONFIG_SH_CLK_MD == 2)
25#define PLL2 (4)
26#elif (CONFIG_SH_CLK_MD == 5) || (CONFIG_SH_CLK_MD == 6)
27#define PLL2 (2)
28#else
29#error "Illigal Clock Mode!"
30#endif
31
32static void master_clk_init(struct clk *clk)
33{
34 clk->rate *= PLL2 * pll1rate[(ctrl_inw(FREQCR) >> 8) & 7];
35}
36
37static struct clk_ops sh7619_master_clk_ops = {
38 .init = master_clk_init,
39};
40
41static void module_clk_recalc(struct clk *clk)
42{
43 int idx = (ctrl_inw(FREQCR) & 0x0007);
44 clk->rate = clk->parent->rate / pfc_divisors[idx];
45}
46
47static struct clk_ops sh7619_module_clk_ops = {
48 .recalc = module_clk_recalc,
49};
50
51static void bus_clk_recalc(struct clk *clk)
52{
53 clk->rate = clk->parent->rate / pll1rate[(ctrl_inw(FREQCR) >> 8) & 7];
54}
55
56static struct clk_ops sh7619_bus_clk_ops = {
57 .recalc = bus_clk_recalc,
58};
59
60static void cpu_clk_recalc(struct clk *clk)
61{
62 clk->rate = clk->parent->rate;
63}
64
65static struct clk_ops sh7619_cpu_clk_ops = {
66 .recalc = cpu_clk_recalc,
67};
68
69static struct clk_ops *sh7619_clk_ops[] = {
70 &sh7619_master_clk_ops,
71 &sh7619_module_clk_ops,
72 &sh7619_bus_clk_ops,
73 &sh7619_cpu_clk_ops,
74};
75
76void __init arch_init_clk_ops(struct clk_ops **ops, int idx)
77{
78 if (idx < ARRAY_SIZE(sh7619_clk_ops))
79 *ops = sh7619_clk_ops[idx];
80}
81
diff --git a/arch/sh/kernel/cpu/sh2/probe.c b/arch/sh/kernel/cpu/sh2/probe.c
index f17a2a0d588e..ba527d9b5024 100644
--- a/arch/sh/kernel/cpu/sh2/probe.c
+++ b/arch/sh/kernel/cpu/sh2/probe.c
@@ -17,17 +17,23 @@
17 17
18int __init detect_cpu_and_cache_system(void) 18int __init detect_cpu_and_cache_system(void)
19{ 19{
20 /* 20#if defined(CONFIG_CPU_SUBTYPE_SH7604)
21 * For now, assume SH7604 .. fix this later.
22 */
23 cpu_data->type = CPU_SH7604; 21 cpu_data->type = CPU_SH7604;
24 cpu_data->dcache.ways = 4; 22 cpu_data->dcache.ways = 4;
25 cpu_data->dcache.way_shift = 6; 23 cpu_data->dcache.way_incr = (1<<10);
26 cpu_data->dcache.sets = 64; 24 cpu_data->dcache.sets = 64;
27 cpu_data->dcache.entry_shift = 4; 25 cpu_data->dcache.entry_shift = 4;
28 cpu_data->dcache.linesz = L1_CACHE_BYTES; 26 cpu_data->dcache.linesz = L1_CACHE_BYTES;
29 cpu_data->dcache.flags = 0; 27 cpu_data->dcache.flags = 0;
30 28#elif defined(CONFIG_CPU_SUBTYPE_SH7619)
29 cpu_data->type = CPU_SH7619;
30 cpu_data->dcache.ways = 4;
31 cpu_data->dcache.way_incr = (1<<12);
32 cpu_data->dcache.sets = 256;
33 cpu_data->dcache.entry_shift = 4;
34 cpu_data->dcache.linesz = L1_CACHE_BYTES;
35 cpu_data->dcache.flags = 0;
36#endif
31 /* 37 /*
32 * SH-2 doesn't have separate caches 38 * SH-2 doesn't have separate caches
33 */ 39 */
diff --git a/arch/sh/kernel/cpu/sh2/setup-sh7619.c b/arch/sh/kernel/cpu/sh2/setup-sh7619.c
new file mode 100644
index 000000000000..82c2d905152f
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2/setup-sh7619.c
@@ -0,0 +1,53 @@
1/*
2 * SH7619 Setup
3 *
4 * Copyright (C) 2006 Yoshinori Sato
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10#include <linux/platform_device.h>
11#include <linux/init.h>
12#include <linux/serial.h>
13#include <asm/sci.h>
14
15static struct plat_sci_port sci_platform_data[] = {
16 {
17 .mapbase = 0xf8400000,
18 .flags = UPF_BOOT_AUTOCONF,
19 .type = PORT_SCIF,
20 .irqs = { 88, 89, 91, 90},
21 }, {
22 .mapbase = 0xf8410000,
23 .flags = UPF_BOOT_AUTOCONF,
24 .type = PORT_SCIF,
25 .irqs = { 92, 93, 95, 94},
26 }, {
27 .mapbase = 0xf8420000,
28 .flags = UPF_BOOT_AUTOCONF,
29 .type = PORT_SCIF,
30 .irqs = { 96, 97, 99, 98},
31 }, {
32 .flags = 0,
33 }
34};
35
36static struct platform_device sci_device = {
37 .name = "sh-sci",
38 .id = -1,
39 .dev = {
40 .platform_data = sci_platform_data,
41 },
42};
43
44static struct platform_device *sh7619_devices[] __initdata = {
45 &sci_device,
46};
47
48static int __init sh7619_devices_setup(void)
49{
50 return platform_add_devices(sh7619_devices,
51 ARRAY_SIZE(sh7619_devices));
52}
53__initcall(sh7619_devices_setup);
diff --git a/arch/sh/kernel/cpu/sh2a/Makefile b/arch/sh/kernel/cpu/sh2a/Makefile
new file mode 100644
index 000000000000..350972ae9410
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2a/Makefile
@@ -0,0 +1,10 @@
1#
2# Makefile for the Linux/SuperH SH-2A backends.
3#
4
5obj-y := common.o probe.o
6
7common-y += $(addprefix ../sh2/, ex.o)
8common-y += $(addprefix ../sh2/, entry.o)
9
10obj-$(CONFIG_CPU_SUBTYPE_SH7206) += setup-sh7206.o clock-sh7206.o
diff --git a/arch/sh/kernel/cpu/sh2a/clock-sh7206.c b/arch/sh/kernel/cpu/sh2a/clock-sh7206.c
new file mode 100644
index 000000000000..a9ad309c6a33
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2a/clock-sh7206.c
@@ -0,0 +1,85 @@
1/*
2 * arch/sh/kernel/cpu/sh2a/clock-sh7206.c
3 *
4 * SH7206 support for the clock framework
5 *
6 * Copyright (C) 2006 Yoshinori Sato
7 *
8 * Based on clock-sh4.c
9 * Copyright (C) 2005 Paul Mundt
10 *
11 * This file is subject to the terms and conditions of the GNU General Public
12 * License. See the file "COPYING" in the main directory of this archive
13 * for more details.
14 */
15#include <linux/init.h>
16#include <linux/kernel.h>
17#include <asm/clock.h>
18#include <asm/freq.h>
19#include <asm/io.h>
20
21const static int pll1rate[]={1,2,3,4,6,8};
22const static int pfc_divisors[]={1,2,3,4,6,8,12};
23#define ifc_divisors pfc_divisors
24
25#if (CONFIG_SH_CLK_MD == 2)
26#define PLL2 (4)
27#elif (CONFIG_SH_CLK_MD == 6)
28#define PLL2 (2)
29#elif (CONFIG_SH_CLK_MD == 7)
30#define PLL2 (1)
31#else
32#error "Illigal Clock Mode!"
33#endif
34
35static void master_clk_init(struct clk *clk)
36{
37 clk->rate *= PLL2 * pll1rate[(ctrl_inw(FREQCR) >> 8) & 0x0007];
38}
39
40static struct clk_ops sh7206_master_clk_ops = {
41 .init = master_clk_init,
42};
43
44static void module_clk_recalc(struct clk *clk)
45{
46 int idx = (ctrl_inw(FREQCR) & 0x0007);
47 clk->rate = clk->parent->rate / pfc_divisors[idx];
48}
49
50static struct clk_ops sh7206_module_clk_ops = {
51 .recalc = module_clk_recalc,
52};
53
54static void bus_clk_recalc(struct clk *clk)
55{
56 clk->rate = clk->parent->rate / pll1rate[(ctrl_inw(FREQCR) >> 8) & 0x0007];
57}
58
59static struct clk_ops sh7206_bus_clk_ops = {
60 .recalc = bus_clk_recalc,
61};
62
63static void cpu_clk_recalc(struct clk *clk)
64{
65 int idx = (ctrl_inw(FREQCR) & 0x0007);
66 clk->rate = clk->parent->rate / ifc_divisors[idx];
67}
68
69static struct clk_ops sh7206_cpu_clk_ops = {
70 .recalc = cpu_clk_recalc,
71};
72
73static struct clk_ops *sh7206_clk_ops[] = {
74 &sh7206_master_clk_ops,
75 &sh7206_module_clk_ops,
76 &sh7206_bus_clk_ops,
77 &sh7206_cpu_clk_ops,
78};
79
80void __init arch_init_clk_ops(struct clk_ops **ops, int idx)
81{
82 if (idx < ARRAY_SIZE(sh7206_clk_ops))
83 *ops = sh7206_clk_ops[idx];
84}
85
diff --git a/arch/sh/kernel/cpu/sh2a/probe.c b/arch/sh/kernel/cpu/sh2a/probe.c
new file mode 100644
index 000000000000..87c6c0542089
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2a/probe.c
@@ -0,0 +1,39 @@
1/*
2 * arch/sh/kernel/cpu/sh2a/probe.c
3 *
4 * CPU Subtype Probing for SH-2A.
5 *
6 * Copyright (C) 2004, 2005 Paul Mundt
7 *
8 * This file is subject to the terms and conditions of the GNU General Public
9 * License. See the file "COPYING" in the main directory of this archive
10 * for more details.
11 */
12
13#include <linux/init.h>
14#include <asm/processor.h>
15#include <asm/cache.h>
16
17int __init detect_cpu_and_cache_system(void)
18{
19 /* Just SH7206 for now .. */
20 cpu_data->type = CPU_SH7206;
21
22 cpu_data->dcache.ways = 4;
23 cpu_data->dcache.way_incr = (1 << 11);
24 cpu_data->dcache.sets = 128;
25 cpu_data->dcache.entry_shift = 4;
26 cpu_data->dcache.linesz = L1_CACHE_BYTES;
27 cpu_data->dcache.flags = 0;
28
29 /*
30 * The icache is the same as the dcache as far as this setup is
31 * concerned. The only real difference in hardware is that the icache
32 * lacks the U bit that the dcache has, none of this has any bearing
33 * on the cache info.
34 */
35 cpu_data->icache = cpu_data->dcache;
36
37 return 0;
38}
39
diff --git a/arch/sh/kernel/cpu/sh2a/setup-sh7206.c b/arch/sh/kernel/cpu/sh2a/setup-sh7206.c
new file mode 100644
index 000000000000..cdfeef49e62e
--- /dev/null
+++ b/arch/sh/kernel/cpu/sh2a/setup-sh7206.c
@@ -0,0 +1,58 @@
1/*
2 * SH7206 Setup
3 *
4 * Copyright (C) 2006 Yoshinori Sato
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10#include <linux/platform_device.h>
11#include <linux/init.h>
12#include <linux/serial.h>
13#include <asm/sci.h>
14
15static struct plat_sci_port sci_platform_data[] = {
16 {
17 .mapbase = 0xfffe8000,
18 .flags = UPF_BOOT_AUTOCONF,
19 .type = PORT_SCIF,
20 .irqs = { 240, 241, 242, 243},
21 }, {
22 .mapbase = 0xfffe8800,
23 .flags = UPF_BOOT_AUTOCONF,
24 .type = PORT_SCIF,
25 .irqs = { 244, 245, 246, 247},
26 }, {
27 .mapbase = 0xfffe9000,
28 .flags = UPF_BOOT_AUTOCONF,
29 .type = PORT_SCIF,
30 .irqs = { 248, 249, 250, 251},
31 }, {
32 .mapbase = 0xfffe9800,
33 .flags = UPF_BOOT_AUTOCONF,
34 .type = PORT_SCIF,
35 .irqs = { 252, 253, 254, 255},
36 }, {
37 .flags = 0,
38 }
39};
40
41static struct platform_device sci_device = {
42 .name = "sh-sci",
43 .id = -1,
44 .dev = {
45 .platform_data = sci_platform_data,
46 },
47};
48
49static struct platform_device *sh7206_devices[] __initdata = {
50 &sci_device,
51};
52
53static int __init sh7206_devices_setup(void)
54{
55 return platform_add_devices(sh7206_devices,
56 ARRAY_SIZE(sh7206_devices));
57}
58__initcall(sh7206_devices_setup);
diff --git a/arch/sh/kernel/setup.c b/arch/sh/kernel/setup.c
index 36d86f9ac38a..c24f6390007b 100644
--- a/arch/sh/kernel/setup.c
+++ b/arch/sh/kernel/setup.c
@@ -404,6 +404,7 @@ static const char *cpu_name[] = {
404 [CPU_SH4_202] = "SH4-202", [CPU_SH4_501] = "SH4-501", 404 [CPU_SH4_202] = "SH4-202", [CPU_SH4_501] = "SH4-501",
405 [CPU_SH7770] = "SH7770", [CPU_SH7780] = "SH7780", 405 [CPU_SH7770] = "SH7770", [CPU_SH7780] = "SH7780",
406 [CPU_SH7781] = "SH7781", [CPU_SH7343] = "SH7343", 406 [CPU_SH7781] = "SH7781", [CPU_SH7343] = "SH7343",
407 [CPU_SH7206] = "SH7206", [CPU_SH7619] = "SH7619",
407 [CPU_SH_NONE] = "Unknown" 408 [CPU_SH_NONE] = "Unknown"
408}; 409};
409 410
diff --git a/arch/sh/kernel/signal.c b/arch/sh/kernel/signal.c
index 5213f5bc6ce0..764886b4bcf1 100644
--- a/arch/sh/kernel/signal.c
+++ b/arch/sh/kernel/signal.c
@@ -98,7 +98,11 @@ sys_sigaltstack(const stack_t __user *uss, stack_t __user *uoss,
98 */ 98 */
99 99
100#define MOVW(n) (0x9300|((n)-2)) /* Move mem word at PC+n to R3 */ 100#define MOVW(n) (0x9300|((n)-2)) /* Move mem word at PC+n to R3 */
101#define TRAP16 0xc310 /* Syscall w/no args (NR in R3) */ 101#if defined(CONFIG_CPU_SH2) || defined(CONFIG_CPU_SH2A)
102#define TRAP_NOARG 0xc320 /* Syscall w/no args (NR in R3) */
103#else
104#define TRAP_NOARG 0xc310 /* Syscall w/no args (NR in R3) */
105#endif
102#define OR_R0_R0 0x200b /* or r0,r0 (insert to avoid hardware bug) */ 106#define OR_R0_R0 0x200b /* or r0,r0 (insert to avoid hardware bug) */
103 107
104struct sigframe 108struct sigframe
@@ -350,7 +354,7 @@ static int setup_frame(int sig, struct k_sigaction *ka,
350 } else { 354 } else {
351 /* Generate return code (system call to sigreturn) */ 355 /* Generate return code (system call to sigreturn) */
352 err |= __put_user(MOVW(7), &frame->retcode[0]); 356 err |= __put_user(MOVW(7), &frame->retcode[0]);
353 err |= __put_user(TRAP16, &frame->retcode[1]); 357 err |= __put_user(TRAP_NOARG, &frame->retcode[1]);
354 err |= __put_user(OR_R0_R0, &frame->retcode[2]); 358 err |= __put_user(OR_R0_R0, &frame->retcode[2]);
355 err |= __put_user(OR_R0_R0, &frame->retcode[3]); 359 err |= __put_user(OR_R0_R0, &frame->retcode[3]);
356 err |= __put_user(OR_R0_R0, &frame->retcode[4]); 360 err |= __put_user(OR_R0_R0, &frame->retcode[4]);
@@ -430,7 +434,7 @@ static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
430 } else { 434 } else {
431 /* Generate return code (system call to rt_sigreturn) */ 435 /* Generate return code (system call to rt_sigreturn) */
432 err |= __put_user(MOVW(7), &frame->retcode[0]); 436 err |= __put_user(MOVW(7), &frame->retcode[0]);
433 err |= __put_user(TRAP16, &frame->retcode[1]); 437 err |= __put_user(TRAP_NOARG, &frame->retcode[1]);
434 err |= __put_user(OR_R0_R0, &frame->retcode[2]); 438 err |= __put_user(OR_R0_R0, &frame->retcode[2]);
435 err |= __put_user(OR_R0_R0, &frame->retcode[3]); 439 err |= __put_user(OR_R0_R0, &frame->retcode[3]);
436 err |= __put_user(OR_R0_R0, &frame->retcode[4]); 440 err |= __put_user(OR_R0_R0, &frame->retcode[4]);
diff --git a/arch/sh/kernel/timers/Makefile b/arch/sh/kernel/timers/Makefile
index 151a6a304cec..bcf244ff6a12 100644
--- a/arch/sh/kernel/timers/Makefile
+++ b/arch/sh/kernel/timers/Makefile
@@ -5,4 +5,6 @@
5obj-y := timer.o 5obj-y := timer.o
6 6
7obj-$(CONFIG_SH_TMU) += timer-tmu.o 7obj-$(CONFIG_SH_TMU) += timer-tmu.o
8obj-$(CONFIG_SH_MTU2) += timer-mtu2.o
9obj-$(CONFIG_SH_CMT) += timer-cmt.o
8 10
diff --git a/arch/sh/kernel/timers/timer-cmt.c b/arch/sh/kernel/timers/timer-cmt.c
new file mode 100644
index 000000000000..9eab395cd341
--- /dev/null
+++ b/arch/sh/kernel/timers/timer-cmt.c
@@ -0,0 +1,256 @@
1/*
2 * arch/sh/kernel/timers/timer-cmt.c - CMT Timer Support
3 *
4 * Copyright (C) 2005 Yoshinori Sato
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10
11#include <linux/init.h>
12#include <linux/kernel.h>
13#include <linux/interrupt.h>
14#include <linux/spinlock.h>
15#include <linux/seqlock.h>
16#include <asm/timer.h>
17#include <asm/rtc.h>
18#include <asm/io.h>
19#include <asm/irq.h>
20#include <asm/clock.h>
21
22#if defined(CONFIG_CPU_SUBTYPE_SH7619)
23#define CMT_CMSTR 0xf84a0070
24#define CMT_CMCSR_0 0xf84a0072
25#define CMT_CMCNT_0 0xf84a0074
26#define CMT_CMCOR_0 0xf84a0076
27#define CMT_CMCSR_1 0xf84a0078
28#define CMT_CMCNT_1 0xf84a007a
29#define CMT_CMCOR_1 0xf84a007c
30
31#define STBCR3 0xf80a0000
32#define cmt_clock_enable() do { ctrl_outb(ctrl_inb(STBCR3) & ~0x10, STBCR3); } while(0)
33#define CMT_CMCSR_INIT 0x0040
34#define CMT_CMCSR_CALIB 0x0000
35#elif defined(CONFIG_CPU_SUBTYPE_SH7206)
36#define CMT_CMSTR 0xfffec000
37#define CMT_CMCSR_0 0xfffec002
38#define CMT_CMCNT_0 0xfffec004
39#define CMT_CMCOR_0 0xfffec006
40
41#define STBCR4 0xfffe040c
42#define cmt_clock_enable() do { ctrl_outb(ctrl_inb(STBCR4) & ~0x04, STBCR4); } while(0)
43#define CMT_CMCSR_INIT 0x0040
44#define CMT_CMCSR_CALIB 0x0000
45#else
46#error "Unknown CPU SUBTYPE"
47#endif
48
49static DEFINE_SPINLOCK(cmt0_lock);
50
51static unsigned long cmt_timer_get_offset(void)
52{
53 int count;
54 unsigned long flags;
55
56 static unsigned short count_p = 0xffff; /* for the first call after boot */
57 static unsigned long jiffies_p = 0;
58
59 /*
60 * cache volatile jiffies temporarily; we have IRQs turned off.
61 */
62 unsigned long jiffies_t;
63
64 spin_lock_irqsave(&cmt0_lock, flags);
65 /* timer count may underflow right here */
66 count = ctrl_inw(CMT_CMCOR_0);
67 count -= ctrl_inw(CMT_CMCNT_0);
68
69 jiffies_t = jiffies;
70
71 /*
72 * avoiding timer inconsistencies (they are rare, but they happen)...
73 * there is one kind of problem that must be avoided here:
74 * 1. the timer counter underflows
75 */
76
77 if (jiffies_t == jiffies_p) {
78 if (count > count_p) {
79 /* the nutcase */
80 if (ctrl_inw(CMT_CMCSR_0) & 0x80) { /* Check CMF bit */
81 count -= LATCH;
82 } else {
83 printk("%s (): hardware timer problem?\n",
84 __FUNCTION__);
85 }
86 }
87 } else
88 jiffies_p = jiffies_t;
89
90 count_p = count;
91 spin_unlock_irqrestore(&cmt0_lock, flags);
92
93 count = ((LATCH-1) - count) * TICK_SIZE;
94 count = (count + LATCH/2) / LATCH;
95
96 return count;
97}
98
99static irqreturn_t cmt_timer_interrupt(int irq, void *dev_id,
100 struct pt_regs *regs)
101{
102 unsigned long timer_status;
103
104 /* Clear CMF bit */
105 timer_status = ctrl_inw(CMT_CMCSR_0);
106 timer_status &= ~0x80;
107 ctrl_outw(timer_status, CMT_CMCSR_0);
108
109 /*
110 * Here we are in the timer irq handler. We just have irqs locally
111 * disabled but we don't know if the timer_bh is running on the other
112 * CPU. We need to avoid to SMP race with it. NOTE: we don' t need
113 * the irq version of write_lock because as just said we have irq
114 * locally disabled. -arca
115 */
116 write_seqlock(&xtime_lock);
117 handle_timer_tick(regs);
118 write_sequnlock(&xtime_lock);
119
120 return IRQ_HANDLED;
121}
122
123static struct irqaction cmt_irq = {
124 .name = "timer",
125 .handler = cmt_timer_interrupt,
126 .flags = SA_INTERRUPT,
127 .mask = CPU_MASK_NONE,
128};
129
130/*
131 * Hah! We'll see if this works (switching from usecs to nsecs).
132 */
133static unsigned long cmt_timer_get_frequency(void)
134{
135 u32 freq;
136 struct timespec ts1, ts2;
137 unsigned long diff_nsec;
138 unsigned long factor;
139
140 /* Setup the timer: We don't want to generate interrupts, just
141 * have it count down at its natural rate.
142 */
143
144 ctrl_outw(ctrl_inw(CMT_CMSTR) & ~0x01, CMT_CMSTR);
145 ctrl_outw(CMT_CMCSR_CALIB, CMT_CMCSR_0);
146 ctrl_outw(0xffff, CMT_CMCOR_0);
147 ctrl_outw(0xffff, CMT_CMCNT_0);
148
149 rtc_sh_get_time(&ts2);
150
151 do {
152 rtc_sh_get_time(&ts1);
153 } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
154
155 /* actually start the timer */
156 ctrl_outw(ctrl_inw(CMT_CMSTR) | 0x01, CMT_CMSTR);
157
158 do {
159 rtc_sh_get_time(&ts2);
160 } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
161
162 freq = 0xffff - ctrl_inw(CMT_CMCNT_0);
163 if (ts2.tv_nsec < ts1.tv_nsec) {
164 ts2.tv_nsec += 1000000000;
165 ts2.tv_sec--;
166 }
167
168 diff_nsec = (ts2.tv_sec - ts1.tv_sec) * 1000000000 + (ts2.tv_nsec - ts1.tv_nsec);
169
170 /* this should work well if the RTC has a precision of n Hz, where
171 * n is an integer. I don't think we have to worry about the other
172 * cases. */
173 factor = (1000000000 + diff_nsec/2) / diff_nsec;
174
175 if (factor * diff_nsec > 1100000000 ||
176 factor * diff_nsec < 900000000)
177 panic("weird RTC (diff_nsec %ld)", diff_nsec);
178
179 return freq * factor;
180}
181
182static void cmt_clk_init(struct clk *clk)
183{
184 u8 divisor = CMT_CMCSR_INIT & 0x3;
185 ctrl_inw(CMT_CMCSR_0);
186 ctrl_outw(CMT_CMCSR_INIT, CMT_CMCSR_0);
187 clk->parent = clk_get("module_clk");
188 clk->rate = clk->parent->rate / (8 << (divisor << 1));
189}
190
191static void cmt_clk_recalc(struct clk *clk)
192{
193 u8 divisor = ctrl_inw(CMT_CMCSR_0) & 0x3;
194 clk->rate = clk->parent->rate / (8 << (divisor << 1));
195}
196
197static struct clk_ops cmt_clk_ops = {
198 .init = cmt_clk_init,
199 .recalc = cmt_clk_recalc,
200};
201
202static struct clk cmt0_clk = {
203 .name = "cmt0_clk",
204 .ops = &cmt_clk_ops,
205};
206
207static int cmt_timer_start(void)
208{
209 ctrl_outw(ctrl_inw(CMT_CMSTR) | 0x01, CMT_CMSTR);
210 return 0;
211}
212
213static int cmt_timer_stop(void)
214{
215 ctrl_outw(ctrl_inw(CMT_CMSTR) & ~0x01, CMT_CMSTR);
216 return 0;
217}
218
219static int cmt_timer_init(void)
220{
221 unsigned long interval;
222
223 cmt_clock_enable();
224
225 setup_irq(TIMER_IRQ, &cmt_irq);
226
227 cmt0_clk.parent = clk_get("module_clk");
228
229 cmt_timer_stop();
230
231 interval = cmt0_clk.parent->rate / 8 / HZ;
232 printk(KERN_INFO "Interval = %ld\n", interval);
233
234 ctrl_outw(interval, CMT_CMCOR_0);
235
236 clk_register(&cmt0_clk);
237 clk_enable(&cmt0_clk);
238
239 cmt_timer_start();
240
241 return 0;
242}
243
244struct sys_timer_ops cmt_timer_ops = {
245 .init = cmt_timer_init,
246 .start = cmt_timer_start,
247 .stop = cmt_timer_stop,
248 .get_frequency = cmt_timer_get_frequency,
249 .get_offset = cmt_timer_get_offset,
250};
251
252struct sys_timer cmt_timer = {
253 .name = "cmt",
254 .ops = &cmt_timer_ops,
255};
256
diff --git a/arch/sh/kernel/timers/timer-mtu2.c b/arch/sh/kernel/timers/timer-mtu2.c
new file mode 100644
index 000000000000..73a5ef3c457d
--- /dev/null
+++ b/arch/sh/kernel/timers/timer-mtu2.c
@@ -0,0 +1,260 @@
1/*
2 * arch/sh/kernel/timers/timer-mtu2.c - MTU2 Timer Support
3 *
4 * Copyright (C) 2005 Paul Mundt
5 *
6 * Based off of arch/sh/kernel/timers/timer-tmu.c
7 *
8 * This file is subject to the terms and conditions of the GNU General Public
9 * License. See the file "COPYING" in the main directory of this archive
10 * for more details.
11 */
12#include <linux/init.h>
13#include <linux/kernel.h>
14#include <linux/interrupt.h>
15#include <linux/spinlock.h>
16#include <linux/seqlock.h>
17#include <asm/timer.h>
18#include <asm/io.h>
19#include <asm/irq.h>
20#include <asm/clock.h>
21
22/*
23 * We use channel 1 for our lowly system timer. Channel 2 would be the other
24 * likely candidate, but we leave it alone as it has higher divisors that
25 * would be of more use to other more interesting applications.
26 *
27 * TODO: Presently we only implement a 16-bit single-channel system timer.
28 * However, we can implement channel cascade if we go the overflow route and
29 * get away with using 2 MTU2 channels as a 32-bit timer.
30 */
31
32static DEFINE_SPINLOCK(mtu2_lock);
33
34#define MTU2_TSTR 0xfffe4280
35#define MTU2_TCR_1 0xfffe4380
36#define MTU2_TMDR_1 0xfffe4381
37#define MTU2_TIOR_1 0xfffe4382
38#define MTU2_TIER_1 0xfffe4384
39#define MTU2_TSR_1 0xfffe4385
40#define MTU2_TCNT_1 0xfffe4386 /* 16-bit counter */
41#define MTU2_TGRA_1 0xfffe438a
42
43#define STBCR3 0xfffe0408
44
45#define MTU2_TSTR_CST1 (1 << 1) /* Counter Start 1 */
46
47#define MTU2_TSR_TGFA (1 << 0) /* GRA compare match */
48
49#define MTU2_TIER_TGIEA (1 << 0) /* GRA compare match interrupt enable */
50
51#define MTU2_TCR_INIT 0x22
52
53#define MTU2_TCR_CALIB 0x00
54
55static unsigned long mtu2_timer_get_offset(void)
56{
57 int count;
58 unsigned long flags;
59
60 static int count_p = 0x7fff; /* for the first call after boot */
61 static unsigned long jiffies_p = 0;
62
63 /*
64 * cache volatile jiffies temporarily; we have IRQs turned off.
65 */
66 unsigned long jiffies_t;
67
68 spin_lock_irqsave(&mtu2_lock, flags);
69 /* timer count may underflow right here */
70 count = ctrl_inw(MTU2_TCNT_1); /* read the latched count */
71
72 jiffies_t = jiffies;
73
74 /*
75 * avoiding timer inconsistencies (they are rare, but they happen)...
76 * there is one kind of problem that must be avoided here:
77 * 1. the timer counter underflows
78 */
79
80 if (jiffies_t == jiffies_p) {
81 if (count > count_p) {
82 if (ctrl_inb(MTU2_TSR_1) & MTU2_TSR_TGFA) {
83 count -= LATCH;
84 } else {
85 printk("%s (): hardware timer problem?\n",
86 __FUNCTION__);
87 }
88 }
89 } else
90 jiffies_p = jiffies_t;
91
92 count_p = count;
93 spin_unlock_irqrestore(&mtu2_lock, flags);
94
95 count = ((LATCH-1) - count) * TICK_SIZE;
96 count = (count + LATCH/2) / LATCH;
97
98 return count;
99}
100
101static irqreturn_t mtu2_timer_interrupt(int irq, void *dev_id,
102 struct pt_regs *regs)
103{
104 unsigned long timer_status;
105
106 /* Clear TGFA bit */
107 timer_status = ctrl_inb(MTU2_TSR_1);
108 timer_status &= ~MTU2_TSR_TGFA;
109 ctrl_outb(timer_status, MTU2_TSR_1);
110
111 /* Do timer tick */
112 write_seqlock(&xtime_lock);
113 handle_timer_tick(regs);
114 write_sequnlock(&xtime_lock);
115
116 return IRQ_HANDLED;
117}
118
119static struct irqaction mtu2_irq = {
120 .name = "timer",
121 .handler = mtu2_timer_interrupt,
122 .flags = SA_INTERRUPT,
123 .mask = CPU_MASK_NONE,
124};
125
126/*
127 * Hah! We'll see if this works (switching from usecs to nsecs).
128 */
129static unsigned long mtu2_timer_get_frequency(void)
130{
131 u32 freq;
132 struct timespec ts1, ts2;
133 unsigned long diff_nsec;
134 unsigned long factor;
135
136 /* Setup the timer: We don't want to generate interrupts, just
137 * have it count down at its natural rate.
138 */
139
140 ctrl_outb(ctrl_inb(MTU2_TSTR) & ~MTU2_TSTR_CST1, MTU2_TSTR);
141 ctrl_outb(MTU2_TCR_CALIB, MTU2_TCR_1);
142 ctrl_outb(ctrl_inb(MTU2_TIER_1) & ~MTU2_TIER_TGIEA, MTU2_TIER_1);
143 ctrl_outw(0, MTU2_TCNT_1);
144
145 rtc_get_time(&ts2);
146
147 do {
148 rtc_get_time(&ts1);
149 } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
150
151 /* actually start the timer */
152 ctrl_outw(ctrl_inw(CMT_CMSTR) | 0x01, CMT_CMSTR);
153
154 do {
155 rtc_get_time(&ts2);
156 } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
157
158 freq = ctrl_inw(MTU2_TCNT_0);
159 if (ts2.tv_nsec < ts1.tv_nsec) {
160 ts2.tv_nsec += 1000000000;
161 ts2.tv_sec--;
162 }
163
164 diff_nsec = (ts2.tv_sec - ts1.tv_sec) * 1000000000 + (ts2.tv_nsec - ts1.tv_nsec);
165
166 /* this should work well if the RTC has a precision of n Hz, where
167 * n is an integer. I don't think we have to worry about the other
168 * cases. */
169 factor = (1000000000 + diff_nsec/2) / diff_nsec;
170
171 if (factor * diff_nsec > 1100000000 ||
172 factor * diff_nsec < 900000000)
173 panic("weird RTC (diff_nsec %ld)", diff_nsec);
174
175 return freq * factor;
176}
177
178static unsigned int divisors[] = { 1, 4, 16, 64, 1, 1, 256 };
179
180static void mtu2_clk_init(struct clk *clk)
181{
182 u8 idx = MTU2_TCR_INIT & 0x7;
183
184 clk->rate = clk->parent->rate / divisors[idx];
185 /* Start TCNT counting */
186 ctrl_outb(ctrl_inb(MTU2_TSTR) | MTU2_TSTR_CST1, MTU2_TSTR);
187
188}
189
190static void mtu2_clk_recalc(struct clk *clk)
191{
192 u8 idx = ctrl_inb(MTU2_TCR_1) & 0x7;
193 clk->rate = clk->parent->rate / divisors[idx];
194}
195
196static struct clk_ops mtu2_clk_ops = {
197 .init = mtu2_clk_init,
198 .recalc = mtu2_clk_recalc,
199};
200
201static struct clk mtu2_clk1 = {
202 .name = "mtu2_clk1",
203 .ops = &mtu2_clk_ops,
204};
205
206static int mtu2_timer_start(void)
207{
208 ctrl_outb(ctrl_inb(MTU2_TSTR) | MTU2_TSTR_CST1, MTU2_TSTR);
209 return 0;
210}
211
212static int mtu2_timer_stop(void)
213{
214 ctrl_outb(ctrl_inb(MTU2_TSTR) & ~MTU2_TSTR_CST1, MTU2_TSTR);
215 return 0;
216}
217
218static int mtu2_timer_init(void)
219{
220 u8 tmp;
221 unsigned long interval;
222
223 setup_irq(TIMER_IRQ, &mtu2_irq);
224
225 mtu2_clk1.parent = clk_get("module_clk");
226
227 ctrl_outb(ctrl_inb(STBCR3) & (~0x20), STBCR3);
228
229 /* Normal operation */
230 ctrl_outb(0, MTU2_TMDR_1);
231 ctrl_outb(MTU2_TCR_INIT, MTU2_TCR_1);
232 ctrl_outb(0x01, MTU2_TIOR_1);
233
234 /* Enable underflow interrupt */
235 ctrl_outb(ctrl_inb(MTU2_TIER_1) | MTU2_TIER_TGIEA, MTU2_TIER_1);
236
237 interval = CONFIG_SH_PCLK_FREQ / 16 / HZ;
238 printk(KERN_INFO "Interval = %ld\n", interval);
239
240 ctrl_outw(interval, MTU2_TGRA_1);
241 ctrl_outw(0, MTU2_TCNT_1);
242
243 clk_register(&mtu2_clk1);
244 clk_enable(&mtu2_clk1);
245
246 return 0;
247}
248
249struct sys_timer_ops mtu2_timer_ops = {
250 .init = mtu2_timer_init,
251 .start = mtu2_timer_start,
252 .stop = mtu2_timer_stop,
253 .get_frequency = mtu2_timer_get_frequency,
254 .get_offset = mtu2_timer_get_offset,
255};
256
257struct sys_timer mtu2_timer = {
258 .name = "mtu2",
259 .ops = &mtu2_timer_ops,
260};
diff --git a/arch/sh/kernel/timers/timer.c b/arch/sh/kernel/timers/timer.c
index dc1f631053a8..a6bcc913d25e 100644
--- a/arch/sh/kernel/timers/timer.c
+++ b/arch/sh/kernel/timers/timer.c
@@ -17,6 +17,12 @@ static struct sys_timer *sys_timers[] __initdata = {
17#ifdef CONFIG_SH_TMU 17#ifdef CONFIG_SH_TMU
18 &tmu_timer, 18 &tmu_timer,
19#endif 19#endif
20#ifdef CONFIG_SH_MTU2
21 &mtu2_timer,
22#endif
23#ifdef CONFIG_SH_CMT
24 &cmt_timer,
25#endif
20 NULL, 26 NULL,
21}; 27};
22 28