aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/plat-s3c24xx
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/plat-s3c24xx')
-rw-r--r--arch/arm/plat-s3c24xx/adc.c58
-rw-r--r--arch/arm/plat-s3c24xx/cpu.c10
-rw-r--r--arch/arm/plat-s3c24xx/devs.c20
-rw-r--r--arch/arm/plat-s3c24xx/gpiolib.c13
-rw-r--r--arch/arm/plat-s3c24xx/include/plat/irq.h21
-rw-r--r--arch/arm/plat-s3c24xx/irq.c50
-rw-r--r--arch/arm/plat-s3c24xx/pwm-clock.c437
7 files changed, 590 insertions, 19 deletions
diff --git a/arch/arm/plat-s3c24xx/adc.c b/arch/arm/plat-s3c24xx/adc.c
index ee1baf11ad9..9cff2320e11 100644
--- a/arch/arm/plat-s3c24xx/adc.c
+++ b/arch/arm/plat-s3c24xx/adc.c
@@ -43,6 +43,7 @@ struct s3c_adc_client {
unsigned int nr_samples;
unsigned char is_ts;
unsigned char channel;
+ unsigned int selected;
void (*select_cb)(unsigned selected);
void (*convert_cb)(unsigned val1, unsigned val2,
@@ -58,20 +59,31 @@ struct adc_device {
void __iomem *regs;
unsigned int prescale;
+ unsigned int delay;
int irq;
};
static struct adc_device *adc_dev;
+static struct work_struct resume_work;
+
static LIST_HEAD(adc_pending);
#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg)
+#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | \
+ S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_AUTO_PST | \
+ S3C2410_ADCTSC_XY_PST(0))
+
static inline void s3c_adc_convert(struct adc_device *adc)
{
unsigned con = readl(adc->regs + S3C2410_ADCCON);
+ if (adc->cur->is_ts)
+ writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
+ adc->regs + S3C2410_ADCTSC);
+
con |= S3C2410_ADCCON_ENABLE_START;
writel(con, adc->regs + S3C2410_ADCCON);
}
@@ -81,7 +93,10 @@ static inline void s3c_adc_select(struct adc_device *adc,
{
unsigned con = readl(adc->regs + S3C2410_ADCCON);
- client->select_cb(1);
+ if (!client->selected) {
+ client->selected = 1;
+ client->select_cb(1);
+ }
con &= ~S3C2410_ADCCON_MUXMASK;
con &= ~S3C2410_ADCCON_STDBM;
@@ -105,12 +120,9 @@ static void s3c_adc_try(struct adc_device *adc)
{
struct s3c_adc_client *next = adc->ts_pend;
- if (!next && !list_empty(&adc_pending)) {
+ if (!next && !list_empty(&adc_pending))
next = list_first_entry(&adc_pending,
struct s3c_adc_client, pend);
- list_del(&next->pend);
- } else
- adc->ts_pend = NULL;
if (next) {
adc_dbg(adc, "new client is %p\n", next);
@@ -234,14 +246,20 @@ static irqreturn_t s3c_adc_irq(int irq, void *pw)
if (client->nr_samples > 0) {
/* fire another conversion for this */
-
+ client->selected = 1;
client->select_cb(1);
s3c_adc_convert(adc);
} else {
local_irq_save(flags);
- (client->select_cb)(0);
+ client->selected = 0;
+ if (!adc->cur->is_ts)
+ list_del(&adc->cur->pend);
+ else
+ adc->ts_pend = NULL;
adc->cur = NULL;
+ (client->select_cb)(0);
+
s3c_adc_try(adc);
local_irq_restore(flags);
}
@@ -264,6 +282,7 @@ static int s3c_adc_probe(struct platform_device *pdev)
adc->pdev = pdev;
adc->prescale = S3C2410_ADCCON_PRSCVL(49);
+ adc->delay = 0x2710;
adc->irq = platform_get_irq(pdev, 1);
if (adc->irq <= 0) {
@@ -303,6 +322,7 @@ static int s3c_adc_probe(struct platform_device *pdev)
writel(adc->prescale | S3C2410_ADCCON_PRSCEN,
adc->regs + S3C2410_ADCCON);
+ writel(adc->delay, adc->regs + S3C2410_ADCDLY);
dev_info(dev, "attached adc driver\n");
@@ -346,18 +366,42 @@ static int s3c_adc_suspend(struct platform_device *pdev, pm_message_t state)
writel(con, adc->regs + S3C2410_ADCCON);
clk_disable(adc->clk);
+ disable_irq(IRQ_ADC);
+ if (!list_empty(&adc_pending) || adc->ts_pend)
+ dev_info(&pdev->dev, "We still have adc clients pending\n");
return 0;
}
+/* It seems this is not needed. This is under upstream review now. */
+static void adc_resume_work(struct work_struct *work)
+{
+ if (!adc_dev) /* Have no ADC here */
+ return;
+
+ if (!list_empty(&adc_pending) || adc_dev->ts_pend)
+ /* We still have adc clients pending */
+ s3c_adc_try(adc_dev);
+}
+
static int s3c_adc_resume(struct platform_device *pdev)
{
struct adc_device *adc = platform_get_drvdata(pdev);
+ enable_irq(IRQ_ADC);
clk_enable(adc->clk);
writel(adc->prescale | S3C2410_ADCCON_PRSCEN,
adc->regs + S3C2410_ADCCON);
+ writel(adc->delay, adc->regs + S3C2410_ADCDLY);
+
+ /* Schedule task if there are clients pending. */
+ if (!list_empty(&adc_pending) || adc_dev->ts_pend) {
+ INIT_WORK(&resume_work, adc_resume_work);
+ if (!schedule_work(&resume_work))
+ dev_err(&pdev->dev,
+ "Failed to schedule adc_resume work!\n");
+ }
return 0;
}
diff --git a/arch/arm/plat-s3c24xx/cpu.c b/arch/arm/plat-s3c24xx/cpu.c
index 1932b7e0da1..ed4c19fa196 100644
--- a/arch/arm/plat-s3c24xx/cpu.c
+++ b/arch/arm/plat-s3c24xx/cpu.c
@@ -61,6 +61,7 @@ static const char name_s3c2410[] = "S3C2410";
static const char name_s3c2412[] = "S3C2412";
static const char name_s3c2440[] = "S3C2440";
static const char name_s3c2442[] = "S3C2442";
+static const char name_s3c2442b[] = "S3C2442B";
static const char name_s3c2443[] = "S3C2443";
static const char name_s3c2410a[] = "S3C2410A";
static const char name_s3c2440a[] = "S3C2440A";
@@ -112,6 +113,15 @@ static struct cpu_table cpu_ids[] __initdata = {
.name = name_s3c2442
},
{
+ .idcode = 0x32440aab,
+ .idmask = 0xffffffff,
+ .map_io = s3c244x_map_io,
+ .init_clocks = s3c244x_init_clocks,
+ .init_uarts = s3c244x_init_uarts,
+ .init = s3c2442_init,
+ .name = name_s3c2442b
+ },
+ {
.idcode = 0x32412001,
.idmask = 0xffffffff,
.map_io = s3c2412_map_io,
diff --git a/arch/arm/plat-s3c24xx/devs.c b/arch/arm/plat-s3c24xx/devs.c
index 4eb378c89a3..a89286b4a12 100644
--- a/arch/arm/plat-s3c24xx/devs.c
+++ b/arch/arm/plat-s3c24xx/devs.c
@@ -28,6 +28,8 @@
#include <mach/hardware.h>
#include <asm/irq.h>
+#include <mach/ts.h>
+
#include <plat/regs-serial.h>
#include <plat/udc.h>
@@ -199,6 +201,24 @@ struct platform_device s3c_device_nand = {
EXPORT_SYMBOL(s3c_device_nand);
+/* Touchscreen */
+
+struct platform_device s3c_device_ts = {
+ .name = "s3c2410-ts",
+ .id = -1,
+ .dev.parent = &s3c_device_adc.dev,
+};
+
+static struct s3c2410_ts_mach_info s3c2410ts_info;
+
+void set_s3c2410ts_info(const struct s3c2410_ts_mach_info *hard_s3c2410ts_info)
+{
+ memcpy(&s3c2410ts_info, hard_s3c2410ts_info,
+ sizeof(struct s3c2410_ts_mach_info));
+ s3c_device_ts.dev.platform_data = &s3c2410ts_info;
+}
+EXPORT_SYMBOL(set_s3c2410ts_info);
+
/* USB Device (Gadget)*/
static struct resource s3c_usbgadget_resource[] = {
diff --git a/arch/arm/plat-s3c24xx/gpiolib.c b/arch/arm/plat-s3c24xx/gpiolib.c
index 6d7a961d326..c2dd900660b 100644
--- a/arch/arm/plat-s3c24xx/gpiolib.c
+++ b/arch/arm/plat-s3c24xx/gpiolib.c
@@ -26,6 +26,7 @@
#include <plat/pm.h>
#include <mach/regs-gpio.h>
+#include <mach/regs-gpioj.h>
static int s3c24xx_gpiolib_banka_input(struct gpio_chip *chip, unsigned offset)
{
@@ -160,8 +161,16 @@ struct s3c_gpio_chip s3c24xx_gpios[] = {
.label = "GPIOH",
.ngpio = 11,
},
- },
-};
+ }, {
+ .base = S3C2440_GPJCON,
+ .pm = __gpio_pm(&s3c_gpio_pm_2bit),
+ .chip = {
+ .base = S3C2440_GPJ0,
+ .owner = THIS_MODULE,
+ .label = "GPIOJ",
+ .ngpio = 11,
+ },
+ },};
static __init int s3c24xx_gpiolib_init(void)
{
diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h
index 69e1be8bec3..11a866466d8 100644
--- a/arch/arm/plat-s3c24xx/include/plat/irq.h
+++ b/arch/arm/plat-s3c24xx/include/plat/irq.h
@@ -12,6 +12,7 @@
#include <linux/io.h>
+#include <mach/irqs.h>
#include <mach/hardware.h>
#include <mach/regs-irq.h>
#include <mach/regs-gpio.h>
@@ -31,8 +32,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit,
{
unsigned long mask;
unsigned long submask;
+#ifdef CONFIG_S3C2440_C_FIQ
+ unsigned long flags;
+#endif
submask = __raw_readl(S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_save_flags(flags);
+ local_fiq_disable();
+#endif
mask = __raw_readl(S3C2410_INTMSK);
submask |= (1UL << (irqno - IRQ_S3CUART_RX0));
@@ -45,6 +53,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit,
/* write back masks */
__raw_writel(submask, S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_irq_restore(flags);
+#endif
}
@@ -53,8 +64,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit)
{
unsigned long mask;
unsigned long submask;
+#ifdef CONFIG_S3C2440_C_FIQ
+ unsigned long flags;
+#endif
submask = __raw_readl(S3C2410_INTSUBMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_save_flags(flags);
+ local_fiq_disable();
+#endif
mask = __raw_readl(S3C2410_INTMSK);
submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0));
@@ -63,6 +81,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit)
/* write back masks */
__raw_writel(submask, S3C2410_INTSUBMSK);
__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_irq_restore(flags);
+#endif
}
diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c
index 958737775ad..4b21ac9d693 100644
--- a/arch/arm/plat-s3c24xx/irq.c
+++ b/arch/arm/plat-s3c24xx/irq.c
@@ -28,6 +28,8 @@
#include <asm/mach/irq.h>
#include <plat/regs-irqtype.h>
+#include <mach/regs-irq.h>
+#include <mach/regs-gpio.h>
#include <plat/cpu.h>
#include <plat/pm.h>
@@ -37,12 +39,20 @@ static void
s3c_irq_mask(unsigned int irqno)
{
unsigned long mask;
-
+#ifdef CONFIG_S3C2440_C_FIQ
+ unsigned long flags;
+#endif
irqno -= IRQ_EINT0;
-
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_save_flags(flags);
+ local_fiq_disable();
+#endif
mask = __raw_readl(S3C2410_INTMSK);
mask |= 1UL << irqno;
__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_irq_restore(flags);
+#endif
}
static inline void
@@ -59,9 +69,19 @@ s3c_irq_maskack(unsigned int irqno)
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
unsigned long mask;
-
+#ifdef CONFIG_S3C2440_C_FIQ
+ unsigned long flags;
+#endif
+
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_save_flags(flags);
+ local_fiq_disable();
+#endif
mask = __raw_readl(S3C2410_INTMSK);
__raw_writel(mask|bitval, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_irq_restore(flags);
+#endif
__raw_writel(bitval, S3C2410_SRCPND);
__raw_writel(bitval, S3C2410_INTPND);
@@ -72,15 +92,25 @@ static void
s3c_irq_unmask(unsigned int irqno)
{
unsigned long mask;
+#ifdef CONFIG_S3C2440_C_FIQ
+ unsigned long flags;
+#endif
if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23)
irqdbf2("s3c_irq_unmask %d\n", irqno);
irqno -= IRQ_EINT0;
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_save_flags(flags);
+ local_fiq_disable();
+#endif
mask = __raw_readl(S3C2410_INTMSK);
mask &= ~(1UL << irqno);
__raw_writel(mask, S3C2410_INTMSK);
+#ifdef CONFIG_S3C2440_C_FIQ
+ local_irq_restore(flags);
+#endif
}
struct irq_chip s3c_irq_level_chip = {
@@ -523,26 +553,26 @@ void __init s3c24xx_init_irq(void)
last = 0;
for (i = 0; i < 4; i++) {
- pend = __raw_readl(S3C2410_INTPND);
+ pend = __raw_readl(S3C2410_SUBSRCPND);
if (pend == 0 || pend == last)
break;
- __raw_writel(pend, S3C2410_SRCPND);
- __raw_writel(pend, S3C2410_INTPND);
- printk("irq: clearing pending status %08x\n", (int)pend);
+ printk("irq: clearing subpending status %08x\n", (int)pend);
+ __raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
- pend = __raw_readl(S3C2410_SUBSRCPND);
+ pend = __raw_readl(S3C2410_INTPND);
if (pend == 0 || pend == last)
break;
- printk("irq: clearing subpending status %08x\n", (int)pend);
- __raw_writel(pend, S3C2410_SUBSRCPND);
+ __raw_writel(pend, S3C2410_SRCPND);
+ __raw_writel(pend, S3C2410_INTPND);
+ printk("irq: clearing pending status %08x\n", (int)pend);
last = pend;
}
diff --git a/arch/arm/plat-s3c24xx/pwm-clock.c b/arch/arm/plat-s3c24xx/pwm-clock.c
new file mode 100644
index 00000000000..d41cccd6a25
--- /dev/null
+++ b/arch/arm/plat-s3c24xx/pwm-clock.c
@@ -0,0 +1,437 @@
+/* linux/arch/arm/plat-s3c24xx/pwm-clock.c
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * Copyright (c) 2007, 2008 Ben Dooks
+ * Ben Dooks <ben-linux@fluff.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License.
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+
+#include <mach/regs-clock.h>
+#include <mach/regs-gpio.h>
+
+#include <asm/plat-s3c24xx/clock.h>
+#include <asm/plat-s3c24xx/cpu.h>
+
+#include <asm/plat-s3c/regs-timer.h>
+
+/* Each of the timers 0 through 5 go through the following
+ * clock tree, with the inputs depending on the timers.
+ *
+ * pclk ---- [ prescaler 0 ] -+---> timer 0
+ * +---> timer 1
+ *
+ * pclk ---- [ prescaler 1 ] -+---> timer 2
+ * +---> timer 3
+ * \---> timer 4
+ *
+ * Which are fed into the timers as so:
+ *
+ * prescaled 0 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 0
+ * tclk 0 ------------------------------/
+ *
+ * prescaled 0 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 1
+ * tclk 0 ------------------------------/
+ *
+ *
+ * prescaled 1 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 2
+ * tclk 1 ------------------------------/
+ *
+ * prescaled 1 ---- [ div 2,4,8,16 ] ---\
+ * [mux] -> timer 3
+ * tclk 1 ------------------------------/
+ *
+ * prescaled 1 ---- [ div 2,4,8, 16 ] --\
+ * [mux] -> timer 4
+ * tclk 1 ------------------------------/
+ *
+ * Since the mux and the divider are tied together in the
+ * same register space, it is impossible to set the parent
+ * and the rate at the same time. To avoid this, we add an
+ * intermediate 'prescaled-and-divided' clock to select
+ * as the parent for the timer input clock called tdiv.
+ *
+ * prescaled clk --> pwm-tdiv ---\
+ * [ mux ] --> timer X
+ * tclk -------------------------/
+*/
+
+static unsigned long clk_pwm_scaler_getrate(struct clk *clk)
+{
+ unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0);
+
+ if (clk->id == 1) {
+ tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK;
+ tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT;
+ } else {
+ tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK;
+ }
+
+ return clk_get_rate(clk->parent) / (tcfg0 + 1);
+}
+
+/* TODO - add set rate calls. */
+
+static struct clk clk_timer_scaler[] = {
+ [0] = {
+ .name = "pwm-scaler0",
+ .id = -1,
+ .get_rate = clk_pwm_scaler_getrate,
+ },
+ [1] = {
+ .name = "pwm-scaler1",
+ .id = -1,
+ .get_rate = clk_pwm_scaler_getrate,
+ },
+};
+
+static struct clk clk_timer_tclk[] = {
+ [0] = {
+ .name = "pwm-tclk0",
+ .id = -1,
+ },
+ [1] = {
+ .name = "pwm-tclk1",
+ .id = -1,
+ },
+};
+
+struct pwm_tdiv_clk {
+ struct clk clk;
+ unsigned int divisor;
+};
+
+static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk)
+{
+ return container_of(clk, struct pwm_tdiv_clk, clk);
+}
+
+static inline unsigned long tcfg_to_divisor(unsigned long tcfg1)
+{
+ return 1 << (1 + tcfg1);
+}
+
+static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned int divisor;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ if (tcfg1 == S3C2410_TCFG1_MUX_TCLK)
+ divisor = to_tdiv(clk)->divisor;
+ else
+ divisor = tcfg_to_divisor(tcfg1);
+
+ return clk_get_rate(clk->parent) / divisor;
+}
+
+static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk,
+ unsigned long rate)
+{
+ unsigned long parent_rate;
+ unsigned long divisor;
+
+ parent_rate = clk_get_rate(clk->parent);
+ divisor = parent_rate / rate;
+
+ if (divisor <= 2)
+ divisor = 2;
+ else if (divisor <= 4)
+ divisor = 4;
+ else if (divisor <= 8)
+ divisor = 8;
+ else
+ divisor = 16;
+
+ return parent_rate / divisor;
+}
+
+static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk)
+{
+ unsigned long bits;
+
+ switch (divclk->divisor) {
+ case 2:
+ bits = S3C2410_TCFG1_MUX_DIV2;
+ break;
+ case 4:
+ bits = S3C2410_TCFG1_MUX_DIV4;
+ break;
+ case 8:
+ bits = S3C2410_TCFG1_MUX_DIV8;
+ break;
+ case 16:
+ default:
+ bits = S3C2410_TCFG1_MUX_DIV16;
+ break;
+ }
+
+ return bits;
+}
+
+static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned long bits = clk_pwm_tdiv_bits(divclk);
+ unsigned long flags;
+ unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id);
+
+ local_irq_save(flags);
+
+ tcfg1 = __raw_readl(S3C2410_TCFG1);
+ tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift);
+ tcfg1 |= bits << shift;
+ __raw_writel(tcfg1, S3C2410_TCFG1);
+
+ local_irq_restore(flags);
+}
+
+static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct pwm_tdiv_clk *divclk = to_tdiv(clk);
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned long parent_rate = clk_get_rate(clk->parent);
+ unsigned long divisor;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ rate = clk_round_rate(clk, rate);
+ divisor = parent_rate / rate;
+
+ if (divisor > 16)
+ return -EINVAL;
+
+ divclk->divisor = divisor;
+
+ /* Update the current MUX settings if we are currently
+ * selected as the clock source for this clock. */
+
+ if (tcfg1 != S3C2410_TCFG1_MUX_TCLK)
+ clk_pwm_tdiv_update(divclk);
+
+ return 0;
+}
+
+static struct pwm_tdiv_clk clk_timer_tdiv[] = {
+ [0] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[0],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [1] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[0],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ }
+ },
+ [2] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [3] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+ [4] = {
+ .clk = {
+ .name = "pwm-tdiv",
+ .parent = &clk_timer_scaler[1],
+ .get_rate = clk_pwm_tdiv_get_rate,
+ .set_rate = clk_pwm_tdiv_set_rate,
+ .round_rate = clk_pwm_tdiv_round_rate,
+ },
+ },
+};
+
+static int __init clk_pwm_tdiv_register(unsigned int id)
+{
+ struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id];
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ divclk->clk.id = id;
+ divclk->divisor = tcfg_to_divisor(tcfg1);
+
+ return s3c24xx_register_clock(&divclk->clk);
+}
+
+static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id)
+{
+ return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0];
+}
+
+static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id)
+{
+ return &clk_timer_tdiv[id].clk;
+}
+
+static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent)
+{
+ unsigned int id = clk->id;
+ unsigned long tcfg1;
+ unsigned long flags;
+ unsigned long bits;
+ unsigned long shift = S3C2410_TCFG1_SHIFT(id);
+
+ if (parent == s3c24xx_pwmclk_tclk(id))
+ bits = S3C2410_TCFG1_MUX_TCLK << shift;
+ else if (parent == s3c24xx_pwmclk_tdiv(id))
+ bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift;
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ local_irq_save(flags);
+
+ tcfg1 = __raw_readl(S3C2410_TCFG1);
+ tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift);
+ __raw_writel(tcfg1 | bits, S3C2410_TCFG1);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static struct clk clk_tin[] = {
+ [0] = {
+ .name = "pwm-tin",
+ .id = 0,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [1] = {
+ .name = "pwm-tin",
+ .id = 1,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [2] = {
+ .name = "pwm-tin",
+ .id = 2,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [3] = {
+ .name = "pwm-tin",
+ .id = 3,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+ [4] = {
+ .name = "pwm-tin",
+ .id = 4,
+ .set_parent = clk_pwm_tin_set_parent,
+ },
+};
+
+static __init int clk_pwm_tin_register(struct clk *pwm)
+{
+ unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1);
+ unsigned int id = pwm->id;
+
+ struct clk *parent;
+ int ret;
+
+ ret = s3c24xx_register_clock(pwm);
+ if (ret < 0)
+ return ret;
+
+ tcfg1 >>= S3C2410_TCFG1_SHIFT(id);
+ tcfg1 &= S3C2410_TCFG1_MUX_MASK;
+
+ if (tcfg1 == S3C2410_TCFG1_MUX_TCLK)
+ parent = s3c24xx_pwmclk_tclk(id);
+ else
+ parent = s3c24xx_pwmclk_tdiv(id);
+
+ return clk_set_parent(pwm, parent);
+}
+
+static __init int s3c24xx_pwmclk_init(void)
+{
+ struct clk *clk_timers;
+ unsigned int clk;
+ int ret;
+
+ clk_timers = clk_get(NULL, "timers");
+ if (IS_ERR(clk_timers)) {
+ printk(KERN_ERR "%s: no parent clock\n", __func__);
+ return -EINVAL;
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) {
+ clk_timer_scaler[clk].parent = clk_timers;
+ ret = s3c24xx_register_clock(&clk_timer_scaler[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm scaler%d clock\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) {
+ ret = s3c24xx_register_clock(&clk_timer_tclk[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pww tclk%d\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) {
+ ret = clk_pwm_tdiv_register(clk);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk);
+ goto err;
+ }
+ }
+
+ for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) {
+ ret = clk_pwm_tin_register(&clk_tin[clk]);
+ if (ret < 0) {
+ printk(KERN_ERR "error adding pwm%d tin clock\n", clk);
+ goto err;
+ }
+ }
+
+ return 0;
+
+ err:
+ return ret;
+}
+
+arch_initcall(s3c24xx_pwmclk_init);