diff options
author | dmitry pervushin <dpervushin@embeddedalley.com> | 2009-04-23 12:24:13 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-04-27 10:28:08 +0100 |
commit | 5cccd37ea15970846a93b4b01fafd6e043bafe8e (patch) | |
tree | 4ab99b59f91964028fbba128d8ae086f60bd8c82 /arch/arm/plat-stmp3xxx | |
parent | e317872ac532fd845c597e55ceb5a9bceee878c1 (diff) | |
download | lwn-5cccd37ea15970846a93b4b01fafd6e043bafe8e.tar.gz lwn-5cccd37ea15970846a93b4b01fafd6e043bafe8e.zip |
[ARM] 5477/1: Freescale STMP platform support [6/10]
Sources: common STMP3xxx platform support
Signed-off-by: dmitry pervushin <dpervushin@embeddedalley.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/plat-stmp3xxx')
-rw-r--r-- | arch/arm/plat-stmp3xxx/Kconfig | 37 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/Makefile | 5 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/clock.c | 1112 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/clock.h | 61 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/core.c | 127 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/dma.c | 462 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/irq.c | 59 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/pinmux.c | 545 | ||||
-rw-r--r-- | arch/arm/plat-stmp3xxx/timer.c | 172 |
9 files changed, 2580 insertions, 0 deletions
diff --git a/arch/arm/plat-stmp3xxx/Kconfig b/arch/arm/plat-stmp3xxx/Kconfig new file mode 100644 index 000000000000..2cf37c35951b --- /dev/null +++ b/arch/arm/plat-stmp3xxx/Kconfig @@ -0,0 +1,37 @@ +if ARCH_STMP3XXX + +menu "Freescale STMP3xxx implementations" + +choice + prompt "Select STMP3xxx chip family" + +config ARCH_STMP37XX + bool "Freescale SMTP37xx" + select CPU_ARM926T + ---help--- + STMP37xx refers to 3700 through 3769 chips + +config ARCH_STMP378X + bool "Freescale STMP378x" + select CPU_ARM926T + ---help--- + STMP378x refers to 3780 through 3789 chips + +endchoice + +choice + prompt "Select STMP3xxx board type" + +config MACH_STMP37XX + depends on ARCH_STMP37XX + bool "Freescale STMP37xx development board" + +config MACH_STMP378X + depends on ARCH_STMP378X + bool "Freescale STMP378x development board" + +endchoice + +endmenu + +endif diff --git a/arch/arm/plat-stmp3xxx/Makefile b/arch/arm/plat-stmp3xxx/Makefile new file mode 100644 index 000000000000..b63480066d6b --- /dev/null +++ b/arch/arm/plat-stmp3xxx/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the linux kernel. +# +# Object file lists. +obj-y += core.o timer.o irq.o dma.o clock.o pinmux.o diff --git a/arch/arm/plat-stmp3xxx/clock.c b/arch/arm/plat-stmp3xxx/clock.c new file mode 100644 index 000000000000..9a1d46b470cd --- /dev/null +++ b/arch/arm/plat-stmp3xxx/clock.c @@ -0,0 +1,1112 @@ +/* + * Clock manipulation routines for Freescale STMP37XX/STMP378X + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <asm/mach-types.h> +#include <asm/clkdev.h> +#include <mach/regs-clkctrl.h> + +#include "clock.h" + +static DEFINE_SPINLOCK(clocks_lock); + +static struct clk osc_24M; +static struct clk pll_clk; +static struct clk cpu_clk; +static struct clk hclk; + +static int propagate_rate(struct clk *); + +static inline int clk_is_busy(struct clk *clk) +{ + return __raw_readl(clk->busy_reg) & (1 << clk->busy_bit); +} + +static inline int clk_good(struct clk *clk) +{ + return clk && !IS_ERR(clk) && clk->ops; +} + +static int std_clk_enable(struct clk *clk) +{ + if (clk->enable_reg) { + u32 clk_reg = __raw_readl(clk->enable_reg); + if (clk->enable_negate) + clk_reg &= ~(1 << clk->enable_shift); + else + clk_reg |= (1 << clk->enable_shift); + __raw_writel(clk_reg, clk->enable_reg); + if (clk->enable_wait) + udelay(clk->enable_wait); + return 0; + } else + return -EINVAL; +} + +static int std_clk_disable(struct clk *clk) +{ + if (clk->enable_reg) { + u32 clk_reg = __raw_readl(clk->enable_reg); + if (clk->enable_negate) + clk_reg |= (1 << clk->enable_shift); + else + clk_reg &= ~(1 << clk->enable_shift); + __raw_writel(clk_reg, clk->enable_reg); + return 0; + } else + return -EINVAL; +} + +static int io_set_rate(struct clk *clk, u32 rate) +{ + u32 reg_frac, clkctrl_frac; + int i, ret = 0, mask = 0x1f; + + clkctrl_frac = (clk->parent->rate * 18 + rate - 1) / rate; + + if (clkctrl_frac < 18 || clkctrl_frac > 35) { + ret = -EINVAL; + goto out; + } + + reg_frac = __raw_readl(clk->scale_reg); + reg_frac &= ~(mask << clk->scale_shift); + __raw_writel(reg_frac | (clkctrl_frac << clk->scale_shift), + clk->scale_reg); + if (clk->busy_reg) { + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + if (!i) + ret = -ETIMEDOUT; + else + ret = 0; + } +out: + return ret; +} + +static long io_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate * 18; + int mask = 0x1f; + + rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; + clk->rate = rate; + + return rate; +} + +static long per_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate; + long div; + const int mask = 0xff; + + if (clk->enable_reg && + !(__raw_readl(clk->enable_reg) & clk->enable_shift)) + clk->rate = 0; + else { + div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; + if (div) + rate /= div; + clk->rate = rate; + } + + return clk->rate; +} + +static int per_set_rate(struct clk *clk, u32 rate) +{ + int ret = -EINVAL; + int div = (clk->parent->rate + rate - 1) / rate; + u32 reg_frac; + const int mask = 0xff; + int try = 10; + int i = -1; + + if (div == 0 || div > mask) + goto out; + + reg_frac = __raw_readl(clk->scale_reg); + reg_frac &= ~(mask << clk->scale_shift); + + while (try--) { + __raw_writel(reg_frac | (div << clk->scale_shift), + clk->scale_reg); + + if (clk->busy_reg) { + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + } + if (i) + break; + } + + if (!i) + ret = -ETIMEDOUT; + else + ret = 0; + +out: + if (ret != 0) + printk(KERN_ERR "%s: error %d\n", __func__, ret); + return ret; +} + +static long lcdif_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate; + long div; + const int mask = 0xff; + + div = (__raw_readl(clk->scale_reg) >> clk->scale_shift) & mask; + if (div) { + rate /= div; + div = (HW_CLKCTRL_FRAC_RD() & BM_CLKCTRL_FRAC_PIXFRAC) >> + BP_CLKCTRL_FRAC_PIXFRAC; + rate /= div; + } + clk->rate = rate; + + return rate; +} + +static int lcdif_set_rate(struct clk *clk, u32 rate) +{ + int ret = 0; + /* + * On 3700, we can get most timings exact by modifying ref_pix + * and the divider, but keeping the phase timings at 1 (2 + * phases per cycle). + * + * ref_pix can be between 480e6*18/35=246.9MHz and 480e6*18/18=480MHz, + * which is between 18/(18*480e6)=2.084ns and 35/(18*480e6)=4.050ns. + * + * ns_cycle >= 2*18e3/(18*480) = 25/6 + * ns_cycle <= 2*35e3/(18*480) = 875/108 + * + * Multiply the ns_cycle by 'div' to lengthen it until it fits the + * bounds. This is the divider we'll use after ref_pix. + * + * 6 * ns_cycle >= 25 * div + * 108 * ns_cycle <= 875 * div + */ + u32 ns_cycle = 1000000 / rate; + u32 div, reg_val; + u32 lowest_result = (u32) -1; + u32 lowest_div = 0, lowest_fracdiv = 0; + + for (div = 1; div < 256; ++div) { + u32 fracdiv; + u32 ps_result; + int lower_bound = 6 * ns_cycle >= 25 * div; + int upper_bound = 108 * ns_cycle <= 875 * div; + if (!lower_bound) + break; + if (!upper_bound) + continue; + /* + * Found a matching div. Calculate fractional divider needed, + * rounded up. + */ + fracdiv = ((clk->parent->rate / 1000 * 18 / 2) * + ns_cycle + 1000 * div - 1) / + (1000 * div); + if (fracdiv < 18 || fracdiv > 35) { + ret = -EINVAL; + goto out; + } + /* Calculate the actual cycle time this results in */ + ps_result = 6250 * div * fracdiv / 27; + + /* Use the fastest result that doesn't break ns_cycle */ + if (ps_result <= lowest_result) { + lowest_result = ps_result; + lowest_div = div; + lowest_fracdiv = fracdiv; + } + } + + if (div >= 256 || lowest_result == (u32) -1) { + ret = -EINVAL; + goto out; + } + pr_debug("Programming PFD=%u,DIV=%u ref_pix=%uMHz " + "PIXCLK=%uMHz cycle=%u.%03uns\n", + lowest_fracdiv, lowest_div, + 480*18/lowest_fracdiv, 480*18/lowest_fracdiv/lowest_div, + lowest_result / 1000, lowest_result % 1000); + + /* Program ref_pix phase fractional divider */ + HW_CLKCTRL_FRAC_WR((HW_CLKCTRL_FRAC_RD() & ~BM_CLKCTRL_FRAC_PIXFRAC) | + BF_CLKCTRL_FRAC_PIXFRAC(lowest_fracdiv)); + /* Ungate PFD */ + HW_CLKCTRL_FRAC_CLR(BM_CLKCTRL_FRAC_CLKGATEPIX); + + /* Program pix divider */ + reg_val = __raw_readl(clk->scale_reg); + reg_val &= ~(BM_CLKCTRL_PIX_DIV | BM_CLKCTRL_PIX_CLKGATE); + reg_val |= BF_CLKCTRL_PIX_DIV(lowest_div); + __raw_writel(reg_val, clk->scale_reg); + + /* Wait for divider update */ + if (clk->busy_reg) { + int i; + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + if (!i) { + ret = -ETIMEDOUT; + goto out; + } + } + + /* Switch to ref_pix source */ + HW_CLKCTRL_CLKSEQ_CLR(BM_CLKCTRL_CLKSEQ_BYPASS_PIX); + +out: + return ret; +} + + +static int cpu_set_rate(struct clk *clk, u32 rate) +{ + if (rate < 24000) + return -EINVAL; + else if (rate == 24000) { + /* switch to the 24M source */ + clk_set_parent(clk, &osc_24M); + } else { + int i; + u32 clkctrl_cpu = 1; + u32 c = clkctrl_cpu; + u32 clkctrl_frac = 1; + u32 val; + for ( ; c < 0x40; c++) { + u32 f = (pll_clk.rate*18/c + rate/2) / rate; + int s1, s2; + + if (f < 18 || f > 35) + continue; + s1 = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - rate; + s2 = pll_clk.rate*18/c/f - rate; + pr_debug("%s: s1 %d, s2 %d\n", __func__, s1, s2); + if (abs(s1) > abs(s2)) { + clkctrl_cpu = c; + clkctrl_frac = f; + } + if (s2 == 0) + break; + }; + pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__, + clkctrl_cpu, clkctrl_frac); + if (c == 0x40) { + int d = pll_clk.rate*18/clkctrl_frac/clkctrl_cpu - + rate; + if (abs(d) > 100 || + clkctrl_frac < 18 || clkctrl_frac > 35) + return -EINVAL; + } + + /* 4.6.2 */ + val = __raw_readl(clk->scale_reg); + val &= ~(0x3f << clk->scale_shift); + val |= clkctrl_frac; + clk_set_parent(clk, &osc_24M); + udelay(10); + __raw_writel(val, clk->scale_reg); + /* ungate */ + __raw_writel(1<<7, clk->scale_reg + 8); + /* write clkctrl_cpu */ + clk->saved_div = clkctrl_cpu; + HW_CLKCTRL_CPU_WR((HW_CLKCTRL_CPU_RD() & ~0x3f) | clkctrl_cpu); + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + if (!i) { + printk(KERN_ERR "couldn't set up CPU divisor\n"); + return -ETIMEDOUT; + } + clk_set_parent(clk, &pll_clk); + clk->saved_div = 0; + udelay(10); + } + return 0; +} + +static long cpu_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate * 18; + + rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f; + rate /= HW_CLKCTRL_CPU_RD() & 0x3f; + rate = ((rate + 9) / 10) * 10; + clk->rate = rate; + + return rate; +} + +static long cpu_round_rate(struct clk *clk, u32 rate) +{ + unsigned long r = 0; + + if (rate <= 24000) + r = 24000; + else { + u32 clkctrl_cpu = 1; + u32 clkctrl_frac; + do { + clkctrl_frac = + (pll_clk.rate*18 / clkctrl_cpu + rate/2) / rate; + if (clkctrl_frac > 35) + continue; + if (pll_clk.rate*18 / clkctrl_frac / clkctrl_cpu/10 == + rate / 10) + break; + } while (pll_clk.rate / 2 >= clkctrl_cpu++ * rate); + if (pll_clk.rate / 2 < (clkctrl_cpu - 1) * rate) + clkctrl_cpu--; + pr_debug("%s: clkctrl_cpu %d, clkctrl_frac %d\n", __func__, + clkctrl_cpu, clkctrl_frac); + if (clkctrl_frac < 18) + clkctrl_frac = 18; + if (clkctrl_frac > 35) + clkctrl_frac = 35; + + r = pll_clk.rate * 18; + r /= clkctrl_frac; + r /= clkctrl_cpu; + r = 10 * ((r + 9) / 10); + } + return r; +} + +static long emi_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate * 18; + + rate /= (__raw_readl(clk->scale_reg) >> clk->scale_shift) & 0x3f; + rate /= HW_CLKCTRL_EMI_RD() & 0x3f; + clk->rate = rate; + + return rate; +} + +static int clkseq_set_parent(struct clk *clk, struct clk *parent) +{ + int ret = -EINVAL; + int shift = 8; + + /* bypass? */ + if (parent == &osc_24M) + shift = 4; + + if (clk->bypass_reg) { + u32 hbus_mask = BM_CLKCTRL_HBUS_DIV_FRAC_EN | + BM_CLKCTRL_HBUS_DIV; + + if (clk == &cpu_clk && shift == 4) { + u32 hbus_val = HW_CLKCTRL_HBUS_RD(); + u32 cpu_val = HW_CLKCTRL_CPU_RD(); + hbus_val &= ~hbus_mask; + hbus_val |= 1; + clk->saved_div = cpu_val & BM_CLKCTRL_CPU_DIV_CPU; + cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU; + cpu_val |= 1; + __raw_writel(1 << clk->bypass_shift, + clk->bypass_reg + shift); + if (machine_is_stmp378x()) { + HW_CLKCTRL_HBUS_WR(hbus_val); + HW_CLKCTRL_CPU_WR(cpu_val); + hclk.rate = 0; + } + } else if (clk == &cpu_clk && shift == 8) { + u32 hbus_val = HW_CLKCTRL_HBUS_RD(); + u32 cpu_val = HW_CLKCTRL_CPU_RD(); + hbus_val &= ~hbus_mask; + hbus_val |= 2; + cpu_val &= ~BM_CLKCTRL_CPU_DIV_CPU; + if (clk->saved_div) + cpu_val |= clk->saved_div; + else + cpu_val |= 2; + if (machine_is_stmp378x()) { + HW_CLKCTRL_HBUS_WR(hbus_val); + HW_CLKCTRL_CPU_WR(cpu_val); + hclk.rate = 0; + } + __raw_writel(1 << clk->bypass_shift, + clk->bypass_reg + shift); + } else + __raw_writel(1 << clk->bypass_shift, + clk->bypass_reg + shift); + + ret = 0; + } + + return ret; +} + +static int hbus_set_rate(struct clk *clk, u32 rate) +{ + u8 div = 0; + int is_frac = 0; + u32 clkctrl_hbus; + struct clk *parent = clk->parent; + + pr_debug("%s: rate %d, parent rate %d\n", __func__, rate, + parent->rate); + + if (rate > parent->rate) + return -EINVAL; + + if (((parent->rate + rate/2) / rate) * rate != parent->rate && + parent->rate / rate < 32) { + pr_debug("%s: switching to fractional mode\n", __func__); + is_frac = 1; + } + + if (is_frac) + div = (32 * rate + parent->rate / 2) / parent->rate; + else + div = (parent->rate + rate - 1) / rate; + pr_debug("%s: div calculated is %d\n", __func__, div); + if (!div || div > 0x1f) + return -EINVAL; + + clk_set_parent(&cpu_clk, &osc_24M); + udelay(10); + clkctrl_hbus = __raw_readl(clk->scale_reg); + clkctrl_hbus &= ~0x3f; + clkctrl_hbus |= div; + clkctrl_hbus |= (is_frac << 5); + + __raw_writel(clkctrl_hbus, clk->scale_reg); + if (clk->busy_reg) { + int i; + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + if (!i) { + printk(KERN_ERR "couldn't set up CPU divisor\n"); + return -ETIMEDOUT; + } + } + clk_set_parent(&cpu_clk, &pll_clk); + __raw_writel(clkctrl_hbus, clk->scale_reg); + udelay(10); + return 0; +} + +static long hbus_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate; + + if (__raw_readl(clk->scale_reg) & 0x20) { + rate *= __raw_readl(clk->scale_reg) & 0x1f; + rate /= 32; + } else + rate /= __raw_readl(clk->scale_reg) & 0x1f; + clk->rate = rate; + + return rate; +} + +static int xbus_set_rate(struct clk *clk, u32 rate) +{ + u16 div = 0; + u32 clkctrl_xbus; + + pr_debug("%s: rate %d, parent rate %d\n", __func__, rate, + clk->parent->rate); + + div = (clk->parent->rate + rate - 1) / rate; + pr_debug("%s: div calculated is %d\n", __func__, div); + if (!div || div > 0x3ff) + return -EINVAL; + + clkctrl_xbus = __raw_readl(clk->scale_reg); + clkctrl_xbus &= ~0x3ff; + clkctrl_xbus |= div; + __raw_writel(clkctrl_xbus, clk->scale_reg); + if (clk->busy_reg) { + int i; + for (i = 10000; i; i--) + if (!clk_is_busy(clk)) + break; + if (!i) { + printk(KERN_ERR "couldn't set up xbus divisor\n"); + return -ETIMEDOUT; + } + } + return 0; +} + +static long xbus_get_rate(struct clk *clk) +{ + long rate = clk->parent->rate; + + rate /= __raw_readl(clk->scale_reg) & 0x3ff; + clk->rate = rate; + + return rate; +} + + +/* Clock ops */ + +static struct clk_ops std_ops = { + .enable = std_clk_enable, + .disable = std_clk_disable, + .get_rate = per_get_rate, + .set_rate = per_set_rate, + .set_parent = clkseq_set_parent, +}; + +static struct clk_ops min_ops = { + .enable = std_clk_enable, + .disable = std_clk_disable, +}; + +static struct clk_ops cpu_ops = { + .enable = std_clk_enable, + .disable = std_clk_disable, + .get_rate = cpu_get_rate, + .set_rate = cpu_set_rate, + .round_rate = cpu_round_rate, + .set_parent = clkseq_set_parent, +}; + +static struct clk_ops io_ops = { + .enable = std_clk_enable, + .disable = std_clk_disable, + .get_rate = io_get_rate, + .set_rate = io_set_rate, +}; + +static struct clk_ops hbus_ops = { + .get_rate = hbus_get_rate, + .set_rate = hbus_set_rate, +}; + +static struct clk_ops xbus_ops = { + .get_rate = xbus_get_rate, + .set_rate = xbus_set_rate, +}; + +static struct clk_ops lcdif_ops = { + .enable = std_clk_enable, + .disable = std_clk_disable, + .get_rate = lcdif_get_rate, + .set_rate = lcdif_set_rate, + .set_parent = clkseq_set_parent, +}; + +static struct clk_ops emi_ops = { + .get_rate = emi_get_rate, +}; + +/* List of on-chip clocks */ + +static struct clk osc_24M = { + .flags = FIXED_RATE | ENABLED, + .rate = 24000, +}; + +static struct clk pll_clk = { + .parent = &osc_24M, + .enable_reg = HW_CLKCTRL_PLLCTRL0_ADDR, + .enable_shift = 16, + .enable_wait = 10, + .flags = FIXED_RATE | ENABLED, + .rate = 480000, + .ops = &min_ops, +}; + +static struct clk cpu_clk = { + .parent = &pll_clk, + .scale_reg = HW_CLKCTRL_FRAC_ADDR, + .scale_shift = 0, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 7, + .busy_reg = HW_CLKCTRL_CPU_ADDR, + .busy_bit = 28, + .flags = RATE_PROPAGATES | ENABLED, + .ops = &cpu_ops, +}; + +static struct clk io_clk = { + .parent = &pll_clk, + .enable_reg = HW_CLKCTRL_FRAC_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .scale_reg = HW_CLKCTRL_FRAC_ADDR, + .scale_shift = 24, + .flags = RATE_PROPAGATES | ENABLED, + .ops = &io_ops, +}; + +static struct clk hclk = { + .parent = &cpu_clk, + .scale_reg = HW_CLKCTRL_HBUS_ADDR, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 7, + .busy_reg = HW_CLKCTRL_HBUS_ADDR, + .busy_bit = 29, + .flags = RATE_PROPAGATES | ENABLED, + .ops = &hbus_ops, +}; + +static struct clk xclk = { + .parent = &osc_24M, + .scale_reg = HW_CLKCTRL_XBUS_ADDR, + .busy_reg = HW_CLKCTRL_XBUS_ADDR, + .busy_bit = 31, + .flags = RATE_PROPAGATES | ENABLED, + .ops = &xbus_ops, +}; + +static struct clk uart_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .flags = ENABLED, + .ops = &min_ops, +}; + +static struct clk audio_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 30, + .enable_negate = 1, + .ops = &min_ops, +}; + +static struct clk pwm_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 29, + .enable_negate = 1, + .ops = &min_ops, +}; + +static struct clk dri_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 28, + .enable_negate = 1, + .ops = &min_ops, +}; + +static struct clk digctl_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 27, + .enable_negate = 1, + .ops = &min_ops, +}; + +static struct clk timer_clk = { + .parent = &xclk, + .enable_reg = HW_CLKCTRL_XTAL_ADDR, + .enable_shift = 26, + .enable_negate = 1, + .flags = ENABLED, + .ops = &min_ops, +}; + +static struct clk lcdif_clk = { + .parent = &pll_clk, + .scale_reg = HW_CLKCTRL_PIX_ADDR, + .busy_reg = HW_CLKCTRL_PIX_ADDR, + .busy_bit = 29, + .enable_reg = HW_CLKCTRL_PIX_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 1, + .flags = NEEDS_SET_PARENT, + .ops = &lcdif_ops, +}; + +static struct clk ssp_clk = { + .parent = &io_clk, + .scale_reg = HW_CLKCTRL_SSP_ADDR, + .busy_reg = HW_CLKCTRL_SSP_ADDR, + .busy_bit = 29, + .enable_reg = HW_CLKCTRL_SSP_ADDR, + .enable_shift = 31, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 5, + .enable_negate = 1, + .flags = NEEDS_SET_PARENT, + .ops = &std_ops, +}; + +static struct clk gpmi_clk = { + .parent = &io_clk, + .scale_reg = HW_CLKCTRL_GPMI_ADDR, + .busy_reg = HW_CLKCTRL_GPMI_ADDR, + .busy_bit = 29, + .enable_reg = HW_CLKCTRL_GPMI_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 4, + .flags = NEEDS_SET_PARENT, + .ops = &std_ops, +}; + +static struct clk spdif_clk = { + .parent = &pll_clk, + .enable_reg = HW_CLKCTRL_SPDIF_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .ops = &min_ops, +}; + +static struct clk emi_clk = { + .parent = &pll_clk, + .enable_reg = HW_CLKCTRL_EMI_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .scale_reg = HW_CLKCTRL_FRAC_ADDR, + .scale_shift = 8, + .busy_reg = HW_CLKCTRL_EMI_ADDR, + .busy_bit = 28, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 6, + .flags = ENABLED, + .ops = &emi_ops, +}; + +static struct clk ir_clk = { + .parent = &io_clk, + .enable_reg = HW_CLKCTRL_IR_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 3, + .ops = &min_ops, +}; + +static struct clk saif_clk = { + .parent = &pll_clk, + .scale_reg = HW_CLKCTRL_SAIF_ADDR, + .busy_reg = HW_CLKCTRL_SAIF_ADDR, + .busy_bit = 29, + .enable_reg = HW_CLKCTRL_SAIF_ADDR, + .enable_shift = 31, + .enable_negate = 1, + .bypass_reg = HW_CLKCTRL_CLKSEQ_ADDR, + .bypass_shift = 0, + .ops = &std_ops, +}; + +static struct clk usb_clk = { + .parent = &pll_clk, + .enable_reg = HW_CLKCTRL_PLLCTRL0_ADDR, + .enable_shift = 18, + .enable_negate = 1, + .ops = &min_ops, +}; + +/* list of all the clocks */ +static __initdata struct clk_lookup onchip_clks[] = { + { + .con_id = "osc_24M", + .clk = &osc_24M, + }, { + .con_id = "pll", + .clk = &pll_clk, + }, { + .con_id = "cpu", + .clk = &cpu_clk, + }, { + .con_id = "hclk", + .clk = &hclk, + }, { + .con_id = "xclk", + .clk = &xclk, + }, { + .con_id = "io", + .clk = &io_clk, + }, { + .con_id = "uart", + .clk = &uart_clk, + }, { + .con_id = "audio", + .clk = &audio_clk, + }, { + .con_id = "pwm", + .clk = &pwm_clk, + }, { + .con_id = "dri", + .clk = &dri_clk, + }, { + .con_id = "digctl", + .clk = &digctl_clk, + }, { + .con_id = "timer", + .clk = &timer_clk, + }, { + .con_id = "lcdif", + .clk = &lcdif_clk, + }, { + .con_id = "ssp", + .clk = &ssp_clk, + }, { + .con_id = "gpmi", + .clk = &gpmi_clk, + }, { + .con_id = "spdif", + .clk = &spdif_clk, + }, { + .con_id = "emi", + .clk = &emi_clk, + }, { + .con_id = "ir", + .clk = &ir_clk, + }, { + .con_id = "saif", + .clk = &saif_clk, + }, { + .con_id = "usb", + .clk = &usb_clk, + }, +}; + +static int __init propagate_rate(struct clk *clk) +{ + struct clk_lookup *cl; + + for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks); + cl++) { + if (unlikely(!clk_good(cl->clk))) + continue; + if (cl->clk->parent == clk && cl->clk->ops->get_rate) { + cl->clk->ops->get_rate(cl->clk); + if (cl->clk->flags & RATE_PROPAGATES) + propagate_rate(cl->clk); + } + } + + return 0; +} + +/* Exported API */ +unsigned long clk_get_rate(struct clk *clk) +{ + if (unlikely(!clk_good(clk))) + return 0; + + if (clk->rate != 0) + return clk->rate; + + if (clk->ops->get_rate != NULL) + return clk->ops->get_rate(clk); + + return clk_get_rate(clk->parent); +} +EXPORT_SYMBOL(clk_get_rate); + +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + if (unlikely(!clk_good(clk))) + return 0; + + if (clk->ops->round_rate) + return clk->ops->round_rate(clk, rate); + + return 0; +} +EXPORT_SYMBOL(clk_round_rate); + +static inline int close_enough(long rate1, long rate2) +{ + return rate1 && !((rate2 - rate1) * 1000 / rate1); +} + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = -EINVAL; + + if (unlikely(!clk_good(clk))) + goto out; + + if (clk->flags & FIXED_RATE || !clk->ops->set_rate) + goto out; + + else if (!close_enough(clk->rate, rate)) { + ret = clk->ops->set_rate(clk, rate); + if (ret < 0) + goto out; + clk->rate = rate; + if (clk->flags & RATE_PROPAGATES) + propagate_rate(clk); + } else + ret = 0; + +out: + return ret; +} +EXPORT_SYMBOL(clk_set_rate); + +int clk_enable(struct clk *clk) +{ + unsigned long clocks_flags; + + if (unlikely(!clk_good(clk))) + return -EINVAL; + + if (clk->parent) + clk_enable(clk->parent); + + spin_lock_irqsave(&clocks_lock, clocks_flags); + + clk->usage++; + if (clk->ops && clk->ops->enable) + clk->ops->enable(clk); + + spin_unlock_irqrestore(&clocks_lock, clocks_flags); + return 0; +} +EXPORT_SYMBOL(clk_enable); + +static void local_clk_disable(struct clk *clk) +{ + if (unlikely(!clk_good(clk))) + return; + + if (clk->usage == 0 && clk->ops->disable) + clk->ops->disable(clk); + + if (clk->parent) + local_clk_disable(clk->parent); +} + +void clk_disable(struct clk *clk) +{ + unsigned long clocks_flags; + + if (unlikely(!clk_good(clk))) + return; + + spin_lock_irqsave(&clocks_lock, clocks_flags); + + if ((--clk->usage) == 0 && clk->ops->disable) + clk->ops->disable(clk); + + spin_unlock_irqrestore(&clocks_lock, clocks_flags); + if (clk->parent) + clk_disable(clk->parent); +} +EXPORT_SYMBOL(clk_disable); + +/* Some additional API */ +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int ret = -ENODEV; + unsigned long clocks_flags; + + if (unlikely(!clk_good(clk))) + goto out; + + if (!clk->ops->set_parent) + goto out; + + spin_lock_irqsave(&clocks_lock, clocks_flags); + + ret = clk->ops->set_parent(clk, parent); + if (!ret) { + /* disable if usage count is 0 */ + local_clk_disable(parent); + + parent->usage += clk->usage; + clk->parent->usage -= clk->usage; + + /* disable if new usage count is 0 */ + local_clk_disable(clk->parent); + + clk->parent = parent; + } + spin_unlock_irqrestore(&clocks_lock, clocks_flags); + +out: + return ret; +} +EXPORT_SYMBOL(clk_set_parent); + +struct clk *clk_get_parent(struct clk *clk) +{ + if (unlikely(!clk_good(clk))) + return NULL; + return clk->parent; +} +EXPORT_SYMBOL(clk_get_parent); + +static int __init clk_init(void) +{ + struct clk_lookup *cl; + struct clk_ops *ops; + + spin_lock_init(&clocks_lock); + + for (cl = onchip_clks; cl < onchip_clks + ARRAY_SIZE(onchip_clks); + cl++) { + if (cl->clk->flags & ENABLED) + clk_enable(cl->clk); + else + local_clk_disable(cl->clk); + + ops = cl->clk->ops; + + if ((cl->clk->flags & NEEDS_INITIALIZATION) && + ops && ops->set_rate) + ops->set_rate(cl->clk, cl->clk->rate); + + if (cl->clk->flags & FIXED_RATE) { + if (cl->clk->flags & RATE_PROPAGATES) + propagate_rate(cl->clk); + } else { + if (ops && ops->get_rate) + ops->get_rate(cl->clk); + } + + if (cl->clk->flags & NEEDS_SET_PARENT) { + if (ops && ops->set_parent) + ops->set_parent(cl->clk, cl->clk->parent); + } + + clkdev_add(cl); + } + return 0; +} + +arch_initcall(clk_init); diff --git a/arch/arm/plat-stmp3xxx/clock.h b/arch/arm/plat-stmp3xxx/clock.h new file mode 100644 index 000000000000..a6611e1a3510 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/clock.h @@ -0,0 +1,61 @@ +/* + * Clock control driver for Freescale STMP37XX/STMP378X - internal header file + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __ARCH_ARM_STMX3XXX_CLOCK_H__ +#define __ARCH_ARM_STMX3XXX_CLOCK_H__ + +#ifndef __ASSEMBLER__ + +struct clk_ops { + int (*enable) (struct clk *); + int (*disable) (struct clk *); + long (*get_rate) (struct clk *); + long (*round_rate) (struct clk *, u32); + int (*set_rate) (struct clk *, u32); + int (*set_parent) (struct clk *, struct clk *); +}; + +struct clk { + struct clk *parent; + u32 rate; + u32 flags; + u8 scale_shift; + u8 enable_shift; + u8 bypass_shift; + u8 busy_bit; + s8 usage; + int enable_wait; + int enable_negate; + u32 saved_div; + void __iomem *enable_reg; + void __iomem *scale_reg; + void __iomem *bypass_reg; + void __iomem *busy_reg; + struct clk_ops *ops; +}; + +#endif /* __ASSEMBLER__ */ + +/* Flags */ +#define RATE_PROPAGATES (1<<0) +#define NEEDS_INITIALIZATION (1<<1) +#define PARENT_SET_RATE (1<<2) +#define FIXED_RATE (1<<3) +#define ENABLED (1<<4) +#define NEEDS_SET_PARENT (1<<5) + +#endif diff --git a/arch/arm/plat-stmp3xxx/core.c b/arch/arm/plat-stmp3xxx/core.c new file mode 100644 index 000000000000..6e2fef1639b0 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/core.c @@ -0,0 +1,127 @@ +/* + * Freescale STMP37XX/STMP378X core routines + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> + +#include <mach/stmp3xxx.h> +#include <mach/dma.h> +#include <mach/regs-clkctrl.h> + +static int __stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) +{ + u32 c; + int timeout; + + /* the process of software reset of IP block is done + in several steps: + + - clear SFTRST and wait for block is enabled; + - clear clock gating (CLKGATE bit); + - set the SFTRST again and wait for block is in reset; + - clear SFTRST and wait for reset completion. + */ + c = __raw_readl(hwreg); + c &= ~(1<<31); /* clear SFTRST */ + __raw_writel(c, hwreg); + for (timeout = 1000000; timeout > 0; timeout--) + /* still in SFTRST state ? */ + if ((__raw_readl(hwreg) & (1<<31)) == 0) + break; + if (timeout <= 0) { + printk(KERN_ERR"%s(%p): timeout when enabling\n", + __func__, hwreg); + return -ETIME; + } + + c = __raw_readl(hwreg); + c &= ~(1<<30); /* clear CLKGATE */ + __raw_writel(c, hwreg); + + if (!just_enable) { + c = __raw_readl(hwreg); + c |= (1<<31); /* now again set SFTRST */ + __raw_writel(c, hwreg); + for (timeout = 1000000; timeout > 0; timeout--) + /* poll until CLKGATE set */ + if (__raw_readl(hwreg) & (1<<30)) + break; + if (timeout <= 0) { + printk(KERN_ERR"%s(%p): timeout when resetting\n", + __func__, hwreg); + return -ETIME; + } + + c = __raw_readl(hwreg); + c &= ~(1<<31); /* clear SFTRST */ + __raw_writel(c, hwreg); + for (timeout = 1000000; timeout > 0; timeout--) + /* still in SFTRST state ? */ + if ((__raw_readl(hwreg) & (1<<31)) == 0) + break; + if (timeout <= 0) { + printk(KERN_ERR"%s(%p): timeout when enabling " + "after reset\n", __func__, hwreg); + return -ETIME; + } + + c = __raw_readl(hwreg); + c &= ~(1<<30); /* clear CLKGATE */ + __raw_writel(c, hwreg); + } + for (timeout = 1000000; timeout > 0; timeout--) + /* still in SFTRST state ? */ + if ((__raw_readl(hwreg) & (1<<30)) == 0) + break; + + if (timeout <= 0) { + printk(KERN_ERR"%s(%p): timeout when unclockgating\n", + __func__, hwreg); + return -ETIME; + } + + return 0; +} + +int stmp3xxx_reset_block(void __iomem *hwreg, int just_enable) +{ + int try = 10; + int r; + + while (try--) { + r = __stmp3xxx_reset_block(hwreg, just_enable); + if (!r) + break; + pr_debug("%s: try %d failed\n", __func__, 10 - try); + } + return r; +} +EXPORT_SYMBOL(stmp3xxx_reset_block); + +struct platform_device stmp3xxx_dbguart = { + .name = "stmp3xxx-dbguart", + .id = -1, +}; + +void __init stmp3xxx_init(void) +{ + /* Turn off auto-slow and other tricks */ + HW_CLKCTRL_HBUS_CLR(0x07f00000U); + + stmp3xxx_dma_init(); +} diff --git a/arch/arm/plat-stmp3xxx/dma.c b/arch/arm/plat-stmp3xxx/dma.c new file mode 100644 index 000000000000..cf42de05e568 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/dma.c @@ -0,0 +1,462 @@ +/* + * DMA helper routines for Freescale STMP37XX/STMP378X + * + * Author: dmitry pervushin <dpervushin@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/sysdev.h> +#include <linux/cpufreq.h> + +#include <asm/page.h> + +#include <mach/dma.h> +#include <mach/regs-apbx.h> +#include <mach/regs-apbh.h> + +static const size_t pool_item_size = sizeof(struct stmp3xxx_dma_command); +static const size_t pool_alignment = 8; +static struct stmp3xxx_dma_user { + void *pool; + int inuse; + const char *name; +} channels[MAX_DMA_CHANNELS]; + +static inline int dmach(int ch) +{ + return ch % 16; +} + +static inline int dmabus(int ch) +{ + return ch / 16; +} + +#define IS_VALID_CHANNEL(ch) ((ch) >= 0 && (ch) < MAX_DMA_CHANNELS) +#define IS_USED(ch) (channels[ch].inuse) + +int stmp3xxx_dma_request(int ch, struct device *dev, const char *name) +{ + struct stmp3xxx_dma_user *user; + int err = 0; + + user = channels + ch; + if (!IS_VALID_CHANNEL(ch)) { + err = -ENODEV; + goto out; + } + if (IS_USED(ch)) { + err = -EBUSY; + goto out; + } + /* Create a pool to allocate dma commands from */ + user->pool = dma_pool_create(name, dev, pool_item_size, + pool_alignment, PAGE_SIZE); + if (user->pool == NULL) { + err = -ENOMEM; + goto out; + } + user->name = name; + user->inuse++; +out: + return err; +} +EXPORT_SYMBOL(stmp3xxx_dma_request); + +int stmp3xxx_dma_release(int ch) +{ + struct stmp3xxx_dma_user *user = channels + ch; + int err = 0; + + if (!IS_VALID_CHANNEL(ch)) { + err = -ENODEV; + goto out; + } + if (!IS_USED(ch)) { + err = -EBUSY; + goto out; + } + BUG_ON(user->pool == NULL); + dma_pool_destroy(user->pool); + user->inuse--; +out: + return err; +} +EXPORT_SYMBOL(stmp3xxx_dma_release); + +int stmp3xxx_dma_read_semaphore(int channel) +{ + int sem = -1; + + switch (dmabus(channel)) { + case STMP3XXX_BUS_APBH: + sem = + (HW_APBH_CHn_SEMA_RD(dmach(channel)) & + BM_APBH_CHn_SEMA_PHORE) >> BP_APBH_CHn_SEMA_PHORE; + break; + + case STMP3XXX_BUS_APBX: + sem = + (HW_APBX_CHn_SEMA_RD(dmach(channel)) & + BM_APBX_CHn_SEMA_PHORE) >> BP_APBX_CHn_SEMA_PHORE; + break; + default: + BUG(); + } + return sem; +} +EXPORT_SYMBOL(stmp3xxx_dma_read_semaphore); + +int stmp3xxx_dma_allocate_command(int channel, + struct stmp3xxx_dma_descriptor *descriptor) +{ + struct stmp3xxx_dma_user *user = channels + channel; + int err = 0; + + if (!IS_VALID_CHANNEL(channel)) { + err = -ENODEV; + goto out; + } + if (!IS_USED(channel)) { + err = -EBUSY; + goto out; + } + if (descriptor == NULL) { + err = -EINVAL; + goto out; + } + + /* Allocate memory for a command from the buffer */ + descriptor->command = + dma_pool_alloc(user->pool, GFP_KERNEL, &descriptor->handle); + + /* Check it worked */ + if (!descriptor->command) { + err = -ENOMEM; + goto out; + } + + memset(descriptor->command, 0, pool_item_size); +out: + WARN_ON(err); + return err; +} +EXPORT_SYMBOL(stmp3xxx_dma_allocate_command); + +int stmp3xxx_dma_free_command(int channel, + struct stmp3xxx_dma_descriptor *descriptor) +{ + int err = 0; + + if (!IS_VALID_CHANNEL(channel)) { + err = -ENODEV; + goto out; + } + if (!IS_USED(channel)) { + err = -EBUSY; + goto out; + } + + /* Return the command memory to the pool */ + dma_pool_free(channels[channel].pool, descriptor->command, + descriptor->handle); + + /* Initialise descriptor so we're not tempted to use it */ + descriptor->command = NULL; + descriptor->handle = 0; + descriptor->virtual_buf_ptr = NULL; + descriptor->next_descr = NULL; + + WARN_ON(err); +out: + return err; +} +EXPORT_SYMBOL(stmp3xxx_dma_free_command); + +void stmp3xxx_dma_go(int channel, + struct stmp3xxx_dma_descriptor *head, u32 semaphore) +{ + int ch = dmach(channel); + + switch (dmabus(channel)) { + case STMP3XXX_BUS_APBH: + /* Set next command */ + HW_APBH_CHn_NXTCMDAR_WR(ch, head->handle); + /* Set counting semaphore (kicks off transfer). Assumes + peripheral has been set up correctly */ + HW_APBH_CHn_SEMA_WR(ch, semaphore); + break; + + case STMP3XXX_BUS_APBX: + /* Set next command */ + HW_APBX_CHn_NXTCMDAR_WR(ch, head->handle); + /* Set counting semaphore (kicks off transfer). Assumes + peripheral has been set up correctly */ + HW_APBX_CHn_SEMA_WR(ch, semaphore); + break; + } +} +EXPORT_SYMBOL(stmp3xxx_dma_go); + +int stmp3xxx_dma_running(int channel) +{ + switch (dmabus(channel)) { + case STMP3XXX_BUS_APBH: + return HW_APBH_CHn_SEMA_RD(dmach(channel)) & + BM_APBH_CHn_SEMA_PHORE; + + case STMP3XXX_BUS_APBX: + return HW_APBX_CHn_SEMA_RD(dmach(channel)) & + BM_APBX_CHn_SEMA_PHORE; + + default: + BUG(); + return 0; + } +} +EXPORT_SYMBOL(stmp3xxx_dma_running); + +/* + * Circular dma chain management + */ +void stmp3xxx_dma_free_chain(struct stmp37xx_circ_dma_chain *chain) +{ + int i; + + for (i = 0; i < chain->total_count; i++) + stmp3xxx_dma_free_command( + STMP3xxx_DMA(chain->channel, chain->bus), + &chain->chain[i]); +} +EXPORT_SYMBOL(stmp3xxx_dma_free_chain); + +int stmp3xxx_dma_make_chain(int ch, struct stmp37xx_circ_dma_chain *chain, + struct stmp3xxx_dma_descriptor descriptors[], + unsigned items) +{ + int i; + int err = 0; + + if (items == 0) + return err; + + for (i = 0; i < items; i++) { + err = stmp3xxx_dma_allocate_command(ch, &descriptors[i]); + if (err) { + WARN_ON(err); + /* + * Couldn't allocate the whole chain. + * deallocate what has been allocated + */ + if (i) { + do { + stmp3xxx_dma_free_command(ch, + &descriptors + [i]); + } while (i-- >= 0); + } + return err; + } + + /* link them! */ + if (i > 0) { + descriptors[i - 1].next_descr = &descriptors[i]; + descriptors[i - 1].command->next = + descriptors[i].handle; + } + } + + /* make list circular */ + descriptors[items - 1].next_descr = &descriptors[0]; + descriptors[items - 1].command->next = descriptors[0].handle; + + chain->total_count = items; + chain->chain = descriptors; + chain->free_index = 0; + chain->active_index = 0; + chain->cooked_index = 0; + chain->free_count = items; + chain->active_count = 0; + chain->cooked_count = 0; + chain->bus = dmabus(ch); + chain->channel = dmach(ch); + return err; +} +EXPORT_SYMBOL(stmp3xxx_dma_make_chain); + +void stmp37xx_circ_clear_chain(struct stmp37xx_circ_dma_chain *chain) +{ + BUG_ON(stmp3xxx_dma_running(STMP3xxx_DMA(chain->channel, chain->bus)) > + 0); + chain->free_index = 0; + chain->active_index = 0; + chain->cooked_index = 0; + chain->free_count = chain->total_count; + chain->active_count = 0; + chain->cooked_count = 0; +} +EXPORT_SYMBOL(stmp37xx_circ_clear_chain); + +void stmp37xx_circ_advance_free(struct stmp37xx_circ_dma_chain *chain, + unsigned count) +{ + BUG_ON(chain->cooked_count < count); + + chain->cooked_count -= count; + chain->cooked_index += count; + chain->cooked_index %= chain->total_count; + chain->free_count += count; +} +EXPORT_SYMBOL(stmp37xx_circ_advance_free); + +void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, + unsigned count) +{ + BUG_ON(chain->free_count < count); + + chain->free_count -= count; + chain->free_index += count; + chain->free_index %= chain->total_count; + chain->active_count += count; + + switch (chain->bus) { + case STMP3XXX_BUS_APBH: + /* Set counting semaphore (kicks off transfer). Assumes + peripheral has been set up correctly */ + HW_APBH_CHn_SEMA_CLR(chain->channel, + BM_APBH_CHn_SEMA_INCREMENT_SEMA); + HW_APBH_CHn_SEMA_SET(chain->channel, + BF_APBH_CHn_SEMA_INCREMENT_SEMA(count)); + break; + + case STMP3XXX_BUS_APBX: + /* Set counting semaphore (kicks off transfer). Assumes + peripheral has been set up correctly */ + HW_APBX_CHn_SEMA_CLR(chain->channel, + BM_APBX_CHn_SEMA_INCREMENT_SEMA); + HW_APBX_CHn_SEMA_SET(chain->channel, + BF_APBX_CHn_SEMA_INCREMENT_SEMA(count)); + break; + + default: + BUG(); + } +} +EXPORT_SYMBOL(stmp37xx_circ_advance_active); + +unsigned stmp37xx_circ_advance_cooked(struct stmp37xx_circ_dma_chain *chain) +{ + unsigned cooked; + + cooked = chain->active_count - + stmp3xxx_dma_read_semaphore(STMP3xxx_DMA(chain->channel, chain->bus)); + + chain->active_count -= cooked; + chain->active_index += cooked; + chain->active_index %= chain->total_count; + + chain->cooked_count += cooked; + + return cooked; +} +EXPORT_SYMBOL(stmp37xx_circ_advance_cooked); + +void stmp3xxx_dma_set_alt_target(int channel, int function) +{ +#if defined(CONFIG_ARCH_STMP37XX) + unsigned bits = 4; +#elif defined(CONFIG_ARCH_STMP378X) + unsigned bits = 2; +#else +#error wrong arch +#endif + int shift = dmach(channel) * bits; + unsigned mask = (1<<bits) - 1; + + BUG_ON(function < 0 || function >= (1<<bits)); + pr_debug("%s: channel = %d, using mask %x, " + "shift = %d\n", __func__, channel, mask, shift); + + switch (dmabus(channel)) { + case STMP3XXX_BUS_APBH: + HW_APBH_DEVSEL_CLR(mask<<shift); + HW_APBH_DEVSEL_SET(function<<shift); + break; + case STMP3XXX_BUS_APBX: + HW_APBX_DEVSEL_CLR(mask<<shift); + HW_APBX_DEVSEL_SET(function<<shift); + break; + default: + BUG(); + } +} +EXPORT_SYMBOL(stmp3xxx_dma_set_alt_target); + +void stmp3xxx_dma_suspend(void) +{ + HW_APBH_CTRL0_SET(BM_APBH_CTRL0_CLKGATE); + HW_APBX_CTRL0_SET(BM_APBX_CTRL0_CLKGATE); +} + +void stmp3xxx_dma_resume(void) +{ + HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST); + HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST); +} + +#ifdef CONFIG_CPU_FREQ + +struct dma_notifier_block { + struct notifier_block nb; + void *data; +}; + +static int dma_cpufreq_notifier(struct notifier_block *self, + unsigned long phase, void *p) +{ + switch (phase) { + case CPUFREQ_POSTCHANGE: + stmp3xxx_dma_resume(); + break; + + case CPUFREQ_PRECHANGE: + stmp3xxx_dma_suspend(); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +static struct dma_notifier_block dma_cpufreq_nb = { + .nb = { + .notifier_call = dma_cpufreq_notifier, + }, +}; +#endif /* CONFIG_CPU_FREQ */ + +void __init stmp3xxx_dma_init(void) +{ + HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST); + HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST); +#ifdef CONFIG_CPU_FREQ + cpufreq_register_notifier(&dma_cpufreq_nb.nb, + CPUFREQ_TRANSITION_NOTIFIER); +#endif /* CONFIG_CPU_FREQ */ + +} diff --git a/arch/arm/plat-stmp3xxx/irq.c b/arch/arm/plat-stmp3xxx/irq.c new file mode 100644 index 000000000000..cb3659096681 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/irq.c @@ -0,0 +1,59 @@ +/* + * Freescale STMP37XX/STMP378X common interrupt handling code + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/sysdev.h> + +#include <mach/stmp3xxx.h> +#include <mach/regs-icoll.h> + +void __init stmp3xxx_init_irq(struct irq_chip *chip) +{ + unsigned int i; + + /* Reset the interrupt controller */ + HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_CLKGATE); + udelay(10); + HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_SFTRST); + udelay(10); + HW_ICOLL_CTRL_SET(BM_ICOLL_CTRL_SFTRST); + while (!(HW_ICOLL_CTRL_RD() & BM_ICOLL_CTRL_CLKGATE)) + continue; + HW_ICOLL_CTRL_CLR(BM_ICOLL_CTRL_SFTRST | BM_ICOLL_CTRL_CLKGATE); + + /* Disable all interrupts initially */ + for (i = 0; i < NR_REAL_IRQS; i++) { + chip->mask(i); + set_irq_chip(i, chip); + set_irq_handler(i, handle_level_irq); + set_irq_flags(i, IRQF_VALID | IRQF_PROBE); + } + + /* Ensure vector is cleared */ + HW_ICOLL_LEVELACK_WR(1); + HW_ICOLL_LEVELACK_WR(2); + HW_ICOLL_LEVELACK_WR(4); + HW_ICOLL_LEVELACK_WR(8); + + HW_ICOLL_VECTOR_WR(0); + /* Barrier */ + (void) HW_ICOLL_STAT_RD(); +} + diff --git a/arch/arm/plat-stmp3xxx/pinmux.c b/arch/arm/plat-stmp3xxx/pinmux.c new file mode 100644 index 000000000000..9b28cc83f31c --- /dev/null +++ b/arch/arm/plat-stmp3xxx/pinmux.c @@ -0,0 +1,545 @@ +/* + * Freescale STMP378X/STMP378X Pin Multiplexing + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sysdev.h> +#include <linux/string.h> +#include <linux/bitops.h> +#include <linux/sysdev.h> +#include <linux/irq.h> + +#include <mach/hardware.h> +#include <mach/regs-pinctrl.h> +#include <mach/pins.h> +#include <mach/pinmux.h> + +#define NR_BANKS ARRAY_SIZE(pinmux_banks) +static struct stmp3xxx_pinmux_bank pinmux_banks[] = { + [0] = { + .hw_muxsel = { + HW_PINCTRL_MUXSEL0_ADDR, + HW_PINCTRL_MUXSEL1_ADDR + }, + .hw_drive = { + HW_PINCTRL_DRIVE0_ADDR, + HW_PINCTRL_DRIVE1_ADDR, + HW_PINCTRL_DRIVE2_ADDR, + HW_PINCTRL_DRIVE3_ADDR + }, + .hw_pull = HW_PINCTRL_PULL0_ADDR, + .functions = { 0x0, 0x1, 0x2, 0x3 }, + .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, + + .hw_gpio_read = HW_PINCTRL_DIN0_ADDR, + .hw_gpio_set = HW_PINCTRL_DOUT0_ADDR + HW_STMP3xxx_SET, + .hw_gpio_clr = HW_PINCTRL_DOUT0_ADDR + HW_STMP3xxx_CLR, + .hw_gpio_doe = HW_PINCTRL_DOE0_ADDR, + .irq = IRQ_GPIO0, + + .pin2irq = HW_PINCTRL_PIN2IRQ0_ADDR, + .irqstat = HW_PINCTRL_IRQSTAT0_ADDR, + .irqlevel = HW_PINCTRL_IRQLEVEL0_ADDR, + .irqpolarity = HW_PINCTRL_IRQPOL0_ADDR, + .irqen = HW_PINCTRL_IRQEN0_ADDR, + }, + [1] = { + .hw_muxsel = { + HW_PINCTRL_MUXSEL2_ADDR, + HW_PINCTRL_MUXSEL3_ADDR + }, + .hw_drive = { + HW_PINCTRL_DRIVE4_ADDR, + HW_PINCTRL_DRIVE5_ADDR, + HW_PINCTRL_DRIVE6_ADDR, + HW_PINCTRL_DRIVE7_ADDR + }, + .hw_pull = HW_PINCTRL_PULL1_ADDR, + .functions = { 0x0, 0x1, 0x2, 0x3 }, + .strengths = { 0x0, 0x1, 0x2, 0x3, 0xff }, + + .hw_gpio_read = HW_PINCTRL_DIN1_ADDR, + .hw_gpio_set = HW_PINCTRL_DOUT1_ADDR + HW_STMP3xxx_SET, + .hw_gpio_clr = HW_PINCTRL_DOUT1_ADDR + HW_STMP3xxx_CLR, + .hw_gpio_doe = HW_PINCTRL_DOE1_ADDR, + .irq = IRQ_GPIO1, + + .pin2irq = HW_PINCTRL_PIN2IRQ1_ADDR, + .irqstat = HW_PINCTRL_IRQSTAT1_ADDR, + .irqlevel = HW_PINCTRL_IRQLEVEL1_ADDR, + .irqpolarity = HW_PINCTRL_IRQPOL1_ADDR, + .irqen = HW_PINCTRL_IRQEN1_ADDR, + }, + [2] = { + .hw_muxsel = { + HW_PINCTRL_MUXSEL4_ADDR, + HW_PINCTRL_MUXSEL5_ADDR, + }, + .hw_drive = { + HW_PINCTRL_DRIVE8_ADDR, + HW_PINCTRL_DRIVE9_ADDR, + HW_PINCTRL_DRIVE10_ADDR, + HW_PINCTRL_DRIVE11_ADDR, + }, + .hw_pull = HW_PINCTRL_PULL2_ADDR, + .functions = { 0x0, 0x1, 0x2, 0x3 }, + .strengths = { 0x0, 0x1, 0x2, 0x1, 0x2 }, + + .hw_gpio_read = HW_PINCTRL_DIN2_ADDR, + .hw_gpio_set = HW_PINCTRL_DOUT2_ADDR + HW_STMP3xxx_SET, + .hw_gpio_clr = HW_PINCTRL_DOUT2_ADDR + HW_STMP3xxx_CLR, + .hw_gpio_doe = HW_PINCTRL_DOE2_ADDR, + .irq = IRQ_GPIO2, + + .pin2irq = HW_PINCTRL_PIN2IRQ2_ADDR, + .irqstat = HW_PINCTRL_IRQSTAT2_ADDR, + .irqlevel = HW_PINCTRL_IRQLEVEL2_ADDR, + .irqpolarity = HW_PINCTRL_IRQPOL2_ADDR, + .irqen = HW_PINCTRL_IRQEN2_ADDR, + }, + [3] = { + .hw_muxsel = { + HW_PINCTRL_MUXSEL6_ADDR, + HW_PINCTRL_MUXSEL7_ADDR, + }, + .hw_drive = { + HW_PINCTRL_DRIVE12_ADDR, + HW_PINCTRL_DRIVE13_ADDR, + HW_PINCTRL_DRIVE14_ADDR, + NULL, + }, + .hw_pull = HW_PINCTRL_PULL3_ADDR, + .functions = {0x0, 0x1, 0x2, 0x3}, + .strengths = {0x0, 0x1, 0x2, 0x3, 0xff}, + }, +}; + +static inline struct stmp3xxx_pinmux_bank * +stmp3xxx_pinmux_bank(unsigned id, unsigned *bank, unsigned *pin) +{ + unsigned b, p; + + b = STMP3XXX_PINID_TO_BANK(id); + p = STMP3XXX_PINID_TO_PINNUM(id); + BUG_ON(b >= NR_BANKS); + if (bank) + *bank = b; + if (pin) + *pin = p; + return &pinmux_banks[b]; +} + +/* Check if requested pin is owned by caller */ +static int stmp3xxx_check_pin(unsigned id, const char *label) +{ + unsigned pin; + struct stmp3xxx_pinmux_bank *pm = stmp3xxx_pinmux_bank(id, NULL, &pin); + + if (!test_bit(pin, &pm->pin_map)) { + printk(KERN_WARNING + "%s: Accessing free pin %x, caller %s\n", + __func__, id, label); + + return -EINVAL; + } + + if (label && pm->pin_labels[pin] && + strcmp(label, pm->pin_labels[pin])) { + printk(KERN_WARNING + "%s: Wrong pin owner %x, caller %s owner %s\n", + __func__, id, label, pm->pin_labels[pin]); + + return -EINVAL; + } + return 0; +} + +void stmp3xxx_pin_strength(unsigned id, enum pin_strength strength, + const char *label) +{ + struct stmp3xxx_pinmux_bank *pbank; + void __iomem *hwdrive; + u32 shift, val; + u32 bank, pin; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + pr_debug("%s: label %s bank %d pin %d strength %d\n", __func__, label, + bank, pin, strength); + + hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; + shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; + val = pbank->strengths[strength]; + if (val == 0xff) { + printk(KERN_WARNING + "%s: strength is not supported for bank %d, caller %s", + __func__, bank, label); + return; + } + + if (stmp3xxx_check_pin(id, label)) + return; + + pr_debug("%s: writing 0x%x to 0x%p register\n", __func__, + val << shift, hwdrive); + __raw_writel(HW_DRIVE_PINDRV_MASK << shift, hwdrive + HW_STMP3xxx_CLR); + __raw_writel(val << shift, hwdrive + HW_STMP3xxx_SET); +} + +void stmp3xxx_pin_voltage(unsigned id, enum pin_voltage voltage, + const char *label) +{ + struct stmp3xxx_pinmux_bank *pbank; + void __iomem *hwdrive; + u32 shift; + u32 bank, pin; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + pr_debug("%s: label %s bank %d pin %d voltage %d\n", __func__, label, + bank, pin, voltage); + + hwdrive = pbank->hw_drive[pin / HW_DRIVE_PIN_NUM]; + shift = (pin % HW_DRIVE_PIN_NUM) * HW_DRIVE_PIN_LEN; + + if (stmp3xxx_check_pin(id, label)) + return; + + pr_debug("%s: changing 0x%x bit in 0x%p register\n", + __func__, HW_DRIVE_PINV_MASK << shift, hwdrive); + if (voltage == PIN_1_8V) + __raw_writel(HW_DRIVE_PINV_MASK << shift, + hwdrive + HW_STMP3xxx_CLR); + else + __raw_writel(HW_DRIVE_PINV_MASK << shift, + hwdrive + HW_STMP3xxx_SET); +} + +void stmp3xxx_pin_pullup(unsigned id, int enable, const char *label) +{ + struct stmp3xxx_pinmux_bank *pbank; + void __iomem *hwpull; + u32 bank, pin; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + pr_debug("%s: label %s bank %d pin %d enable %d\n", __func__, label, + bank, pin, enable); + + hwpull = pbank->hw_pull; + + if (stmp3xxx_check_pin(id, label)) + return; + + pr_debug("%s: changing 0x%x bit in 0x%p register\n", + __func__, 1 << pin, hwpull); + __raw_writel(1 << pin, + hwpull + (enable ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); +} + +int stmp3xxx_request_pin(unsigned id, enum pin_fun fun, const char *label) +{ + struct stmp3xxx_pinmux_bank *pbank; + u32 bank, pin; + int ret = 0; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + pr_debug("%s: label %s bank %d pin %d fun %d\n", __func__, label, + bank, pin, fun); + + if (test_bit(pin, &pbank->pin_map)) { + printk(KERN_WARNING + "%s: CONFLICT DETECTED pin %d:%d caller %s owner %s\n", + __func__, bank, pin, label, pbank->pin_labels[pin]); + return -EBUSY; + } + + set_bit(pin, &pbank->pin_map); + pbank->pin_labels[pin] = label; + + stmp3xxx_set_pin_type(id, fun); + + return ret; +} + +void stmp3xxx_set_pin_type(unsigned id, enum pin_fun fun) +{ + struct stmp3xxx_pinmux_bank *pbank; + void __iomem *hwmux; + u32 shift, val; + u32 bank, pin; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + + hwmux = pbank->hw_muxsel[pin / HW_MUXSEL_PIN_NUM]; + shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; + + val = pbank->functions[fun]; + shift = (pin % HW_MUXSEL_PIN_NUM) * HW_MUXSEL_PIN_LEN; + pr_debug("%s: writing 0x%x to 0x%p register\n", + __func__, val << shift, hwmux); + __raw_writel(HW_MUXSEL_PINFUN_MASK << shift, hwmux + HW_STMP3xxx_CLR); + __raw_writel(val << shift, hwmux + HW_STMP3xxx_SET); +} + +void stmp3xxx_release_pin(unsigned id, const char *label) +{ + struct stmp3xxx_pinmux_bank *pbank; + u32 bank, pin; + + pbank = stmp3xxx_pinmux_bank(id, &bank, &pin); + pr_debug("%s: label %s bank %d pin %d\n", __func__, label, bank, pin); + + if (stmp3xxx_check_pin(id, label)) + return; + + clear_bit(pin, &pbank->pin_map); + pbank->pin_labels[pin] = NULL; +} + +int stmp3xxx_request_pin_group(struct pin_group *pin_group, const char *label) +{ + struct pin_desc *pin; + int p; + int err = 0; + + /* Allocate and configure pins */ + for (p = 0; p < pin_group->nr_pins; p++) { + pr_debug("%s: #%d\n", __func__, p); + pin = &pin_group->pins[p]; + + err = stmp3xxx_request_pin(pin->id, pin->fun, label); + if (err) + goto out_err; + + stmp3xxx_pin_strength(pin->id, pin->strength, label); + stmp3xxx_pin_voltage(pin->id, pin->voltage, label); + stmp3xxx_pin_pullup(pin->id, pin->pullup, label); + } + + return 0; + +out_err: + /* Release allocated pins in case of error */ + while (--p >= 0) { + pr_debug("%s: releasing #%d\n", __func__, p); + stmp3xxx_release_pin(pin_group->pins[p].id, label); + } + return err; +} +EXPORT_SYMBOL(stmp3xxx_request_pin_group); + +void stmp3xxx_release_pin_group(struct pin_group *pin_group, const char *label) +{ + struct pin_desc *pin; + int p; + + for (p = 0; p < pin_group->nr_pins; p++) { + pin = &pin_group->pins[p]; + stmp3xxx_release_pin(pin->id, label); + } +} +EXPORT_SYMBOL(stmp3xxx_release_pin_group); + +static int stmp3xxx_irq_to_gpio(int irq, + struct stmp3xxx_pinmux_bank **bank, unsigned *gpio) +{ + struct stmp3xxx_pinmux_bank *pm; + + for (pm = pinmux_banks; pm < pinmux_banks + NR_BANKS; pm++) + if (pm->virq <= irq && irq < pm->virq + 32) { + *bank = pm; + *gpio = irq - pm->virq; + return 0; + } + return -ENOENT; +} + +static int stmp3xxx_set_irqtype(unsigned irq, unsigned type) +{ + struct stmp3xxx_pinmux_bank *pm; + unsigned gpio; + int l, p; + + stmp3xxx_irq_to_gpio(irq, &pm, &gpio); + switch (type) { + case IRQ_TYPE_EDGE_RISING: + l = 0; p = 1; break; + case IRQ_TYPE_EDGE_FALLING: + l = 0; p = 0; break; + case IRQ_TYPE_LEVEL_HIGH: + l = 1; p = 1; break; + case IRQ_TYPE_LEVEL_LOW: + l = 1; p = 0; break; + default: + pr_debug("%s: Incorrect GPIO interrupt type 0x%x\n", + __func__, type); + return -ENXIO; + } + __raw_writel(1 << gpio, + pm->irqlevel + (l ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); + __raw_writel(1 << gpio, + pm->irqpolarity + (p ? HW_STMP3xxx_SET : HW_STMP3xxx_CLR)); + return 0; +} + +static void stmp3xxx_pin_ack_irq(unsigned irq) +{ + u32 stat; + struct stmp3xxx_pinmux_bank *pm; + unsigned gpio; + + stmp3xxx_irq_to_gpio(irq, &pm, &gpio); + stat = __raw_readl(pm->irqstat) & (1<<gpio); + __raw_writel(stat, pm->irqstat + HW_STMP3xxx_CLR); +} + +static void stmp3xxx_pin_mask_irq(unsigned irq) +{ + struct stmp3xxx_pinmux_bank *pm; + unsigned gpio; + + stmp3xxx_irq_to_gpio(irq, &pm, &gpio); + __raw_writel(1 << gpio, pm->irqen + HW_STMP3xxx_CLR); + __raw_writel(1 << gpio, pm->pin2irq + HW_STMP3xxx_CLR); +} + +static void stmp3xxx_pin_unmask_irq(unsigned irq) +{ + struct stmp3xxx_pinmux_bank *pm; + unsigned gpio; + + stmp3xxx_irq_to_gpio(irq, &pm, &gpio); + __raw_writel(1 << gpio, pm->irqen + HW_STMP3xxx_SET); + __raw_writel(1 << gpio, pm->pin2irq + HW_STMP3xxx_SET); +} + +static inline +struct stmp3xxx_pinmux_bank *to_pinmux_bank(struct gpio_chip *chip) +{ + return container_of(chip, struct stmp3xxx_pinmux_bank, chip); +} + +static int stmp3xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); + return pm->virq + offset; +} + +static int stmp3xxx_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); + unsigned v; + + v = __raw_readl(pm->hw_gpio_read) & (1 << offset); + return v ? 1 : 0; +} + +static void stmp3xxx_gpio_set(struct gpio_chip *chip, unsigned offset, int v) +{ + struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); + + __raw_writel(1 << offset, v ? pm->hw_gpio_set : pm->hw_gpio_clr); +} + +static int stmp3xxx_gpio_output(struct gpio_chip *chip, unsigned offset, int v) +{ + struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); + + __raw_writel(1 << offset, pm->hw_gpio_doe + HW_STMP3xxx_SET); + stmp3xxx_gpio_set(chip, offset, v); + return 0; +} + +static int stmp3xxx_gpio_input(struct gpio_chip *chip, unsigned offset) +{ + struct stmp3xxx_pinmux_bank *pm = to_pinmux_bank(chip); + + __raw_writel(1 << offset, pm->hw_gpio_doe + HW_STMP3xxx_CLR); + return 0; +} + +static int stmp3xxx_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + return stmp3xxx_request_pin(chip->base + offset, PIN_GPIO, "gpio"); +} + +static void stmp3xxx_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + stmp3xxx_release_pin(chip->base + offset, "gpio"); +} + +static void stmp3xxx_gpio_irq(u32 irq, struct irq_desc *desc) +{ + struct stmp3xxx_pinmux_bank *pm = get_irq_data(irq); + int gpio_irq = pm->virq; + u32 stat = __raw_readl(pm->irqstat); + + while (stat) { + if (stat & 1) + irq_desc[gpio_irq].handle_irq(gpio_irq, + &irq_desc[gpio_irq]); + gpio_irq++; + stat >>= 1; + } +} + +static struct irq_chip gpio_irq_chip = { + .ack = stmp3xxx_pin_ack_irq, + .mask = stmp3xxx_pin_mask_irq, + .unmask = stmp3xxx_pin_unmask_irq, + .set_type = stmp3xxx_set_irqtype, +}; + +int __init stmp3xxx_pinmux_init(int virtual_irq_start) +{ + int b, r = 0; + struct stmp3xxx_pinmux_bank *pm; + int virq; + + for (b = 0; b < 3; b++) { + /* only banks 0,1,2 are allowed to GPIO */ + pm = pinmux_banks + b; + pm->chip.base = 32 * b; + pm->chip.ngpio = 32; + pm->chip.owner = THIS_MODULE; + pm->chip.can_sleep = 1; + pm->chip.exported = 1; + pm->chip.to_irq = stmp3xxx_gpio_to_irq; + pm->chip.direction_input = stmp3xxx_gpio_input; + pm->chip.direction_output = stmp3xxx_gpio_output; + pm->chip.get = stmp3xxx_gpio_get; + pm->chip.set = stmp3xxx_gpio_set; + pm->chip.request = stmp3xxx_gpio_request; + pm->chip.free = stmp3xxx_gpio_free; + pm->virq = virtual_irq_start + b * 32; + + for (virq = pm->virq; virq < pm->virq; virq++) { + gpio_irq_chip.mask(virq); + set_irq_chip(virq, &gpio_irq_chip); + set_irq_handler(virq, handle_level_irq); + set_irq_flags(virq, IRQF_VALID); + } + r = gpiochip_add(&pm->chip); + if (r < 0) + break; + set_irq_chained_handler(pm->irq, stmp3xxx_gpio_irq); + set_irq_data(pm->irq, pm); + } + return r; +} + +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/plat-stmp3xxx/timer.c b/arch/arm/plat-stmp3xxx/timer.c new file mode 100644 index 000000000000..c916068f0cab --- /dev/null +++ b/arch/arm/plat-stmp3xxx/timer.c @@ -0,0 +1,172 @@ +/* + * System timer for Freescale STMP37XX/STMP378X + * + * Embedded Alley Solutions, Inc <source@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> + +#include <asm/mach/time.h> +#include <mach/stmp3xxx.h> +#include <mach/regs-timrot.h> + +static irqreturn_t +stmp3xxx_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *c = dev_id; + + if (HW_TIMROT_TIMCTRLn_RD(0) & (1<<15)) { + HW_TIMROT_TIMCTRLn_CLR(0, (1<<15)); + c->event_handler(c); + } else if (HW_TIMROT_TIMCTRLn_RD(1) & (1<<15)) { + HW_TIMROT_TIMCTRLn_CLR(1, (1<<15)); + HW_TIMROT_TIMCTRLn_CLR(1, BM_TIMROT_TIMCTRLn_IRQ_EN); + HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); + } + + return IRQ_HANDLED; +} + +static cycle_t stmp3xxx_clock_read(void) +{ + return ~((HW_TIMROT_TIMCOUNTn_RD(1) & 0xFFFF0000) >> 16); +} + +static int +stmp3xxx_timrot_set_next_event(unsigned long delta, + struct clock_event_device *dev) +{ + HW_TIMROT_TIMCOUNTn_WR(0, delta); /* reload */ + return 0; +} + +static void +stmp3xxx_timrot_set_mode(enum clock_event_mode mode, + struct clock_event_device *dev) +{ +} + +static struct clock_event_device ckevt_timrot = { + .name = "timrot", + .features = CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .set_next_event = stmp3xxx_timrot_set_next_event, + .set_mode = stmp3xxx_timrot_set_mode, +}; + +static struct clocksource cksrc_stmp3xxx = { + .name = "cksrc_stmp3xxx", + .rating = 250, + .read = stmp3xxx_clock_read, + .mask = CLOCKSOURCE_MASK(16), + .shift = 10, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static struct irqaction stmp3xxx_timer_irq = { + .name = "stmp3xxx_timer", + .flags = IRQF_DISABLED | IRQF_TIMER, + .handler = stmp3xxx_timer_interrupt, + .dev_id = &ckevt_timrot, +}; + + +/* + * Set up timer interrupt, and return the current time in seconds. + */ +static void __init stmp3xxx_init_timer(void) +{ + cksrc_stmp3xxx.mult = clocksource_hz2mult(CLOCK_TICK_RATE, + cksrc_stmp3xxx.shift); + ckevt_timrot.mult = div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, + ckevt_timrot.shift); + ckevt_timrot.min_delta_ns = clockevent_delta2ns(2, &ckevt_timrot); + ckevt_timrot.max_delta_ns = clockevent_delta2ns(0xFFF, &ckevt_timrot); + ckevt_timrot.cpumask = cpumask_of(0); + + HW_TIMROT_ROTCTRL_CLR(BM_TIMROT_ROTCTRL_SFTRST | + BM_TIMROT_ROTCTRL_CLKGATE); + HW_TIMROT_TIMCOUNTn_WR(0, 0); + HW_TIMROT_TIMCOUNTn_WR(1, 0); + + HW_TIMROT_TIMCTRLn_WR(0, + (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ + BF_TIMROT_TIMCTRLn_PRESCALE(0) | + BM_TIMROT_TIMCTRLn_RELOAD | + BM_TIMROT_TIMCTRLn_UPDATE | + BM_TIMROT_TIMCTRLn_IRQ_EN)); + HW_TIMROT_TIMCTRLn_WR(1, + (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ + BF_TIMROT_TIMCTRLn_PRESCALE(0) | + BM_TIMROT_TIMCTRLn_RELOAD | + BM_TIMROT_TIMCTRLn_UPDATE)); + + HW_TIMROT_TIMCOUNTn_WR(0, CLOCK_TICK_RATE / HZ - 1); + HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); /* reload */ + + setup_irq(IRQ_TIMER0, &stmp3xxx_timer_irq); + + clocksource_register(&cksrc_stmp3xxx); + clockevents_register_device(&ckevt_timrot); +} + +#ifdef CONFIG_PM + +void stmp3xxx_suspend_timer(void) +{ + HW_TIMROT_TIMCTRLn_CLR(0, BM_TIMROT_TIMCTRLn_IRQ_EN); + HW_TIMROT_TIMCTRLn_CLR(0, (1<<15)); + HW_TIMROT_ROTCTRL_SET(BM_TIMROT_ROTCTRL_CLKGATE); +} + +void stmp3xxx_resume_timer(void) +{ + HW_TIMROT_ROTCTRL_CLR(BM_TIMROT_ROTCTRL_SFTRST | + BM_TIMROT_ROTCTRL_CLKGATE); + + + HW_TIMROT_TIMCTRLn_WR(0, + (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ + BF_TIMROT_TIMCTRLn_PRESCALE(0) | + BM_TIMROT_TIMCTRLn_UPDATE | + BM_TIMROT_TIMCTRLn_IRQ_EN)); + HW_TIMROT_TIMCTRLn_WR(1, + (BF_TIMROT_TIMCTRLn_SELECT(8) | /* 32 kHz */ + BF_TIMROT_TIMCTRLn_PRESCALE(0) | + BM_TIMROT_TIMCTRLn_RELOAD | + BM_TIMROT_TIMCTRLn_UPDATE)); + + HW_TIMROT_TIMCOUNTn_WR(0, CLOCK_TICK_RATE / HZ - 1); + HW_TIMROT_TIMCOUNTn_WR(1, 0xFFFF); /* reload */ +} + +#else + +#define stmp3xxx_suspend_timer NULL +#define stmp3xxx_resume_timer NULL + +#endif /* CONFIG_PM */ + +struct sys_timer stmp3xxx_timer = { + .init = stmp3xxx_init_timer, + .suspend = stmp3xxx_suspend_timer, + .resume = stmp3xxx_resume_timer, +}; |