Commit 5cccd37e authored by dmitry pervushin's avatar dmitry pervushin Committed by Russell King

[ARM] 5477/1: Freescale STMP platform support [6/10]

Sources: common STMP3xxx platform support
Signed-off-by: default avatardmitry pervushin <dpervushin@embeddedalley.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent e317872a
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
#
# Makefile for the linux kernel.
#
# Object file lists.
obj-y += core.o timer.o irq.o dma.o clock.o pinmux.o
/*
* 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);
/*
* 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
/*
* 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();
}
/*
* 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 */
}
/*
* 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();
}
/*
* 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");
/*
* 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,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment