summaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-s5p
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/plat-s5p')
-rw-r--r--arch/arm/plat-s5p/Kconfig23
-rw-r--r--arch/arm/plat-s5p/Makefile3
-rw-r--r--arch/arm/plat-s5p/cpu.c25
-rw-r--r--arch/arm/plat-s5p/dev-csis0.c2
-rw-r--r--arch/arm/plat-s5p/dev-csis1.c2
-rw-r--r--arch/arm/plat-s5p/dev-fimc3.c43
-rw-r--r--arch/arm/plat-s5p/include/plat/camport.h28
-rw-r--r--arch/arm/plat-s5p/include/plat/csis.h28
-rw-r--r--arch/arm/plat-s5p/include/plat/exynos4.h34
-rw-r--r--arch/arm/plat-s5p/include/plat/mipi_csis.h43
-rw-r--r--arch/arm/plat-s5p/include/plat/s5p-time.h40
-rw-r--r--arch/arm/plat-s5p/include/plat/s5pv310.h34
-rw-r--r--arch/arm/plat-s5p/include/plat/sysmmu.h95
-rw-r--r--arch/arm/plat-s5p/irq-gpioint.c170
-rw-r--r--arch/arm/plat-s5p/s5p-time.c448
-rw-r--r--arch/arm/plat-s5p/setup-mipiphy.c63
-rw-r--r--arch/arm/plat-s5p/sysmmu.c370
17 files changed, 1112 insertions, 339 deletions
diff --git a/arch/arm/plat-s5p/Kconfig b/arch/arm/plat-s5p/Kconfig
index 557f8c507f6..84922971658 100644
--- a/arch/arm/plat-s5p/Kconfig
+++ b/arch/arm/plat-s5p/Kconfig
@@ -7,10 +7,10 @@
config PLAT_S5P
bool
- depends on (ARCH_S5P64X0 || ARCH_S5P6442 || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_S5PV310)
+ depends on (ARCH_S5P64X0 || ARCH_S5P6442 || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS4)
default y
- select ARM_VIC if !ARCH_S5PV310
- select ARM_GIC if ARCH_S5PV310
+ select ARM_VIC if !ARCH_EXYNOS4
+ select ARM_GIC if ARCH_EXYNOS4
select NO_IOPORT
select ARCH_REQUIRE_GPIOLIB
select S3C_GPIO_TRACK
@@ -37,11 +37,16 @@ config S5P_GPIO_INT
help
Common code for the GPIO interrupts (other than external interrupts.)
+config S5P_HRT
+ bool
+ help
+ Use the High Resolution timer support
+
comment "System MMU"
config S5P_SYSTEM_MMU
bool "S5P SYSTEM MMU"
- depends on ARCH_S5PV310
+ depends on ARCH_EXYNOS4
help
Say Y here if you want to enable System MMU
@@ -60,6 +65,11 @@ config S5P_DEV_FIMC2
help
Compile in platform device definitions for FIMC controller 2
+config S5P_DEV_FIMC3
+ bool
+ help
+ Compile in platform device definitions for FIMC controller 3
+
config S5P_DEV_ONENAND
bool
help
@@ -74,3 +84,8 @@ config S5P_DEV_CSIS1
bool
help
Compile in platform device definitions for MIPI-CSIS channel 1
+
+config S5P_SETUP_MIPIPHY
+ bool
+ help
+ Compile in common setup code for MIPI-CSIS and MIPI-DSIM devices
diff --git a/arch/arm/plat-s5p/Makefile b/arch/arm/plat-s5p/Makefile
index 4bd5cf90897..42afff7f60b 100644
--- a/arch/arm/plat-s5p/Makefile
+++ b/arch/arm/plat-s5p/Makefile
@@ -22,12 +22,15 @@ obj-$(CONFIG_S5P_GPIO_INT) += irq-gpioint.o
obj-$(CONFIG_S5P_SYSTEM_MMU) += sysmmu.o
obj-$(CONFIG_PM) += pm.o
obj-$(CONFIG_PM) += irq-pm.o
+obj-$(CONFIG_S5P_HRT) += s5p-time.o
# devices
obj-$(CONFIG_S5P_DEV_FIMC0) += dev-fimc0.o
obj-$(CONFIG_S5P_DEV_FIMC1) += dev-fimc1.o
obj-$(CONFIG_S5P_DEV_FIMC2) += dev-fimc2.o
+obj-$(CONFIG_S5P_DEV_FIMC3) += dev-fimc3.o
obj-$(CONFIG_S5P_DEV_ONENAND) += dev-onenand.o
obj-$(CONFIG_S5P_DEV_CSIS0) += dev-csis0.o
obj-$(CONFIG_S5P_DEV_CSIS1) += dev-csis1.o
+obj-$(CONFIG_S5P_SETUP_MIPIPHY) += setup-mipiphy.o
diff --git a/arch/arm/plat-s5p/cpu.c b/arch/arm/plat-s5p/cpu.c
index 047d31c1bbd..c3bfe9b13ac 100644
--- a/arch/arm/plat-s5p/cpu.c
+++ b/arch/arm/plat-s5p/cpu.c
@@ -1,7 +1,7 @@
/* linux/arch/arm/plat-s5p/cpu.c
*
- * Copyright (c) 2009 Samsung Electronics Co., Ltd.
- * http://www.samsung.com/
+ * Copyright (c) 2009-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
*
* S5P CPU Support
*
@@ -12,17 +12,20 @@
#include <linux/init.h>
#include <linux/module.h>
-#include <mach/map.h>
+
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
+
+#include <mach/map.h>
#include <mach/regs-clock.h>
+
#include <plat/cpu.h>
#include <plat/s5p6440.h>
#include <plat/s5p6442.h>
#include <plat/s5p6450.h>
#include <plat/s5pc100.h>
#include <plat/s5pv210.h>
-#include <plat/s5pv310.h>
+#include <plat/exynos4.h>
/* table of supported CPUs */
@@ -31,7 +34,7 @@ static const char name_s5p6442[] = "S5P6442";
static const char name_s5p6450[] = "S5P6450";
static const char name_s5pc100[] = "S5PC100";
static const char name_s5pv210[] = "S5PV210/S5PC110";
-static const char name_s5pv310[] = "S5PV310";
+static const char name_exynos4210[] = "EXYNOS4210";
static struct cpu_table cpu_ids[] __initdata = {
{
@@ -75,13 +78,13 @@ static struct cpu_table cpu_ids[] __initdata = {
.init = s5pv210_init,
.name = name_s5pv210,
}, {
- .idcode = 0x43200000,
+ .idcode = 0x43210000,
.idmask = 0xfffff000,
- .map_io = s5pv310_map_io,
- .init_clocks = s5pv310_init_clocks,
- .init_uarts = s5pv310_init_uarts,
- .init = s5pv310_init,
- .name = name_s5pv310,
+ .map_io = exynos4_map_io,
+ .init_clocks = exynos4_init_clocks,
+ .init_uarts = exynos4_init_uarts,
+ .init = exynos4_init,
+ .name = name_exynos4210,
},
};
diff --git a/arch/arm/plat-s5p/dev-csis0.c b/arch/arm/plat-s5p/dev-csis0.c
index dfab1c85f54..e3aabef5e34 100644
--- a/arch/arm/plat-s5p/dev-csis0.c
+++ b/arch/arm/plat-s5p/dev-csis0.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Samsung Electronics
+ * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd.
*
* S5P series device definition for MIPI-CSIS channel 0
*
diff --git a/arch/arm/plat-s5p/dev-csis1.c b/arch/arm/plat-s5p/dev-csis1.c
index e3053f27fbb..08b91b58020 100644
--- a/arch/arm/plat-s5p/dev-csis1.c
+++ b/arch/arm/plat-s5p/dev-csis1.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Samsung Electronics
+ * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd.
*
* S5P series device definition for MIPI-CSIS channel 1
*
diff --git a/arch/arm/plat-s5p/dev-fimc3.c b/arch/arm/plat-s5p/dev-fimc3.c
new file mode 100644
index 00000000000..ef31beca386
--- /dev/null
+++ b/arch/arm/plat-s5p/dev-fimc3.c
@@ -0,0 +1,43 @@
+/* linux/arch/arm/plat-s5p/dev-fimc3.c
+ *
+ * Copyright (c) 2010 Samsung Electronics
+ *
+ * Base S5P FIMC3 resource and device definitions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <mach/map.h>
+
+static struct resource s5p_fimc3_resource[] = {
+ [0] = {
+ .start = S5P_PA_FIMC3,
+ .end = S5P_PA_FIMC3 + SZ_4K - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = IRQ_FIMC3,
+ .end = IRQ_FIMC3,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static u64 s5p_fimc3_dma_mask = DMA_BIT_MASK(32);
+
+struct platform_device s5p_device_fimc3 = {
+ .name = "s5p-fimc",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(s5p_fimc3_resource),
+ .resource = s5p_fimc3_resource,
+ .dev = {
+ .dma_mask = &s5p_fimc3_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+};
diff --git a/arch/arm/plat-s5p/include/plat/camport.h b/arch/arm/plat-s5p/include/plat/camport.h
new file mode 100644
index 00000000000..71688c8ba28
--- /dev/null
+++ b/arch/arm/plat-s5p/include/plat/camport.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ *
+ * S5P series camera interface helper functions
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef PLAT_S5P_CAMPORT_H_
+#define PLAT_S5P_CAMPORT_H_ __FILE__
+
+enum s5p_camport_id {
+ S5P_CAMPORT_A,
+ S5P_CAMPORT_B,
+};
+
+/*
+ * The helper functions to configure GPIO for the camera parallel bus.
+ * The camera port can be multiplexed with any FIMC entity, even multiple
+ * FIMC entities are allowed to be attached to a single port simultaneously.
+ * These functions are to be used in the board setup code.
+ */
+int s5pv210_fimc_setup_gpio(enum s5p_camport_id id);
+int exynos4_fimc_setup_gpio(enum s5p_camport_id id);
+
+#endif
diff --git a/arch/arm/plat-s5p/include/plat/csis.h b/arch/arm/plat-s5p/include/plat/csis.h
deleted file mode 100644
index 51e308c7981..00000000000
--- a/arch/arm/plat-s5p/include/plat/csis.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2010 Samsung Electronics
- *
- * S5P series MIPI CSI slave device support
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#ifndef PLAT_S5P_CSIS_H_
-#define PLAT_S5P_CSIS_H_ __FILE__
-
-/**
- * struct s5p_platform_mipi_csis - platform data for MIPI-CSIS
- * @clk_rate: bus clock frequency
- * @lanes: number of data lanes used
- * @alignment: data alignment in bits
- * @hs_settle: HS-RX settle time
- */
-struct s5p_platform_mipi_csis {
- unsigned long clk_rate;
- u8 lanes;
- u8 alignment;
- u8 hs_settle;
-};
-
-#endif /* PLAT_S5P_CSIS_H_ */
diff --git a/arch/arm/plat-s5p/include/plat/exynos4.h b/arch/arm/plat-s5p/include/plat/exynos4.h
new file mode 100644
index 00000000000..907caab53dc
--- /dev/null
+++ b/arch/arm/plat-s5p/include/plat/exynos4.h
@@ -0,0 +1,34 @@
+/* linux/arch/arm/plat-s5p/include/plat/exynos4.h
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Header file for exynos4 cpu support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+/* Common init code for EXYNOS4 related SoCs */
+
+extern void exynos4_common_init_uarts(struct s3c2410_uartcfg *cfg, int no);
+extern void exynos4_register_clocks(void);
+extern void exynos4_setup_clocks(void);
+
+#ifdef CONFIG_CPU_EXYNOS4210
+
+extern int exynos4_init(void);
+extern void exynos4_init_irq(void);
+extern void exynos4_map_io(void);
+extern void exynos4_init_clocks(int xtal);
+extern struct sys_timer exynos4_timer;
+
+#define exynos4_init_uarts exynos4_common_init_uarts
+
+#else
+#define exynos4_init_clocks NULL
+#define exynos4_init_uarts NULL
+#define exynos4_map_io NULL
+#define exynos4_init NULL
+#endif
diff --git a/arch/arm/plat-s5p/include/plat/mipi_csis.h b/arch/arm/plat-s5p/include/plat/mipi_csis.h
new file mode 100644
index 00000000000..9bd254c5ed2
--- /dev/null
+++ b/arch/arm/plat-s5p/include/plat/mipi_csis.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd.
+ *
+ * S5P series MIPI CSI slave device support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef PLAT_S5P_MIPI_CSIS_H_
+#define PLAT_S5P_MIPI_CSIS_H_ __FILE__
+
+struct platform_device;
+
+/**
+ * struct s5p_platform_mipi_csis - platform data for S5P MIPI-CSIS driver
+ * @clk_rate: bus clock frequency
+ * @lanes: number of data lanes used
+ * @alignment: data alignment in bits
+ * @hs_settle: HS-RX settle time
+ * @fixed_phy_vdd: false to enable external D-PHY regulator management in the
+ * driver or true in case this regulator has no enable function
+ * @phy_enable: pointer to a callback controlling D-PHY enable/reset
+ */
+struct s5p_platform_mipi_csis {
+ unsigned long clk_rate;
+ u8 lanes;
+ u8 alignment;
+ u8 hs_settle;
+ bool fixed_phy_vdd;
+ int (*phy_enable)(struct platform_device *pdev, bool on);
+};
+
+/**
+ * s5p_csis_phy_enable - global MIPI-CSI receiver D-PHY control
+ * @pdev: MIPI-CSIS platform device
+ * @on: true to enable D-PHY and deassert its reset
+ * false to disable D-PHY
+ */
+int s5p_csis_phy_enable(struct platform_device *pdev, bool on);
+
+#endif /* PLAT_S5P_MIPI_CSIS_H_ */
diff --git a/arch/arm/plat-s5p/include/plat/s5p-time.h b/arch/arm/plat-s5p/include/plat/s5p-time.h
new file mode 100644
index 00000000000..575e88109db
--- /dev/null
+++ b/arch/arm/plat-s5p/include/plat/s5p-time.h
@@ -0,0 +1,40 @@
+/* linux/arch/arm/plat-s5p/include/plat/s5p-time.h
+ *
+ * Copyright 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Header file for s5p time support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#ifndef __ASM_PLAT_S5P_TIME_H
+#define __ASM_PLAT_S5P_TIME_H __FILE__
+
+/* S5P HR-Timer Clock mode */
+enum s5p_timer_mode {
+ S5P_PWM0,
+ S5P_PWM1,
+ S5P_PWM2,
+ S5P_PWM3,
+ S5P_PWM4,
+};
+
+struct s5p_timer_source {
+ unsigned int event_id;
+ unsigned int source_id;
+};
+
+/* Be able to sleep for atleast 4 seconds (usually more) */
+#define S5PTIMER_MIN_RANGE 4
+
+#define TCNT_MAX 0xffffffff
+#define NON_PERIODIC 0
+#define PERIODIC 1
+
+extern void __init s5p_set_timer_source(enum s5p_timer_mode event,
+ enum s5p_timer_mode source);
+extern struct sys_timer s5p_timer;
+#endif /* __ASM_PLAT_S5P_TIME_H */
diff --git a/arch/arm/plat-s5p/include/plat/s5pv310.h b/arch/arm/plat-s5p/include/plat/s5pv310.h
deleted file mode 100644
index 769c991ceb3..00000000000
--- a/arch/arm/plat-s5p/include/plat/s5pv310.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* linux/arch/arm/plat-s5p/include/plat/s5pv310.h
- *
- * Copyright (c) 2010 Samsung Electronics Co., Ltd.
- * http://www.samsung.com/
- *
- * Header file for s5pv310 cpu support
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
-*/
-
-/* Common init code for S5PV310 related SoCs */
-
-extern void s5pv310_common_init_uarts(struct s3c2410_uartcfg *cfg, int no);
-extern void s5pv310_register_clocks(void);
-extern void s5pv310_setup_clocks(void);
-
-#ifdef CONFIG_CPU_S5PV310
-
-extern int s5pv310_init(void);
-extern void s5pv310_init_irq(void);
-extern void s5pv310_map_io(void);
-extern void s5pv310_init_clocks(int xtal);
-extern struct sys_timer s5pv310_timer;
-
-#define s5pv310_init_uarts s5pv310_common_init_uarts
-
-#else
-#define s5pv310_init_clocks NULL
-#define s5pv310_init_uarts NULL
-#define s5pv310_map_io NULL
-#define s5pv310_init NULL
-#endif
diff --git a/arch/arm/plat-s5p/include/plat/sysmmu.h b/arch/arm/plat-s5p/include/plat/sysmmu.h
new file mode 100644
index 00000000000..bf5283c2a19
--- /dev/null
+++ b/arch/arm/plat-s5p/include/plat/sysmmu.h
@@ -0,0 +1,95 @@
+/* linux/arch/arm/plat-s5p/include/plat/sysmmu.h
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Samsung System MMU driver for S5P platform
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#ifndef __ASM__PLAT_SYSMMU_H
+#define __ASM__PLAT_SYSMMU_H __FILE__
+
+enum S5P_SYSMMU_INTERRUPT_TYPE {
+ SYSMMU_PAGEFAULT,
+ SYSMMU_AR_MULTIHIT,
+ SYSMMU_AW_MULTIHIT,
+ SYSMMU_BUSERROR,
+ SYSMMU_AR_SECURITY,
+ SYSMMU_AR_ACCESS,
+ SYSMMU_AW_SECURITY,
+ SYSMMU_AW_PROTECTION, /* 7 */
+ SYSMMU_FAULTS_NUM
+};
+
+#ifdef CONFIG_S5P_SYSTEM_MMU
+
+#include <mach/sysmmu.h>
+
+/**
+ * s5p_sysmmu_enable() - enable system mmu of ip
+ * @ips: The ip connected system mmu.
+ * #pgd: Base physical address of the 1st level page table
+ *
+ * This function enable system mmu to transfer address
+ * from virtual address to physical address
+ */
+void s5p_sysmmu_enable(sysmmu_ips ips, unsigned long pgd);
+
+/**
+ * s5p_sysmmu_disable() - disable sysmmu mmu of ip
+ * @ips: The ip connected system mmu.
+ *
+ * This function disable system mmu to transfer address
+ * from virtual address to physical address
+ */
+void s5p_sysmmu_disable(sysmmu_ips ips);
+
+/**
+ * s5p_sysmmu_set_tablebase_pgd() - set page table base address to refer page table
+ * @ips: The ip connected system mmu.
+ * @pgd: The page table base address.
+ *
+ * This function set page table base address
+ * When system mmu transfer address from virtaul address to physical address,
+ * system mmu refer address information from page table
+ */
+void s5p_sysmmu_set_tablebase_pgd(sysmmu_ips ips, unsigned long pgd);
+
+/**
+ * s5p_sysmmu_tlb_invalidate() - flush all TLB entry in system mmu
+ * @ips: The ip connected system mmu.
+ *
+ * This function flush all TLB entry in system mmu
+ */
+void s5p_sysmmu_tlb_invalidate(sysmmu_ips ips);
+
+/** s5p_sysmmu_set_fault_handler() - Fault handler for System MMUs
+ * @itype: type of fault.
+ * @pgtable_base: the physical address of page table base. This is 0 if @ips is
+ * SYSMMU_BUSERROR.
+ * @fault_addr: the device (virtual) address that the System MMU tried to
+ * translated. This is 0 if @ips is SYSMMU_BUSERROR.
+ * Called when interrupt occurred by the System MMUs
+ * The device drivers of peripheral devices that has a System MMU can implement
+ * a fault handler to resolve address translation fault by System MMU.
+ * The meanings of return value and parameters are described below.
+
+ * return value: non-zero if the fault is correctly resolved.
+ * zero if the fault is not handled.
+ */
+void s5p_sysmmu_set_fault_handler(sysmmu_ips ips,
+ int (*handler)(enum S5P_SYSMMU_INTERRUPT_TYPE itype,
+ unsigned long pgtable_base,
+ unsigned long fault_addr));
+#else
+#define s5p_sysmmu_enable(ips, pgd) do { } while (0)
+#define s5p_sysmmu_disable(ips) do { } while (0)
+#define s5p_sysmmu_set_tablebase_pgd(ips, pgd) do { } while (0)
+#define s5p_sysmmu_tlb_invalidate(ips) do { } while (0)
+#define s5p_sysmmu_set_fault_handler(ips, handler) do { } while (0)
+#endif
+#endif /* __ASM_PLAT_SYSMMU_H */
diff --git a/arch/arm/plat-s5p/irq-gpioint.c b/arch/arm/plat-s5p/irq-gpioint.c
index 3b6bf89d173..cd87d3256e0 100644
--- a/arch/arm/plat-s5p/irq-gpioint.c
+++ b/arch/arm/plat-s5p/irq-gpioint.c
@@ -17,82 +17,79 @@
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/gpio.h>
+#include <linux/slab.h>
#include <mach/map.h>
#include <plat/gpio-core.h>
#include <plat/gpio-cfg.h>
-#define S5P_GPIOREG(x) (S5P_VA_GPIO + (x))
+#define GPIO_BASE(chip) (((unsigned long)(chip)->base) & 0xFFFFF000u)
-#define GPIOINT_CON_OFFSET 0x700
-#define GPIOINT_MASK_OFFSET 0x900
-#define GPIOINT_PEND_OFFSET 0xA00
+#define CON_OFFSET 0x700
+#define MASK_OFFSET 0x900
+#define PEND_OFFSET 0xA00
+#define REG_OFFSET(x) ((x) << 2)
-static struct s3c_gpio_chip *irq_chips[S5P_GPIOINT_GROUP_MAXNR];
-
-static int s5p_gpioint_get_group(struct irq_data *data)
-{
- struct gpio_chip *chip = irq_data_get_irq_data(data);
- struct s3c_gpio_chip *s3c_chip = container_of(chip,
- struct s3c_gpio_chip, chip);
- int group;
-
- for (group = 0; group < S5P_GPIOINT_GROUP_MAXNR; group++)
- if (s3c_chip == irq_chips[group])
- break;
+struct s5p_gpioint_bank {
+ struct list_head list;
+ int start;
+ int nr_groups;
+ int irq;
+ struct s3c_gpio_chip **chips;
+ void (*handler)(unsigned int, struct irq_desc *);
+};
- return group;
-}
+LIST_HEAD(banks);
static int s5p_gpioint_get_offset(struct irq_data *data)
{
- struct gpio_chip *chip = irq_data_get_irq_data(data);
- struct s3c_gpio_chip *s3c_chip = container_of(chip,
- struct s3c_gpio_chip, chip);
-
- return data->irq - s3c_chip->irq_base;
+ struct s3c_gpio_chip *chip = irq_data_get_irq_data(data);
+ return data->irq - chip->irq_base;
}
static void s5p_gpioint_ack(struct irq_data *data)
{
+ struct s3c_gpio_chip *chip = irq_data_get_irq_data(data);
int group, offset, pend_offset;
unsigned int value;
- group = s5p_gpioint_get_group(data);
+ group = chip->group;
offset = s5p_gpioint_get_offset(data);
- pend_offset = group << 2;
+ pend_offset = REG_OFFSET(group);
- value = __raw_readl(S5P_GPIOREG(GPIOINT_PEND_OFFSET) + pend_offset);
- value |= 1 << offset;
- __raw_writel(value, S5P_GPIOREG(GPIOINT_PEND_OFFSET) + pend_offset);
+ value = __raw_readl(GPIO_BASE(chip) + PEND_OFFSET + pend_offset);
+ value |= BIT(offset);
+ __raw_writel(value, GPIO_BASE(chip) + PEND_OFFSET + pend_offset);
}
static void s5p_gpioint_mask(struct irq_data *data)
{
+ struct s3c_gpio_chip *chip = irq_data_get_irq_data(data);
int group, offset, mask_offset;
unsigned int value;
- group = s5p_gpioint_get_group(data);
+ group = chip->group;
offset = s5p_gpioint_get_offset(data);
- mask_offset = group << 2;
+ mask_offset = REG_OFFSET(group);
- value = __raw_readl(S5P_GPIOREG(GPIOINT_MASK_OFFSET) + mask_offset);
- value |= 1 << offset;
- __raw_writel(value, S5P_GPIOREG(GPIOINT_MASK_OFFSET) + mask_offset);
+ value = __raw_readl(GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
+ value |= BIT(offset);
+ __raw_writel(value, GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
}
static void s5p_gpioint_unmask(struct irq_data *data)
{
+ struct s3c_gpio_chip *chip = irq_data_get_irq_data(data);
int group, offset, mask_offset;
unsigned int value;
- group = s5p_gpioint_get_group(data);
+ group = chip->group;
offset = s5p_gpioint_get_offset(data);
- mask_offset = group << 2;
+ mask_offset = REG_OFFSET(group);
- value = __raw_readl(S5P_GPIOREG(GPIOINT_MASK_OFFSET) + mask_offset);
- value &= ~(1 << offset);
- __raw_writel(value, S5P_GPIOREG(GPIOINT_MASK_OFFSET) + mask_offset);
+ value = __raw_readl(GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
+ value &= ~BIT(offset);
+ __raw_writel(value, GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
}
static void s5p_gpioint_mask_ack(struct irq_data *data)
@@ -103,12 +100,13 @@ static void s5p_gpioint_mask_ack(struct irq_data *data)
static int s5p_gpioint_set_type(struct irq_data *data, unsigned int type)
{
+ struct s3c_gpio_chip *chip = irq_data_get_irq_data(data);
int group, offset, con_offset;
unsigned int value;
- group = s5p_gpioint_get_group(data);
+ group = chip->group;
offset = s5p_gpioint_get_offset(data);
- con_offset = group << 2;
+ con_offset = REG_OFFSET(group);
switch (type) {
case IRQ_TYPE_EDGE_RISING:
@@ -132,15 +130,15 @@ static int s5p_gpioint_set_type(struct irq_data *data, unsigned int type)
return -EINVAL;
}
- value = __raw_readl(S5P_GPIOREG(GPIOINT_CON_OFFSET) + con_offset);
+ value = __raw_readl(GPIO_BASE(chip) + CON_OFFSET + con_offset);
value &= ~(0x7 << (offset * 0x4));
value |= (type << (offset * 0x4));
- __raw_writel(value, S5P_GPIOREG(GPIOINT_CON_OFFSET) + con_offset);
+ __raw_writel(value, GPIO_BASE(chip) + CON_OFFSET + con_offset);
return 0;
}
-struct irq_chip s5p_gpioint = {
+static struct irq_chip s5p_gpioint = {
.name = "s5p_gpioint",
.irq_ack = s5p_gpioint_ack,
.irq_mask = s5p_gpioint_mask,
@@ -151,30 +149,29 @@ struct irq_chip s5p_gpioint = {
static void s5p_gpioint_handler(unsigned int irq, struct irq_desc *desc)
{
- int group, offset, pend_offset, mask_offset;
- int real_irq;
+ struct s5p_gpioint_bank *bank = get_irq_data(irq);
+ int group, pend_offset, mask_offset;
unsigned int pend, mask;
- for (group = 0; group < S5P_GPIOINT_GROUP_MAXNR; group++) {
- pend_offset = group << 2;
- pend = __raw_readl(S5P_GPIOREG(GPIOINT_PEND_OFFSET) +
- pend_offset);
+ for (group = 0; group < bank->nr_groups; group++) {
+ struct s3c_gpio_chip *chip = bank->chips[group];
+ if (!chip)
+ continue;
+
+ pend_offset = REG_OFFSET(group);
+ pend = __raw_readl(GPIO_BASE(chip) + PEND_OFFSET + pend_offset);
if (!pend)
continue;
- mask_offset = group << 2;
- mask = __raw_readl(S5P_GPIOREG(GPIOINT_MASK_OFFSET) +
- mask_offset);
+ mask_offset = REG_OFFSET(group);
+ mask = __raw_readl(GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
pend &= ~mask;
- for (offset = 0; offset < 8; offset++) {
- if (pend & (1 << offset)) {
- struct s3c_gpio_chip *chip = irq_chips[group];
- if (chip) {
- real_irq = chip->irq_base + offset;
- generic_handle_irq(real_irq);
- }
- }
+ while (pend) {
+ int offset = fls(pend) - 1;
+ int real_irq = chip->irq_base + offset;
+ generic_handle_irq(real_irq);
+ pend &= ~BIT(offset);
}
}
}
@@ -182,27 +179,48 @@ static void s5p_gpioint_handler(unsigned int irq, struct irq_desc *desc)
static __init int s5p_gpioint_add(struct s3c_gpio_chip *chip)
{
static int used_gpioint_groups = 0;
- static bool handler_registered = 0;
int irq, group = chip->group;
int i;
+ struct s5p_gpioint_bank *bank = NULL;
if (used_gpioint_groups >= S5P_GPIOINT_GROUP_COUNT)
return -ENOMEM;
+ list_for_each_entry(bank, &banks, list) {
+ if (group >= bank->start &&
+ group < bank->start + bank->nr_groups)
+ break;
+ }
+ if (!bank)
+ return -EINVAL;
+
+ if (!bank->handler) {
+ bank->chips = kzalloc(sizeof(struct s3c_gpio_chip *) *
+ bank->nr_groups, GFP_KERNEL);
+ if (!bank->chips)
+ return -ENOMEM;
+
+ set_irq_chained_handler(bank->irq, s5p_gpioint_handler);
+ set_irq_data(bank->irq, bank);
+ bank->handler = s5p_gpioint_handler;
+ printk(KERN_INFO "Registered chained gpio int handler for interrupt %d.\n",
+ bank->irq);
+ }
+
+ /*
+ * chained GPIO irq has been sucessfully registered, allocate new gpio
+ * int group and assign irq nubmers
+ */
+
chip->irq_base = S5P_GPIOINT_BASE +
used_gpioint_groups * S5P_GPIOINT_GROUP_SIZE;
used_gpioint_groups++;
- if (!handler_registered) {
- set_irq_chained_handler(IRQ_GPIOINT, s5p_gpioint_handler);
- handler_registered = 1;
- }
-
- irq_chips[group] = chip;
+ bank->chips[group - bank->start] = chip;
for (i = 0; i < chip->chip.ngpio; i++) {
irq = chip->irq_base + i;
set_irq_chip(irq, &s5p_gpioint);
- set_irq_data(irq, &chip->chip);
+ set_irq_data(irq, chip);
set_irq_handler(irq, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
}
@@ -235,3 +253,19 @@ int __init s5p_register_gpio_interrupt(int pin)
}
return ret;
}
+
+int __init s5p_register_gpioint_bank(int chain_irq, int start, int nr_groups)
+{
+ struct s5p_gpioint_bank *bank;
+
+ bank = kzalloc(sizeof(*bank), GFP_KERNEL);
+ if (!bank)
+ return -ENOMEM;
+
+ bank->start = start;
+ bank->nr_groups = nr_groups;
+ bank->irq = chain_irq;
+
+ list_add_tail(&bank->list, &banks);
+ return 0;
+}
diff --git a/arch/arm/plat-s5p/s5p-time.c b/arch/arm/plat-s5p/s5p-time.c
new file mode 100644
index 00000000000..8090403eec0
--- /dev/null
+++ b/arch/arm/plat-s5p/s5p-time.c
@@ -0,0 +1,448 @@
+/* linux/arch/arm/plat-s5p/s5p-time.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * S5P - Common hr-timer support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+
+#include <asm/smp_twd.h>
+#include <asm/mach/time.h>
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/sched_clock.h>
+
+#include <mach/map.h>
+#include <plat/devs.h>
+#include <plat/regs-timer.h>
+#include <plat/s5p-time.h>
+
+static struct clk *tin_event;
+static struct clk *tin_source;
+static struct clk *tdiv_event;
+static struct clk *tdiv_source;
+static struct clk *timerclk;
+static struct s5p_timer_source timer_source;
+static unsigned long clock_count_per_tick;
+static void s5p_timer_resume(void);
+
+static void s5p_time_stop(enum s5p_timer_mode mode)
+{
+ unsigned long tcon;
+
+ tcon = __raw_readl(S3C2410_TCON);
+
+ switch (mode) {
+ case S5P_PWM0:
+ tcon &= ~S3C2410_TCON_T0START;
+ break;
+
+ case S5P_PWM1:
+ tcon &= ~S3C2410_TCON_T1START;
+ break;
+
+ case S5P_PWM2:
+ tcon &= ~S3C2410_TCON_T2START;
+ break;
+
+ case S5P_PWM3:
+ tcon &= ~S3C2410_TCON_T3START;
+ break;
+
+ case S5P_PWM4:
+ tcon &= ~S3C2410_TCON_T4START;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", mode);
+ break;
+ }
+ __raw_writel(tcon, S3C2410_TCON);
+}
+
+static void s5p_time_setup(enum s5p_timer_mode mode, unsigned long tcnt)
+{
+ unsigned long tcon;
+
+ tcon = __raw_readl(S3C2410_TCON);
+
+ tcnt--;
+
+ switch (mode) {
+ case S5P_PWM0:
+ tcon &= ~(0x0f << 0);
+ tcon |= S3C2410_TCON_T0MANUALUPD;
+ break;
+
+ case S5P_PWM1:
+ tcon &= ~(0x0f << 8);
+ tcon |= S3C2410_TCON_T1MANUALUPD;
+ break;
+
+ case S5P_PWM2:
+ tcon &= ~(0x0f << 12);
+ tcon |= S3C2410_TCON_T2MANUALUPD;
+ break;
+
+ case S5P_PWM3:
+ tcon &= ~(0x0f << 16);
+ tcon |= S3C2410_TCON_T3MANUALUPD;
+ break;
+
+ case S5P_PWM4:
+ tcon &= ~(0x07 << 20);
+ tcon |= S3C2410_TCON_T4MANUALUPD;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", mode);
+ break;
+ }
+
+ __raw_writel(tcnt, S3C2410_TCNTB(mode));
+ __raw_writel(tcnt, S3C2410_TCMPB(mode));
+ __raw_writel(tcon, S3C2410_TCON);
+}
+
+static void s5p_time_start(enum s5p_timer_mode mode, bool periodic)
+{
+ unsigned long tcon;
+
+ tcon = __raw_readl(S3C2410_TCON);
+
+ switch (mode) {
+ case S5P_PWM0:
+ tcon |= S3C2410_TCON_T0START;
+ tcon &= ~S3C2410_TCON_T0MANUALUPD;
+
+ if (periodic)
+ tcon |= S3C2410_TCON_T0RELOAD;
+ else
+ tcon &= ~S3C2410_TCON_T0RELOAD;
+ break;
+
+ case S5P_PWM1:
+ tcon |= S3C2410_TCON_T1START;
+ tcon &= ~S3C2410_TCON_T1MANUALUPD;
+
+ if (periodic)
+ tcon |= S3C2410_TCON_T1RELOAD;
+ else
+ tcon &= ~S3C2410_TCON_T1RELOAD;
+ break;
+
+ case S5P_PWM2:
+ tcon |= S3C2410_TCON_T2START;
+ tcon &= ~S3C2410_TCON_T2MANUALUPD;
+
+ if (periodic)
+ tcon |= S3C2410_TCON_T2RELOAD;
+ else
+ tcon &= ~S3C2410_TCON_T2RELOAD;
+ break;
+
+ case S5P_PWM3:
+ tcon |= S3C2410_TCON_T3START;
+ tcon &= ~S3C2410_TCON_T3MANUALUPD;
+
+ if (periodic)
+ tcon |= S3C2410_TCON_T3RELOAD;
+ else
+ tcon &= ~S3C2410_TCON_T3RELOAD;
+ break;
+
+ case S5P_PWM4:
+ tcon |= S3C2410_TCON_T4START;
+ tcon &= ~S3C2410_TCON_T4MANUALUPD;
+
+ if (periodic)
+ tcon |= S3C2410_TCON_T4RELOAD;
+ else
+ tcon &= ~S3C2410_TCON_T4RELOAD;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", mode);
+ break;
+ }
+ __raw_writel(tcon, S3C2410_TCON);
+}
+
+static int s5p_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ s5p_time_setup(timer_source.event_id, cycles);
+ s5p_time_start(timer_source.event_id, NON_PERIODIC);
+
+ return 0;
+}
+
+static void s5p_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *evt)
+{
+ s5p_time_stop(timer_source.event_id);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ s5p_time_setup(timer_source.event_id, clock_count_per_tick);
+ s5p_time_start(timer_source.event_id, PERIODIC);
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ break;
+
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ break;
+
+ case CLOCK_EVT_MODE_RESUME:
+ s5p_timer_resume();
+ break;
+ }
+}
+
+static void s5p_timer_resume(void)
+{
+ /* event timer restart */
+ s5p_time_setup(timer_source.event_id, clock_count_per_tick);
+ s5p_time_start(timer_source.event_id, PERIODIC);
+
+ /* source timer restart */
+ s5p_time_setup(timer_source.source_id, TCNT_MAX);
+ s5p_time_start(timer_source.source_id, PERIODIC);
+}
+
+void __init s5p_set_timer_source(enum s5p_timer_mode event,
+ enum s5p_timer_mode source)
+{
+ s3c_device_timer[event].dev.bus = &platform_bus_type;
+ s3c_device_timer[source].dev.bus = &platform_bus_type;
+
+ timer_source.event_id = event;
+ timer_source.source_id = source;
+}
+
+static struct clock_event_device time_event_device = {
+ .name = "s5p_event_timer",
+ .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+ .rating = 200,
+ .set_next_event = s5p_set_next_event,
+ .set_mode = s5p_set_mode,
+};
+
+static irqreturn_t s5p_clock_event_isr(int irq, void *dev_id)
+{
+ struct clock_event_device *evt = dev_id;
+
+ evt->event_handler(evt);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction s5p_clock_event_irq = {
+ .name = "s5p_time_irq",
+ .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+ .handler = s5p_clock_event_isr,
+ .dev_id = &time_event_device,
+};
+
+static void __init s5p_clockevent_init(void)
+{
+ unsigned long pclk;
+ unsigned long clock_rate;
+ unsigned int irq_number;
+ struct clk *tscaler;
+
+ pclk = clk_get_rate(timerclk);
+
+ tscaler = clk_get_parent(tdiv_event);
+
+ clk_set_rate(tscaler, pclk / 2);
+ clk_set_rate(tdiv_event, pclk / 2);
+ clk_set_parent(tin_event, tdiv_event);
+
+ clock_rate = clk_get_rate(tin_event);
+ clock_count_per_tick = clock_rate / HZ;
+
+ clockevents_calc_mult_shift(&time_event_device,
+ clock_rate, S5PTIMER_MIN_RANGE);
+ time_event_device.max_delta_ns =
+ clockevent_delta2ns(-1, &time_event_device);
+ time_event_device.min_delta_ns =
+ clockevent_delta2ns(1, &time_event_device);
+
+ time_event_device.cpumask = cpumask_of(0);
+ clockevents_register_device(&time_event_device);
+
+ irq_number = timer_source.event_id + IRQ_TIMER0;
+ setup_irq(irq_number, &s5p_clock_event_irq);
+}
+
+static cycle_t s5p_timer_read(struct clocksource *cs)
+{
+ unsigned long offset = 0;
+
+ switch (timer_source.source_id) {
+ case S5P_PWM0:
+ case S5P_PWM1:
+ case S5P_PWM2:
+ case S5P_PWM3:
+ offset = (timer_source.source_id * 0x0c) + 0x14;
+ break;
+
+ case S5P_PWM4:
+ offset = 0x40;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
+ return 0;
+ }
+
+ return (cycle_t) ~__raw_readl(S3C_TIMERREG(offset));
+}
+
+/*
+ * Override the global weak sched_clock symbol with this
+ * local implementation which uses the clocksource to get some
+ * better resolution when scheduling the kernel. We accept that
+ * this wraps around for now, since it is just a relative time
+ * stamp. (Inspired by U300 implementation.)
+ */
+static DEFINE_CLOCK_DATA(cd);
+
+unsigned long long notrace sched_clock(void)
+{
+ u32 cyc;
+ unsigned long offset = 0;
+
+ switch (timer_source.source_id) {
+ case S5P_PWM0:
+ case S5P_PWM1:
+ case S5P_PWM2:
+ case S5P_PWM3:
+ offset = (timer_source.source_id * 0x0c) + 0x14;
+ break;
+
+ case S5P_PWM4:
+ offset = 0x40;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
+ return 0;
+ }
+
+ cyc = ~__raw_readl(S3C_TIMERREG(offset));
+ return cyc_to_sched_clock(&cd, cyc, (u32)~0);
+}
+
+static void notrace s5p_update_sched_clock(void)
+{
+ u32 cyc;
+ unsigned long offset = 0;
+
+ switch (timer_source.source_id) {
+ case S5P_PWM0:
+ case S5P_PWM1:
+ case S5P_PWM2:
+ case S5P_PWM3:
+ offset = (timer_source.source_id * 0x0c) + 0x14;
+ break;
+
+ case S5P_PWM4:
+ offset = 0x40;
+ break;
+
+ default:
+ printk(KERN_ERR "Invalid Timer %d\n", timer_source.source_id);
+ }
+
+ cyc = ~__raw_readl(S3C_TIMERREG(offset));
+ update_sched_clock(&cd, cyc, (u32)~0);
+}
+
+struct clocksource time_clocksource = {
+ .name = "s5p_clocksource_timer",
+ .rating = 250,
+ .read = s5p_timer_read,
+ .mask = CLOCKSOURCE_MASK(32),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static void __init s5p_clocksource_init(void)
+{
+ unsigned long pclk;
+ unsigned long clock_rate;
+
+ pclk = clk_get_rate(timerclk);
+
+ clk_set_rate(tdiv_source, pclk / 2);
+ clk_set_parent(tin_source, tdiv_source);
+
+ clock_rate = clk_get_rate(tin_source);
+
+ init_sched_clock(&cd, s5p_update_sched_clock, 32, clock_rate);
+
+ s5p_time_setup(timer_source.source_id, TCNT_MAX);
+ s5p_time_start(timer_source.source_id, PERIODIC);
+
+ if (clocksource_register_hz(&time_clocksource, clock_rate))
+ panic("%s: can't register clocksource\n", time_clocksource.name);
+}
+
+static void __init s5p_timer_resources(void)
+{
+
+ unsigned long event_id = timer_source.event_id;
+ unsigned long source_id = timer_source.source_id;
+
+ timerclk = clk_get(NULL, "timers");
+ if (IS_ERR(timerclk))
+ panic("failed to get timers clock for timer");
+
+ clk_enable(timerclk);
+
+ tin_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tin");
+ if (IS_ERR(tin_event))
+ panic("failed to get pwm-tin clock for event timer");
+
+ tdiv_event = clk_get(&s3c_device_timer[event_id].dev, "pwm-tdiv");
+ if (IS_ERR(tdiv_event))
+ panic("failed to get pwm-tdiv clock for event timer");
+
+ clk_enable(tin_event);
+
+ tin_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tin");
+ if (IS_ERR(tin_source))
+ panic("failed to get pwm-tin clock for source timer");
+
+ tdiv_source = clk_get(&s3c_device_timer[source_id].dev, "pwm-tdiv");
+ if (IS_ERR(tdiv_source))
+ panic("failed to get pwm-tdiv clock for source timer");
+
+ clk_enable(tin_source);
+}
+
+static void __init s5p_timer_init(void)
+{
+ s5p_timer_resources();
+ s5p_clockevent_init();
+ s5p_clocksource_init();
+}
+
+struct sys_timer s5p_timer = {
+ .init = s5p_timer_init,
+};
diff --git a/arch/arm/plat-s5p/setup-mipiphy.c b/arch/arm/plat-s5p/setup-mipiphy.c
new file mode 100644
index 00000000000..683c466c0e6
--- /dev/null
+++ b/arch/arm/plat-s5p/setup-mipiphy.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ *
+ * S5P - Helper functions for MIPI-CSIS and MIPI-DSIM D-PHY control
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <mach/regs-clock.h>
+
+static int __s5p_mipi_phy_control(struct platform_device *pdev,
+ bool on, u32 reset)
+{
+ static DEFINE_SPINLOCK(lock);
+ void __iomem *addr;
+ unsigned long flags;
+ int pid;
+ u32 cfg;
+
+ if (!pdev)
+ return -EINVAL;
+
+ pid = (pdev->id == -1) ? 0 : pdev->id;
+
+ if (pid != 0 && pid != 1)
+ return -EINVAL;
+
+ addr = S5P_MIPI_DPHY_CONTROL(pid);
+
+ spin_lock_irqsave(&lock, flags);
+
+ cfg = __raw_readl(addr);
+ cfg = on ? (cfg | reset) : (cfg & ~reset);
+ __raw_writel(cfg, addr);
+
+ if (on) {
+ cfg |= S5P_MIPI_DPHY_ENABLE;
+ } else if (!(cfg & (S5P_MIPI_DPHY_SRESETN |
+ S5P_MIPI_DPHY_MRESETN) & ~reset)) {
+ cfg &= ~S5P_MIPI_DPHY_ENABLE;
+ }
+
+ __raw_writel(cfg, addr);
+ spin_unlock_irqrestore(&lock, flags);
+
+ return 0;
+}
+
+int s5p_csis_phy_enable(struct platform_device *pdev, bool on)
+{
+ return __s5p_mipi_phy_control(pdev, on, S5P_MIPI_DPHY_SRESETN);
+}
+
+int s5p_dsim_phy_enable(struct platform_device *pdev, bool on)
+{
+ return __s5p_mipi_phy_control(pdev, on, S5P_MIPI_DPHY_MRESETN);
+}
diff --git a/arch/arm/plat-s5p/sysmmu.c b/arch/arm/plat-s5p/sysmmu.c
index ffe8a48bc3c..54f5eddc921 100644
--- a/arch/arm/plat-s5p/sysmmu.c
+++ b/arch/arm/plat-s5p/sysmmu.c
@@ -12,280 +12,266 @@
#include <linux/interrupt.h>
#include <linux/platform_device.h>
+#include <asm/pgtable.h>
+
#include <mach/map.h>
#include <mach/regs-sysmmu.h>
-#include <mach/sysmmu.h>
+#include <plat/sysmmu.h>
+
+#define CTRL_ENABLE 0x5
+#define CTRL_BLOCK 0x7
+#define CTRL_DISABLE 0x0
+
+static struct device *dev;
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+ S5P_PAGE_FAULT_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR,
+ S5P_DEFAULT_SLAVE_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+ "PAGE FAULT",
+ "AR MULTI-HIT FAULT",
+ "AW MULTI-HIT FAULT",
+ "BUS ERROR",
+ "AR SECURITY PROTECTION FAULT",
+ "AR ACCESS PROTECTION FAULT",
+ "AW SECURITY PROTECTION FAULT",
+ "AW ACCESS PROTECTION FAULT"
+};
-struct sysmmu_controller s5p_sysmmu_cntlrs[S5P_SYSMMU_TOTAL_IPNUM];
+static int (*fault_handlers[S5P_SYSMMU_TOTAL_IPNUM])(
+ enum S5P_SYSMMU_INTERRUPT_TYPE itype,
+ unsigned long pgtable_base,
+ unsigned long fault_addr);
-void s5p_sysmmu_register(struct sysmmu_controller *sysmmuconp)
+/*
+ * If adjacent 2 bits are true, the system MMU is enabled.
+ * The system MMU is disabled, otherwise.
+ */
+static unsigned long sysmmu_states;
+
+static inline void set_sysmmu_active(sysmmu_ips ips)
{
- unsigned int reg_mmu_ctrl;
- unsigned int reg_mmu_status;
- unsigned int reg_pt_base_addr;
- unsigned int reg_int_status;
- unsigned int reg_page_ft_addr;
-
- reg_int_status = __raw_readl(sysmmuconp->regs + S5P_INT_STATUS);
- reg_mmu_ctrl = __raw_readl(sysmmuconp->regs + S5P_MMU_CTRL);
- reg_mmu_status = __raw_readl(sysmmuconp->regs + S5P_MMU_STATUS);
- reg_pt_base_addr = __raw_readl(sysmmuconp->regs + S5P_PT_BASE_ADDR);
- reg_page_ft_addr = __raw_readl(sysmmuconp->regs + S5P_PAGE_FAULT_ADDR);
-
- printk(KERN_INFO "%s: ips:%s\n", __func__, sysmmuconp->name);
- printk(KERN_INFO "%s: MMU_CTRL:0x%X, ", __func__, reg_mmu_ctrl);
- printk(KERN_INFO "MMU_STATUS:0x%X, PT_BASE_ADDR:0x%X\n", reg_mmu_status, reg_pt_base_addr);
- printk(KERN_INFO "%s: INT_STATUS:0x%X, PAGE_FAULT_ADDR:0x%X\n", __func__, reg_int_status, reg_page_ft_addr);
-
- switch (reg_int_status & 0xFF) {
- case 0x1:
- printk(KERN_INFO "%s: Page fault\n", __func__);
- printk(KERN_INFO "%s: Virtual address causing last page fault or bus error : 0x%x\n", __func__ , reg_page_ft_addr);
- break;
- case 0x2:
- printk(KERN_INFO "%s: AR multi-hit fault\n", __func__);
- break;
- case 0x4:
- printk(KERN_INFO "%s: AW multi-hit fault\n", __func__);
- break;
- case 0x8:
- printk(KERN_INFO "%s: Bus error\n", __func__);
- break;
- case 0x10:
- printk(KERN_INFO "%s: AR Security protection fault\n", __func__);
- break;
- case 0x20:
- printk(KERN_INFO "%s: AR Access protection fault\n", __func__);
- break;
- case 0x40:
- printk(KERN_INFO "%s: AW Security protection fault\n", __func__);
- break;
- case 0x80:
- printk(KERN_INFO "%s: AW Access protection fault\n", __func__);
- break;
- }
+ sysmmu_states |= 3 << (ips * 2);
}
-static irqreturn_t s5p_sysmmu_irq(int irq, void *dev_id)
+static inline void set_sysmmu_inactive(sysmmu_ips ips)
{
- unsigned int i;
- unsigned int reg_int_status;
- struct sysmmu_controller *sysmmuconp;
-
- for (i = 0; i < S5P_SYSMMU_TOTAL_IPNUM; i++) {
- sysmmuconp = &s5p_sysmmu_cntlrs[i];
-
- if (sysmmuconp->enable == true) {
- reg_int_status = __raw_readl(sysmmuconp->regs + S5P_INT_STATUS);
-
- if (reg_int_status & 0xFF)
- s5p_sysmmu_register(sysmmuconp);
- }
- }
- return IRQ_HANDLED;
+ sysmmu_states &= ~(3 << (ips * 2));
}
-int s5p_sysmmu_set_tablebase_pgd(sysmmu_ips ips, unsigned long pgd)
+static inline int is_sysmmu_active(sysmmu_ips ips)
{
- struct sysmmu_controller *sysmmuconp = NULL;
-
- sysmmuconp = &s5p_sysmmu_cntlrs[ips];
-
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- return 1;
- }
-
- /* Set sysmmu page table base address */
- __raw_writel(pgd, sysmmuconp->regs + S5P_PT_BASE_ADDR);
+ return sysmmu_states & (3 << (ips * 2));
+}
- if (s5p_sysmmu_tlb_invalidate(ips) != 0)
- printk(KERN_ERR "failed s5p_sysmmu_tlb_invalidate\n");
+static void __iomem *sysmmusfrs[S5P_SYSMMU_TOTAL_IPNUM];
- return 0;
+static inline void sysmmu_block(sysmmu_ips ips)
+{
+ __raw_writel(CTRL_BLOCK, sysmmusfrs[ips] + S5P_MMU_CTRL);
+ dev_dbg(dev, "%s is blocked.\n", sysmmu_ips_name[ips]);
}
-static int s5p_sysmmu_set_tablebase(sysmmu_ips ips)
+static inline void sysmmu_unblock(sysmmu_ips ips)
{
- unsigned int pg;
- struct sysmmu_controller *sysmmuconp;
+ __raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
+ dev_dbg(dev, "%s is unblocked.\n", sysmmu_ips_name[ips]);
+}
- sysmmuconp = &s5p_sysmmu_cntlrs[ips];
+static inline void __sysmmu_tlb_invalidate(sysmmu_ips ips)
+{
+ __raw_writel(0x1, sysmmusfrs[ips] + S5P_MMU_FLUSH);
+ dev_dbg(dev, "TLB of %s is invalidated.\n", sysmmu_ips_name[ips]);
+}
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- return 1;
+static inline void __sysmmu_set_ptbase(sysmmu_ips ips, unsigned long pgd)
+{
+ if (unlikely(pgd == 0)) {
+ pgd = (unsigned long)ZERO_PAGE(0);
+ __raw_writel(0x20, sysmmusfrs[ips] + S5P_MMU_CFG); /* 4KB LV1 */
+ } else {
+ __raw_writel(0x0, sysmmusfrs[ips] + S5P_MMU_CFG); /* 16KB LV1 */
}
- __asm__("mrc p15, 0, %0, c2, c0, 0" \
- : "=r" (pg) : : "cc"); \
- pg &= ~0x3fff;
+ __raw_writel(pgd, sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
- printk(KERN_INFO "%s: CP15 TTBR0 : 0x%x\n", __func__, pg);
-
- /* Set sysmmu page table base address */
- __raw_writel(pg, sysmmuconp->regs + S5P_PT_BASE_ADDR);
+ dev_dbg(dev, "Page table base of %s is initialized with 0x%08lX.\n",
+ sysmmu_ips_name[ips], pgd);
+ __sysmmu_tlb_invalidate(ips);
+}
- return 0;
+void sysmmu_set_fault_handler(sysmmu_ips ips,
+ int (*handler)(enum S5P_SYSMMU_INTERRUPT_TYPE itype,
+ unsigned long pgtable_base,
+ unsigned long fault_addr))
+{
+ BUG_ON(!((ips >= SYSMMU_MDMA) && (ips < S5P_SYSMMU_TOTAL_IPNUM)));
+ fault_handlers[ips] = handler;
}
-int s5p_sysmmu_enable(sysmmu_ips ips)
+static irqreturn_t s5p_sysmmu_irq(int irq, void *dev_id)
{
- unsigned int reg;
+ /* SYSMMU is in blocked when interrupt occurred. */
+ unsigned long base = 0;
+ sysmmu_ips ips = (sysmmu_ips)dev_id;
+ enum S5P_SYSMMU_INTERRUPT_TYPE itype;
- struct sysmmu_controller *sysmmuconp;
+ itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
+ __ffs(__raw_readl(sysmmusfrs[ips] + S5P_INT_STATUS));
- sysmmuconp = &s5p_sysmmu_cntlrs[ips];
+ BUG_ON(!((itype >= 0) && (itype < 8)));
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- return 1;
- }
+ dev_alert(dev, "%s occurred by %s.\n", sysmmu_fault_name[itype],
+ sysmmu_ips_name[ips]);
- s5p_sysmmu_set_tablebase(ips);
+ if (fault_handlers[ips]) {
+ unsigned long addr;
- /* replacement policy : LRU */
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CFG);
- reg |= 0x1;
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CFG);
+ base = __raw_readl(sysmmusfrs[ips] + S5P_PT_BASE_ADDR);
+ addr = __raw_readl(sysmmusfrs[ips] + fault_reg_offset[itype]);
- /* Enable interrupt, Enable MMU */
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CTRL);
- reg |= (0x1 << 2) | (0x1 << 0);
+ if (fault_handlers[ips](itype, base, addr)) {
+ __raw_writel(1 << itype,
+ sysmmusfrs[ips] + S5P_INT_CLEAR);
+ dev_notice(dev, "%s from %s is resolved."
+ " Retrying translation.\n",
+ sysmmu_fault_name[itype], sysmmu_ips_name[ips]);
+ } else {
+ base = 0;
+ }
+ }
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CTRL);
+ sysmmu_unblock(ips);
- sysmmuconp->enable = true;
+ if (!base)
+ dev_notice(dev, "%s from %s is not handled.\n",
+ sysmmu_fault_name[itype], sysmmu_ips_name[ips]);
- return 0;
+ return IRQ_HANDLED;
}
-int s5p_sysmmu_disable(sysmmu_ips ips)
+void s5p_sysmmu_set_tablebase_pgd(sysmmu_ips ips, unsigned long pgd)
{
- unsigned int reg;
-
- struct sysmmu_controller *sysmmuconp = NULL;
-
- if (ips > S5P_SYSMMU_TOTAL_IPNUM)
- printk(KERN_ERR "failed to get ips parameter\n");
-
- sysmmuconp = &s5p_sysmmu_cntlrs[ips];
-
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- return 1;
+ if (is_sysmmu_active(ips)) {
+ sysmmu_block(ips);
+ __sysmmu_set_ptbase(ips, pgd);
+ sysmmu_unblock(ips);
+ } else {
+ dev_dbg(dev, "%s is disabled. "
+ "Skipping initializing page table base.\n",
+ sysmmu_ips_name[ips]);
}
+}
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CFG);
-
- /* replacement policy : LRU */
- reg |= 0x1;
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CFG);
-
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CTRL);
+void s5p_sysmmu_enable(sysmmu_ips ips, unsigned long pgd)
+{
+ if (!is_sysmmu_active(ips)) {
+ sysmmu_clk_enable(ips);
- /* Disable MMU */
- reg &= ~0x1;
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CTRL);
+ __sysmmu_set_ptbase(ips, pgd);
- sysmmuconp->enable = false;
+ __raw_writel(CTRL_ENABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
- return 0;
+ set_sysmmu_active(ips);
+ dev_dbg(dev, "%s is enabled.\n", sysmmu_ips_name[ips]);
+ } else {
+ dev_dbg(dev, "%s is already enabled.\n", sysmmu_ips_name[ips]);
+ }
}
-int s5p_sysmmu_tlb_invalidate(sysmmu_ips ips)
+void s5p_sysmmu_disable(sysmmu_ips ips)
{
- unsigned int reg;
- struct sysmmu_controller *sysmmuconp = NULL;
-
- sysmmuconp = &s5p_sysmmu_cntlrs[ips];
-
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- return 1;
+ if (is_sysmmu_active(ips)) {
+ __raw_writel(CTRL_DISABLE, sysmmusfrs[ips] + S5P_MMU_CTRL);
+ set_sysmmu_inactive(ips);
+ sysmmu_clk_disable(ips);
+ dev_dbg(dev, "%s is disabled.\n", sysmmu_ips_name[ips]);
+ } else {
+ dev_dbg(dev, "%s is already disabled.\n", sysmmu_ips_name[ips]);
}
+}
- /* set Block MMU for flush TLB */
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CTRL);
- reg |= 0x1 << 1;
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CTRL);
-
- /* flush all TLB entry */
- __raw_writel(0x1, sysmmuconp->regs + S5P_MMU_FLUSH);
-
- /* set Un-block MMU after flush TLB */
- reg = __raw_readl(sysmmuconp->regs + S5P_MMU_CTRL);
- reg &= ~(0x1 << 1);
- __raw_writel(reg, sysmmuconp->regs + S5P_MMU_CTRL);
-
- return 0;
+void s5p_sysmmu_tlb_invalidate(sysmmu_ips ips)
+{
+ if (is_sysmmu_active(ips)) {
+ sysmmu_block(ips);
+ __sysmmu_tlb_invalidate(ips);
+ sysmmu_unblock(ips);
+ } else {
+ dev_dbg(dev, "%s is disabled. "
+ "Skipping invalidating TLB.\n", sysmmu_ips_name[ips]);
+ }
}
static int s5p_sysmmu_probe(struct platform_device *pdev)
{
- int i;
- int ret;
- struct resource *res;
- struct sysmmu_controller *sysmmuconp;
- sysmmu_ips ips;
+ int i, ret;
+ struct resource *res, *mem;
+
+ dev = &pdev->dev;
for (i = 0; i < S5P_SYSMMU_TOTAL_IPNUM; i++) {
- sysmmuconp = &s5p_sysmmu_cntlrs[i];
- if (sysmmuconp == NULL) {
- printk(KERN_ERR "failed to get ip's sysmmu info\n");
- ret = -ENOENT;
- goto err_res;
- }
+ int irq;
- sysmmuconp->name = sysmmu_ips_name[i];
+ sysmmu_clk_init(dev, i);
+ sysmmu_clk_disable(i);
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
if (!res) {
- printk(KERN_ERR "failed to get sysmmu resource\n");
+ dev_err(dev, "Failed to get the resource of %s.\n",
+ sysmmu_ips_name[i]);
ret = -ENODEV;
goto err_res;
}
- sysmmuconp->mem = request_mem_region(res->start,
+ mem = request_mem_region(res->start,
((res->end) - (res->start)) + 1, pdev->name);
- if (!sysmmuconp->mem) {
- pr_err("failed to request sysmmu memory region\n");
+ if (!mem) {
+ dev_err(dev, "Failed to request the memory region of %s.\n",
+ sysmmu_ips_name[i]);
ret = -EBUSY;
goto err_res;
}
- sysmmuconp->regs = ioremap(res->start, res->end - res->start + 1);
- if (!sysmmuconp->regs) {
- pr_err("failed to sysmmu ioremap\n");
+ sysmmusfrs[i] = ioremap(res->start, res->end - res->start + 1);
+ if (!sysmmusfrs[i]) {
+ dev_err(dev, "Failed to ioremap() for %s.\n",
+ sysmmu_ips_name[i]);
ret = -ENXIO;
goto err_reg;
}
- sysmmuconp->irq = platform_get_irq(pdev, i);
- if (sysmmuconp->irq <= 0) {
- pr_err("failed to get sysmmu irq resource\n");
+ irq = platform_get_irq(pdev, i);
+ if (irq <= 0) {
+ dev_err(dev, "Failed to get the IRQ resource of %s.\n",
+ sysmmu_ips_name[i]);
ret = -ENOENT;
goto err_map;
}
- ret = request_irq(sysmmuconp->irq, s5p_sysmmu_irq, IRQF_DISABLED, pdev->name, sysmmuconp);
- if (ret) {
- pr_err("failed to request irq\n");
+ if (request_irq(irq, s5p_sysmmu_irq, IRQF_DISABLED,
+ pdev->name, (void *)i)) {
+ dev_err(dev, "Failed to request IRQ for %s.\n",
+ sysmmu_ips_name[i]);
ret = -ENOENT;
goto err_map;
}
-
- ips = (sysmmu_ips)i;
-
- sysmmuconp->ips = ips;
}
return 0;
-err_reg:
- release_mem_region((resource_size_t)sysmmuconp->mem, (resource_size_t)((res->end) - (res->start) + 1));
err_map:
- iounmap(sysmmuconp->regs);
+ iounmap(sysmmusfrs[i]);
+err_reg:
+ release_mem_region(mem->start, resource_size(mem));
err_res:
return ret;
}