Merge branch 'clockevents/3.16' of git://git.linaro.org/people/daniel.lezcano/linux into timers/core
This pull request contains the following changes: * Laurent Pinchart did a lot of modifications to prepare the DT support. These modifications include a lot of cleanup (structure renaming, preparation to support multiple channel, kzalloc usage, ...) and then finishes to drop the old code to the new one. * Jingoo Han removed the dev_err when an allocation fails because this error is already given by the mm subsystems. * Matthew Leach added the ARM global timer with vexpress, enabled the ARM global timer with the A5 and added the definition in the DT. He also fixed a invalid check when looking for an usable ARM global timer for A9 * Maxime Ripard added the support for AllWinner A31 for sun4i and made the timer reset optional through the DT * Stephen Boyd used the msm timer for the udelay * Uwe Kleine-König fixed the non-standard 'compatible' binding for efm32 * Xiubo Li clarified the types for the clocksource_mmio_read* and added a new Flextimer Module (FTM) with its bindings * Yang Wei added the 'notrace' attribute to 'read_sched_clock' for the dw_apb_timer
This commit is contained in:
commit
331b483f42
@ -4,8 +4,11 @@
|
||||
|
||||
** Timer node required properties:
|
||||
|
||||
- compatible : Should be "arm,cortex-a9-global-timer"
|
||||
Driver supports versions r2p0 and above.
|
||||
- compatible : should contain
|
||||
* "arm,cortex-a5-global-timer" for Cortex-A5 global timers.
|
||||
* "arm,cortex-a9-global-timer" for Cortex-A9 global
|
||||
timers or any compatible implementation. Note: driver
|
||||
supports versions r2p0 and above.
|
||||
|
||||
- interrupts : One interrupt to each core
|
||||
|
||||
|
@ -9,6 +9,9 @@ Required properties:
|
||||
one)
|
||||
- clocks: phandle to the source clock (usually the AHB clock)
|
||||
|
||||
Optionnal properties:
|
||||
- resets: phandle to a reset controller asserting the timer
|
||||
|
||||
Example:
|
||||
|
||||
timer@01c60000 {
|
||||
@ -19,4 +22,5 @@ timer@01c60000 {
|
||||
<0 53 1>,
|
||||
<0 54 1>;
|
||||
clocks = <&ahb1_gates 19>;
|
||||
resets = <&ahb1rst 19>;
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ channels and can be used as PWM or Quadrature Decoder. Available clock sources
|
||||
are the cpu's HFPERCLK (with a 10-bit prescaler) or an external pin.
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be efm32,timer
|
||||
- compatible : Should be "energymicro,efm32-timer"
|
||||
- reg : Address and length of the register set
|
||||
- clocks : Should contain a reference to the HFPERCLK
|
||||
|
||||
@ -16,7 +16,7 @@ Optional properties:
|
||||
Example:
|
||||
|
||||
timer@40010c00 {
|
||||
compatible = "efm32,timer";
|
||||
compatible = "energymicro,efm32-timer";
|
||||
reg = <0x40010c00 0x400>;
|
||||
interrupts = <14>;
|
||||
clocks = <&cmu clk_HFPERCLKTIMER3>;
|
31
Documentation/devicetree/bindings/timer/fsl,ftm-timer.txt
Normal file
31
Documentation/devicetree/bindings/timer/fsl,ftm-timer.txt
Normal file
@ -0,0 +1,31 @@
|
||||
Freescale FlexTimer Module (FTM) Timer
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : should be "fsl,ftm-timer"
|
||||
- reg : Specifies base physical address and size of the register sets for the
|
||||
clock event device and clock source device.
|
||||
- interrupts : Should be the clock event device interrupt.
|
||||
- clocks : The clocks provided by the SoC to drive the timer, must contain an
|
||||
entry for each entry in clock-names.
|
||||
- clock-names : Must include the following entries:
|
||||
o "ftm-evt"
|
||||
o "ftm-src"
|
||||
o "ftm-evt-counter-en"
|
||||
o "ftm-src-counter-en"
|
||||
- big-endian: One boolean property, the big endian mode will be in use if it is
|
||||
present, or the little endian mode will be in use for all the device registers.
|
||||
|
||||
Example:
|
||||
ftm: ftm@400b8000 {
|
||||
compatible = "fsl,ftm-timer";
|
||||
reg = <0x400b8000 0x1000 0x400b9000 0x1000>;
|
||||
interrupts = <0 44 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clock-names = "ftm-evt", "ftm-src",
|
||||
"ftm-evt-counter-en", "ftm-src-counter-en";
|
||||
clocks = <&clks VF610_CLK_FTM2>,
|
||||
<&clks VF610_CLK_FTM3>,
|
||||
<&clks VF610_CLK_FTM2_EXT_FIX_EN>,
|
||||
<&clks VF610_CLK_FTM3_EXT_FIX_EN>;
|
||||
big-endian;
|
||||
};
|
@ -428,6 +428,17 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
timer@01c60000 {
|
||||
compatible = "allwinner,sun6i-a31-hstimer", "allwinner,sun7i-a20-hstimer";
|
||||
reg = <0x01c60000 0x1000>;
|
||||
interrupts = <0 51 4>,
|
||||
<0 52 4>,
|
||||
<0 53 4>,
|
||||
<0 54 4>;
|
||||
clocks = <&ahb1_gates 19>;
|
||||
resets = <&ahb1_rst 19>;
|
||||
};
|
||||
|
||||
spi0: spi@01c68000 {
|
||||
compatible = "allwinner,sun6i-a31-spi";
|
||||
reg = <0x01c68000 0x1000>;
|
||||
|
@ -88,6 +88,14 @@
|
||||
interrupts = <1 13 0x304>;
|
||||
};
|
||||
|
||||
timer@2c000200 {
|
||||
compatible = "arm,cortex-a5-global-timer",
|
||||
"arm,cortex-a9-global-timer";
|
||||
reg = <0x2c000200 0x20>;
|
||||
interrupts = <1 11 0x304>;
|
||||
clocks = <&oscclk0>;
|
||||
};
|
||||
|
||||
watchdog@2c000620 {
|
||||
compatible = "arm,cortex-a5-twd-wdt";
|
||||
reg = <0x2c000620 0x20>;
|
||||
@ -120,7 +128,7 @@
|
||||
compatible = "arm,vexpress,config-bus";
|
||||
arm,vexpress,config-bridge = <&v2m_sysreg>;
|
||||
|
||||
osc@0 {
|
||||
oscclk0: osc@0 {
|
||||
/* CPU and internal AXI reference clock */
|
||||
compatible = "arm,vexpress-osc";
|
||||
arm,vexpress-sysreg,func = <1 0>;
|
||||
|
@ -347,6 +347,19 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
ftm: ftm@400b8000 {
|
||||
compatible = "fsl,ftm-timer";
|
||||
reg = <0x400b8000 0x1000 0x400b9000 0x1000>;
|
||||
interrupts = <0 44 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clock-names = "ftm-evt", "ftm-src",
|
||||
"ftm-evt-counter-en", "ftm-src-counter-en";
|
||||
clocks = <&clks VF610_CLK_FTM2>,
|
||||
<&clks VF610_CLK_FTM3>,
|
||||
<&clks VF610_CLK_FTM2_EXT_FIX_EN>,
|
||||
<&clks VF610_CLK_FTM3_EXT_FIX_EN>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
fec0: ethernet@400d0000 {
|
||||
compatible = "fsl,mvf600-fec";
|
||||
reg = <0x400d0000 0x1000>;
|
||||
|
@ -4,6 +4,7 @@ config ARCH_VEXPRESS
|
||||
select ARCH_SUPPORTS_BIG_ENDIAN
|
||||
select ARM_AMBA
|
||||
select ARM_GIC
|
||||
select ARM_GLOBAL_TIMER
|
||||
select ARM_TIMER_SP804
|
||||
select COMMON_CLK_VERSATILE
|
||||
select HAVE_ARM_SCU if SMP
|
||||
|
@ -136,6 +136,11 @@ config CLKSRC_SAMSUNG_PWM
|
||||
for all devicetree enabled platforms. This driver will be
|
||||
needed only on systems that do not have the Exynos MCT available.
|
||||
|
||||
config FSL_FTM_TIMER
|
||||
bool
|
||||
help
|
||||
Support for Freescale FlexTimer Module (FTM) timer.
|
||||
|
||||
config VF_PIT_TIMER
|
||||
bool
|
||||
help
|
||||
|
@ -31,6 +31,7 @@ obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o
|
||||
obj-$(CONFIG_CLKSRC_EFM32) += time-efm32.o
|
||||
obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o
|
||||
obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o
|
||||
obj-$(CONFIG_FSL_FTM_TIMER) += fsl_ftm_timer.o
|
||||
obj-$(CONFIG_VF_PIT_TIMER) += vf_pit_timer.o
|
||||
obj-$(CONFIG_CLKSRC_QCOM) += qcom-timer.o
|
||||
|
||||
|
@ -246,11 +246,12 @@ static void __init global_timer_of_register(struct device_node *np)
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* In r2p0 the comparators for each processor with the global timer
|
||||
* In A9 r2p0 the comparators for each processor with the global timer
|
||||
* fire when the timer value is greater than or equal to. In previous
|
||||
* revisions the comparators fired when the timer value was equal to.
|
||||
*/
|
||||
if ((read_cpuid_id() & 0xf0000f) < 0x200000) {
|
||||
if (read_cpuid_part_number() == ARM_CPU_PART_CORTEX_A9
|
||||
&& (read_cpuid_id() & 0xf0000f) < 0x200000) {
|
||||
pr_warn("global-timer: non support for this cpu version.\n");
|
||||
return;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ static void __init add_clocksource(struct device_node *source_timer)
|
||||
sched_rate = rate;
|
||||
}
|
||||
|
||||
static u64 read_sched_clock(void)
|
||||
static u64 notrace read_sched_clock(void)
|
||||
{
|
||||
return ~__raw_readl(sched_io_base);
|
||||
}
|
||||
|
@ -318,10 +318,8 @@ static int em_sti_probe(struct platform_device *pdev)
|
||||
int irq;
|
||||
|
||||
p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
|
||||
if (p == NULL) {
|
||||
dev_err(&pdev->dev, "failed to allocate driver data\n");
|
||||
if (p == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
p->pdev = pdev;
|
||||
platform_set_drvdata(pdev, p);
|
||||
|
367
drivers/clocksource/fsl_ftm_timer.c
Normal file
367
drivers/clocksource/fsl_ftm_timer.c
Normal file
@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Freescale FlexTimer Module (FTM) timer driver.
|
||||
*
|
||||
* Copyright 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/sched_clock.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define FTM_SC 0x00
|
||||
#define FTM_SC_CLK_SHIFT 3
|
||||
#define FTM_SC_CLK_MASK (0x3 << FTM_SC_CLK_SHIFT)
|
||||
#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_SHIFT)
|
||||
#define FTM_SC_PS_MASK 0x7
|
||||
#define FTM_SC_TOIE BIT(6)
|
||||
#define FTM_SC_TOF BIT(7)
|
||||
|
||||
#define FTM_CNT 0x04
|
||||
#define FTM_MOD 0x08
|
||||
#define FTM_CNTIN 0x4C
|
||||
|
||||
#define FTM_PS_MAX 7
|
||||
|
||||
struct ftm_clock_device {
|
||||
void __iomem *clksrc_base;
|
||||
void __iomem *clkevt_base;
|
||||
unsigned long periodic_cyc;
|
||||
unsigned long ps;
|
||||
bool big_endian;
|
||||
};
|
||||
|
||||
static struct ftm_clock_device *priv;
|
||||
|
||||
static inline u32 ftm_readl(void __iomem *addr)
|
||||
{
|
||||
if (priv->big_endian)
|
||||
return ioread32be(addr);
|
||||
else
|
||||
return ioread32(addr);
|
||||
}
|
||||
|
||||
static inline void ftm_writel(u32 val, void __iomem *addr)
|
||||
{
|
||||
if (priv->big_endian)
|
||||
iowrite32be(val, addr);
|
||||
else
|
||||
iowrite32(val, addr);
|
||||
}
|
||||
|
||||
static inline void ftm_counter_enable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* select and enable counter clock source */
|
||||
val = ftm_readl(base + FTM_SC);
|
||||
val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
|
||||
val |= priv->ps | FTM_SC_CLK(1);
|
||||
ftm_writel(val, base + FTM_SC);
|
||||
}
|
||||
|
||||
static inline void ftm_counter_disable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* disable counter clock source */
|
||||
val = ftm_readl(base + FTM_SC);
|
||||
val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
|
||||
ftm_writel(val, base + FTM_SC);
|
||||
}
|
||||
|
||||
static inline void ftm_irq_acknowledge(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = ftm_readl(base + FTM_SC);
|
||||
val &= ~FTM_SC_TOF;
|
||||
ftm_writel(val, base + FTM_SC);
|
||||
}
|
||||
|
||||
static inline void ftm_irq_enable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = ftm_readl(base + FTM_SC);
|
||||
val |= FTM_SC_TOIE;
|
||||
ftm_writel(val, base + FTM_SC);
|
||||
}
|
||||
|
||||
static inline void ftm_irq_disable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = ftm_readl(base + FTM_SC);
|
||||
val &= ~FTM_SC_TOIE;
|
||||
ftm_writel(val, base + FTM_SC);
|
||||
}
|
||||
|
||||
static inline void ftm_reset_counter(void __iomem *base)
|
||||
{
|
||||
/*
|
||||
* The CNT register contains the FTM counter value.
|
||||
* Reset clears the CNT register. Writing any value to COUNT
|
||||
* updates the counter with its initial value, CNTIN.
|
||||
*/
|
||||
ftm_writel(0x00, base + FTM_CNT);
|
||||
}
|
||||
|
||||
static u64 ftm_read_sched_clock(void)
|
||||
{
|
||||
return ftm_readl(priv->clksrc_base + FTM_CNT);
|
||||
}
|
||||
|
||||
static int ftm_set_next_event(unsigned long delta,
|
||||
struct clock_event_device *unused)
|
||||
{
|
||||
/*
|
||||
* The CNNIN and MOD are all double buffer registers, writing
|
||||
* to the MOD register latches the value into a buffer. The MOD
|
||||
* register is updated with the value of its write buffer with
|
||||
* the following scenario:
|
||||
* a, the counter source clock is diabled.
|
||||
*/
|
||||
ftm_counter_disable(priv->clkevt_base);
|
||||
|
||||
/* Force the value of CNTIN to be loaded into the FTM counter */
|
||||
ftm_reset_counter(priv->clkevt_base);
|
||||
|
||||
/*
|
||||
* The counter increments until the value of MOD is reached,
|
||||
* at which point the counter is reloaded with the value of CNTIN.
|
||||
* The TOF (the overflow flag) bit is set when the FTM counter
|
||||
* changes from MOD to CNTIN. So we should using the delta - 1.
|
||||
*/
|
||||
ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);
|
||||
|
||||
ftm_counter_enable(priv->clkevt_base);
|
||||
|
||||
ftm_irq_enable(priv->clkevt_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ftm_set_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
switch (mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
ftm_set_next_event(priv->periodic_cyc, evt);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_ONESHOT:
|
||||
ftm_counter_disable(priv->clkevt_base);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct clock_event_device *evt = dev_id;
|
||||
|
||||
ftm_irq_acknowledge(priv->clkevt_base);
|
||||
|
||||
if (likely(evt->mode == CLOCK_EVT_MODE_ONESHOT)) {
|
||||
ftm_irq_disable(priv->clkevt_base);
|
||||
ftm_counter_disable(priv->clkevt_base);
|
||||
}
|
||||
|
||||
evt->event_handler(evt);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct clock_event_device ftm_clockevent = {
|
||||
.name = "Freescale ftm timer",
|
||||
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
||||
.set_mode = ftm_set_mode,
|
||||
.set_next_event = ftm_set_next_event,
|
||||
.rating = 300,
|
||||
};
|
||||
|
||||
static struct irqaction ftm_timer_irq = {
|
||||
.name = "Freescale ftm timer",
|
||||
.flags = IRQF_TIMER | IRQF_IRQPOLL,
|
||||
.handler = ftm_evt_interrupt,
|
||||
.dev_id = &ftm_clockevent,
|
||||
};
|
||||
|
||||
static int __init ftm_clockevent_init(unsigned long freq, int irq)
|
||||
{
|
||||
int err;
|
||||
|
||||
ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);
|
||||
ftm_writel(~0UL, priv->clkevt_base + FTM_MOD);
|
||||
|
||||
ftm_reset_counter(priv->clkevt_base);
|
||||
|
||||
err = setup_irq(irq, &ftm_timer_irq);
|
||||
if (err) {
|
||||
pr_err("ftm: setup irq failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ftm_clockevent.cpumask = cpumask_of(0);
|
||||
ftm_clockevent.irq = irq;
|
||||
|
||||
clockevents_config_and_register(&ftm_clockevent,
|
||||
freq / (1 << priv->ps),
|
||||
1, 0xffff);
|
||||
|
||||
ftm_counter_enable(priv->clkevt_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init ftm_clocksource_init(unsigned long freq)
|
||||
{
|
||||
int err;
|
||||
|
||||
ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);
|
||||
ftm_writel(~0UL, priv->clksrc_base + FTM_MOD);
|
||||
|
||||
ftm_reset_counter(priv->clksrc_base);
|
||||
|
||||
sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));
|
||||
err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",
|
||||
freq / (1 << priv->ps), 300, 16,
|
||||
clocksource_mmio_readl_up);
|
||||
if (err) {
|
||||
pr_err("ftm: init clock source mmio failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ftm_counter_enable(priv->clksrc_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,
|
||||
char *ftm_name)
|
||||
{
|
||||
struct clk *clk;
|
||||
int err;
|
||||
|
||||
clk = of_clk_get_by_name(np, cnt_name);
|
||||
if (IS_ERR(clk)) {
|
||||
pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
err = clk_prepare_enable(clk);
|
||||
if (err) {
|
||||
pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
|
||||
cnt_name, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
clk = of_clk_get_by_name(np, ftm_name);
|
||||
if (IS_ERR(clk)) {
|
||||
pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
err = clk_prepare_enable(clk);
|
||||
if (err)
|
||||
pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
|
||||
ftm_name, err);
|
||||
|
||||
return clk_get_rate(clk);
|
||||
}
|
||||
|
||||
static unsigned long __init ftm_clk_init(struct device_node *np)
|
||||
{
|
||||
unsigned long freq;
|
||||
|
||||
freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");
|
||||
if (freq <= 0)
|
||||
return 0;
|
||||
|
||||
freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");
|
||||
if (freq <= 0)
|
||||
return 0;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static int __init ftm_calc_closest_round_cyc(unsigned long freq)
|
||||
{
|
||||
priv->ps = 0;
|
||||
|
||||
/* The counter register is only using the lower 16 bits, and
|
||||
* if the 'freq' value is to big here, then the periodic_cyc
|
||||
* may exceed 0xFFFF.
|
||||
*/
|
||||
do {
|
||||
priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,
|
||||
HZ * (1 << priv->ps++));
|
||||
} while (priv->periodic_cyc > 0xFFFF);
|
||||
|
||||
if (priv->ps > FTM_PS_MAX) {
|
||||
pr_err("ftm: the prescaler is %lu > %d\n",
|
||||
priv->ps, FTM_PS_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init ftm_timer_init(struct device_node *np)
|
||||
{
|
||||
unsigned long freq;
|
||||
int irq;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
priv->clkevt_base = of_iomap(np, 0);
|
||||
if (!priv->clkevt_base) {
|
||||
pr_err("ftm: unable to map event timer registers\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
priv->clksrc_base = of_iomap(np, 1);
|
||||
if (!priv->clksrc_base) {
|
||||
pr_err("ftm: unable to map source timer registers\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
irq = irq_of_parse_and_map(np, 0);
|
||||
if (irq <= 0) {
|
||||
pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
|
||||
goto err;
|
||||
}
|
||||
|
||||
priv->big_endian = of_property_read_bool(np, "big-endian");
|
||||
|
||||
freq = ftm_clk_init(np);
|
||||
if (!freq)
|
||||
goto err;
|
||||
|
||||
if (ftm_calc_closest_round_cyc(freq))
|
||||
goto err;
|
||||
|
||||
if (ftm_clocksource_init(freq))
|
||||
goto err;
|
||||
|
||||
if (ftm_clockevent_init(freq, irq))
|
||||
goto err;
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
kfree(priv);
|
||||
}
|
||||
CLOCKSOURCE_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);
|
@ -22,22 +22,22 @@ static inline struct clocksource_mmio *to_mmio_clksrc(struct clocksource *c)
|
||||
|
||||
cycle_t clocksource_mmio_readl_up(struct clocksource *c)
|
||||
{
|
||||
return readl_relaxed(to_mmio_clksrc(c)->reg);
|
||||
return (cycle_t)readl_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
|
||||
cycle_t clocksource_mmio_readl_down(struct clocksource *c)
|
||||
{
|
||||
return ~readl_relaxed(to_mmio_clksrc(c)->reg);
|
||||
return ~(cycle_t)readl_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
|
||||
cycle_t clocksource_mmio_readw_up(struct clocksource *c)
|
||||
{
|
||||
return readw_relaxed(to_mmio_clksrc(c)->reg);
|
||||
return (cycle_t)readw_relaxed(to_mmio_clksrc(c)->reg);
|
||||
}
|
||||
|
||||
cycle_t clocksource_mmio_readw_down(struct clocksource *c)
|
||||
{
|
||||
return ~(unsigned)readw_relaxed(to_mmio_clksrc(c)->reg);
|
||||
return ~(cycle_t)readw_relaxed(to_mmio_clksrc(c)->reg) & c->mask;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <asm/delay.h>
|
||||
|
||||
#define TIMER_MATCH_VAL 0x0000
|
||||
#define TIMER_COUNT_VAL 0x0004
|
||||
#define TIMER_ENABLE 0x0008
|
||||
@ -179,6 +181,15 @@ static u64 notrace msm_sched_clock_read(void)
|
||||
return msm_clocksource.read(&msm_clocksource);
|
||||
}
|
||||
|
||||
static unsigned long msm_read_current_timer(void)
|
||||
{
|
||||
return msm_clocksource.read(&msm_clocksource);
|
||||
}
|
||||
|
||||
static struct delay_timer msm_delay_timer = {
|
||||
.read_current_timer = msm_read_current_timer,
|
||||
};
|
||||
|
||||
static void __init msm_timer_init(u32 dgt_hz, int sched_bits, int irq,
|
||||
bool percpu)
|
||||
{
|
||||
@ -217,6 +228,8 @@ err:
|
||||
if (res)
|
||||
pr_err("clocksource_register failed\n");
|
||||
sched_clock_register(msm_sched_clock_read, sched_bits, dgt_hz);
|
||||
msm_delay_timer.freq = dgt_hz;
|
||||
register_current_timer_delay(&msm_delay_timer);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARCH_QCOM
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,37 +11,48 @@
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/sh_timer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/sh_timer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
struct sh_mtu2_device;
|
||||
|
||||
struct sh_mtu2_channel {
|
||||
struct sh_mtu2_device *mtu;
|
||||
unsigned int index;
|
||||
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
|
||||
struct clock_event_device ced;
|
||||
};
|
||||
|
||||
struct sh_mtu2_device {
|
||||
struct platform_device *pdev;
|
||||
|
||||
struct sh_mtu2_priv {
|
||||
void __iomem *mapbase;
|
||||
struct clk *clk;
|
||||
struct irqaction irqaction;
|
||||
struct platform_device *pdev;
|
||||
unsigned long rate;
|
||||
unsigned long periodic;
|
||||
struct clock_event_device ced;
|
||||
|
||||
struct sh_mtu2_channel *channels;
|
||||
unsigned int num_channels;
|
||||
|
||||
bool legacy;
|
||||
bool has_clockevent;
|
||||
};
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(sh_mtu2_lock);
|
||||
@ -55,6 +66,88 @@ static DEFINE_RAW_SPINLOCK(sh_mtu2_lock);
|
||||
#define TCNT 5 /* channel register */
|
||||
#define TGR 6 /* channel register */
|
||||
|
||||
#define TCR_CCLR_NONE (0 << 5)
|
||||
#define TCR_CCLR_TGRA (1 << 5)
|
||||
#define TCR_CCLR_TGRB (2 << 5)
|
||||
#define TCR_CCLR_SYNC (3 << 5)
|
||||
#define TCR_CCLR_TGRC (5 << 5)
|
||||
#define TCR_CCLR_TGRD (6 << 5)
|
||||
#define TCR_CCLR_MASK (7 << 5)
|
||||
#define TCR_CKEG_RISING (0 << 3)
|
||||
#define TCR_CKEG_FALLING (1 << 3)
|
||||
#define TCR_CKEG_BOTH (2 << 3)
|
||||
#define TCR_CKEG_MASK (3 << 3)
|
||||
/* Values 4 to 7 are channel-dependent */
|
||||
#define TCR_TPSC_P1 (0 << 0)
|
||||
#define TCR_TPSC_P4 (1 << 0)
|
||||
#define TCR_TPSC_P16 (2 << 0)
|
||||
#define TCR_TPSC_P64 (3 << 0)
|
||||
#define TCR_TPSC_CH0_TCLKA (4 << 0)
|
||||
#define TCR_TPSC_CH0_TCLKB (5 << 0)
|
||||
#define TCR_TPSC_CH0_TCLKC (6 << 0)
|
||||
#define TCR_TPSC_CH0_TCLKD (7 << 0)
|
||||
#define TCR_TPSC_CH1_TCLKA (4 << 0)
|
||||
#define TCR_TPSC_CH1_TCLKB (5 << 0)
|
||||
#define TCR_TPSC_CH1_P256 (6 << 0)
|
||||
#define TCR_TPSC_CH1_TCNT2 (7 << 0)
|
||||
#define TCR_TPSC_CH2_TCLKA (4 << 0)
|
||||
#define TCR_TPSC_CH2_TCLKB (5 << 0)
|
||||
#define TCR_TPSC_CH2_TCLKC (6 << 0)
|
||||
#define TCR_TPSC_CH2_P1024 (7 << 0)
|
||||
#define TCR_TPSC_CH34_P256 (4 << 0)
|
||||
#define TCR_TPSC_CH34_P1024 (5 << 0)
|
||||
#define TCR_TPSC_CH34_TCLKA (6 << 0)
|
||||
#define TCR_TPSC_CH34_TCLKB (7 << 0)
|
||||
#define TCR_TPSC_MASK (7 << 0)
|
||||
|
||||
#define TMDR_BFE (1 << 6)
|
||||
#define TMDR_BFB (1 << 5)
|
||||
#define TMDR_BFA (1 << 4)
|
||||
#define TMDR_MD_NORMAL (0 << 0)
|
||||
#define TMDR_MD_PWM_1 (2 << 0)
|
||||
#define TMDR_MD_PWM_2 (3 << 0)
|
||||
#define TMDR_MD_PHASE_1 (4 << 0)
|
||||
#define TMDR_MD_PHASE_2 (5 << 0)
|
||||
#define TMDR_MD_PHASE_3 (6 << 0)
|
||||
#define TMDR_MD_PHASE_4 (7 << 0)
|
||||
#define TMDR_MD_PWM_SYNC (8 << 0)
|
||||
#define TMDR_MD_PWM_COMP_CREST (13 << 0)
|
||||
#define TMDR_MD_PWM_COMP_TROUGH (14 << 0)
|
||||
#define TMDR_MD_PWM_COMP_BOTH (15 << 0)
|
||||
#define TMDR_MD_MASK (15 << 0)
|
||||
|
||||
#define TIOC_IOCH(n) ((n) << 4)
|
||||
#define TIOC_IOCL(n) ((n) << 0)
|
||||
#define TIOR_OC_RETAIN (0 << 0)
|
||||
#define TIOR_OC_0_CLEAR (1 << 0)
|
||||
#define TIOR_OC_0_SET (2 << 0)
|
||||
#define TIOR_OC_0_TOGGLE (3 << 0)
|
||||
#define TIOR_OC_1_CLEAR (5 << 0)
|
||||
#define TIOR_OC_1_SET (6 << 0)
|
||||
#define TIOR_OC_1_TOGGLE (7 << 0)
|
||||
#define TIOR_IC_RISING (8 << 0)
|
||||
#define TIOR_IC_FALLING (9 << 0)
|
||||
#define TIOR_IC_BOTH (10 << 0)
|
||||
#define TIOR_IC_TCNT (12 << 0)
|
||||
#define TIOR_MASK (15 << 0)
|
||||
|
||||
#define TIER_TTGE (1 << 7)
|
||||
#define TIER_TTGE2 (1 << 6)
|
||||
#define TIER_TCIEU (1 << 5)
|
||||
#define TIER_TCIEV (1 << 4)
|
||||
#define TIER_TGIED (1 << 3)
|
||||
#define TIER_TGIEC (1 << 2)
|
||||
#define TIER_TGIEB (1 << 1)
|
||||
#define TIER_TGIEA (1 << 0)
|
||||
|
||||
#define TSR_TCFD (1 << 7)
|
||||
#define TSR_TCFU (1 << 5)
|
||||
#define TSR_TCFV (1 << 4)
|
||||
#define TSR_TGFD (1 << 3)
|
||||
#define TSR_TGFC (1 << 2)
|
||||
#define TSR_TGFB (1 << 1)
|
||||
#define TSR_TGFA (1 << 0)
|
||||
|
||||
static unsigned long mtu2_reg_offs[] = {
|
||||
[TCR] = 0,
|
||||
[TMDR] = 1,
|
||||
@ -65,135 +158,143 @@ static unsigned long mtu2_reg_offs[] = {
|
||||
[TGR] = 8,
|
||||
};
|
||||
|
||||
static inline unsigned long sh_mtu2_read(struct sh_mtu2_priv *p, int reg_nr)
|
||||
static inline unsigned long sh_mtu2_read(struct sh_mtu2_channel *ch, int reg_nr)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
void __iomem *base = p->mapbase;
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR)
|
||||
return ioread8(base + cfg->channel_offset);
|
||||
|
||||
offs = mtu2_reg_offs[reg_nr];
|
||||
|
||||
if ((reg_nr == TCNT) || (reg_nr == TGR))
|
||||
return ioread16(base + offs);
|
||||
else
|
||||
return ioread8(base + offs);
|
||||
}
|
||||
|
||||
static inline void sh_mtu2_write(struct sh_mtu2_priv *p, int reg_nr,
|
||||
unsigned long value)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
void __iomem *base = p->mapbase;
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR) {
|
||||
iowrite8(value, base + cfg->channel_offset);
|
||||
return;
|
||||
if (ch->mtu->legacy)
|
||||
return ioread8(ch->mtu->mapbase);
|
||||
else
|
||||
return ioread8(ch->mtu->mapbase + 0x280);
|
||||
}
|
||||
|
||||
offs = mtu2_reg_offs[reg_nr];
|
||||
|
||||
if ((reg_nr == TCNT) || (reg_nr == TGR))
|
||||
iowrite16(value, base + offs);
|
||||
return ioread16(ch->base + offs);
|
||||
else
|
||||
iowrite8(value, base + offs);
|
||||
return ioread8(ch->base + offs);
|
||||
}
|
||||
|
||||
static void sh_mtu2_start_stop_ch(struct sh_mtu2_priv *p, int start)
|
||||
static inline void sh_mtu2_write(struct sh_mtu2_channel *ch, int reg_nr,
|
||||
unsigned long value)
|
||||
{
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR) {
|
||||
if (ch->mtu->legacy)
|
||||
return iowrite8(value, ch->mtu->mapbase);
|
||||
else
|
||||
return iowrite8(value, ch->mtu->mapbase + 0x280);
|
||||
}
|
||||
|
||||
offs = mtu2_reg_offs[reg_nr];
|
||||
|
||||
if ((reg_nr == TCNT) || (reg_nr == TGR))
|
||||
iowrite16(value, ch->base + offs);
|
||||
else
|
||||
iowrite8(value, ch->base + offs);
|
||||
}
|
||||
|
||||
static void sh_mtu2_start_stop_ch(struct sh_mtu2_channel *ch, int start)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
unsigned long flags, value;
|
||||
|
||||
/* start stop register shared by multiple timer channels */
|
||||
raw_spin_lock_irqsave(&sh_mtu2_lock, flags);
|
||||
value = sh_mtu2_read(p, TSTR);
|
||||
value = sh_mtu2_read(ch, TSTR);
|
||||
|
||||
if (start)
|
||||
value |= 1 << cfg->timer_bit;
|
||||
value |= 1 << ch->index;
|
||||
else
|
||||
value &= ~(1 << cfg->timer_bit);
|
||||
value &= ~(1 << ch->index);
|
||||
|
||||
sh_mtu2_write(p, TSTR, value);
|
||||
sh_mtu2_write(ch, TSTR, value);
|
||||
raw_spin_unlock_irqrestore(&sh_mtu2_lock, flags);
|
||||
}
|
||||
|
||||
static int sh_mtu2_enable(struct sh_mtu2_priv *p)
|
||||
static int sh_mtu2_enable(struct sh_mtu2_channel *ch)
|
||||
{
|
||||
unsigned long periodic;
|
||||
unsigned long rate;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(&p->pdev->dev);
|
||||
dev_pm_syscore_device(&p->pdev->dev, true);
|
||||
pm_runtime_get_sync(&ch->mtu->pdev->dev);
|
||||
dev_pm_syscore_device(&ch->mtu->pdev->dev, true);
|
||||
|
||||
/* enable clock */
|
||||
ret = clk_enable(p->clk);
|
||||
ret = clk_enable(ch->mtu->clk);
|
||||
if (ret) {
|
||||
dev_err(&p->pdev->dev, "cannot enable clock\n");
|
||||
dev_err(&ch->mtu->pdev->dev, "ch%u: cannot enable clock\n",
|
||||
ch->index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* make sure channel is disabled */
|
||||
sh_mtu2_start_stop_ch(p, 0);
|
||||
sh_mtu2_start_stop_ch(ch, 0);
|
||||
|
||||
p->rate = clk_get_rate(p->clk) / 64;
|
||||
p->periodic = (p->rate + HZ/2) / HZ;
|
||||
rate = clk_get_rate(ch->mtu->clk) / 64;
|
||||
periodic = (rate + HZ/2) / HZ;
|
||||
|
||||
/* "Periodic Counter Operation" */
|
||||
sh_mtu2_write(p, TCR, 0x23); /* TGRA clear, divide clock by 64 */
|
||||
sh_mtu2_write(p, TIOR, 0);
|
||||
sh_mtu2_write(p, TGR, p->periodic);
|
||||
sh_mtu2_write(p, TCNT, 0);
|
||||
sh_mtu2_write(p, TMDR, 0);
|
||||
sh_mtu2_write(p, TIER, 0x01);
|
||||
/*
|
||||
* "Periodic Counter Operation"
|
||||
* Clear on TGRA compare match, divide clock by 64.
|
||||
*/
|
||||
sh_mtu2_write(ch, TCR, TCR_CCLR_TGRA | TCR_TPSC_P64);
|
||||
sh_mtu2_write(ch, TIOR, TIOC_IOCH(TIOR_OC_0_CLEAR) |
|
||||
TIOC_IOCL(TIOR_OC_0_CLEAR));
|
||||
sh_mtu2_write(ch, TGR, periodic);
|
||||
sh_mtu2_write(ch, TCNT, 0);
|
||||
sh_mtu2_write(ch, TMDR, TMDR_MD_NORMAL);
|
||||
sh_mtu2_write(ch, TIER, TIER_TGIEA);
|
||||
|
||||
/* enable channel */
|
||||
sh_mtu2_start_stop_ch(p, 1);
|
||||
sh_mtu2_start_stop_ch(ch, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh_mtu2_disable(struct sh_mtu2_priv *p)
|
||||
static void sh_mtu2_disable(struct sh_mtu2_channel *ch)
|
||||
{
|
||||
/* disable channel */
|
||||
sh_mtu2_start_stop_ch(p, 0);
|
||||
sh_mtu2_start_stop_ch(ch, 0);
|
||||
|
||||
/* stop clock */
|
||||
clk_disable(p->clk);
|
||||
clk_disable(ch->mtu->clk);
|
||||
|
||||
dev_pm_syscore_device(&p->pdev->dev, false);
|
||||
pm_runtime_put(&p->pdev->dev);
|
||||
dev_pm_syscore_device(&ch->mtu->pdev->dev, false);
|
||||
pm_runtime_put(&ch->mtu->pdev->dev);
|
||||
}
|
||||
|
||||
static irqreturn_t sh_mtu2_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct sh_mtu2_priv *p = dev_id;
|
||||
struct sh_mtu2_channel *ch = dev_id;
|
||||
|
||||
/* acknowledge interrupt */
|
||||
sh_mtu2_read(p, TSR);
|
||||
sh_mtu2_write(p, TSR, 0xfe);
|
||||
sh_mtu2_read(ch, TSR);
|
||||
sh_mtu2_write(ch, TSR, ~TSR_TGFA);
|
||||
|
||||
/* notify clockevent layer */
|
||||
p->ced.event_handler(&p->ced);
|
||||
ch->ced.event_handler(&ch->ced);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct sh_mtu2_priv *ced_to_sh_mtu2(struct clock_event_device *ced)
|
||||
static struct sh_mtu2_channel *ced_to_sh_mtu2(struct clock_event_device *ced)
|
||||
{
|
||||
return container_of(ced, struct sh_mtu2_priv, ced);
|
||||
return container_of(ced, struct sh_mtu2_channel, ced);
|
||||
}
|
||||
|
||||
static void sh_mtu2_clock_event_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *ced)
|
||||
{
|
||||
struct sh_mtu2_priv *p = ced_to_sh_mtu2(ced);
|
||||
struct sh_mtu2_channel *ch = ced_to_sh_mtu2(ced);
|
||||
int disabled = 0;
|
||||
|
||||
/* deal with old setting first */
|
||||
switch (ced->mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
sh_mtu2_disable(p);
|
||||
sh_mtu2_disable(ch);
|
||||
disabled = 1;
|
||||
break;
|
||||
default:
|
||||
@ -202,12 +303,13 @@ static void sh_mtu2_clock_event_mode(enum clock_event_mode mode,
|
||||
|
||||
switch (mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
dev_info(&p->pdev->dev, "used for periodic clock events\n");
|
||||
sh_mtu2_enable(p);
|
||||
dev_info(&ch->mtu->pdev->dev,
|
||||
"ch%u: used for periodic clock events\n", ch->index);
|
||||
sh_mtu2_enable(ch);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_UNUSED:
|
||||
if (!disabled)
|
||||
sh_mtu2_disable(p);
|
||||
sh_mtu2_disable(ch);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_SHUTDOWN:
|
||||
default:
|
||||
@ -217,125 +319,207 @@ static void sh_mtu2_clock_event_mode(enum clock_event_mode mode,
|
||||
|
||||
static void sh_mtu2_clock_event_suspend(struct clock_event_device *ced)
|
||||
{
|
||||
pm_genpd_syscore_poweroff(&ced_to_sh_mtu2(ced)->pdev->dev);
|
||||
pm_genpd_syscore_poweroff(&ced_to_sh_mtu2(ced)->mtu->pdev->dev);
|
||||
}
|
||||
|
||||
static void sh_mtu2_clock_event_resume(struct clock_event_device *ced)
|
||||
{
|
||||
pm_genpd_syscore_poweron(&ced_to_sh_mtu2(ced)->pdev->dev);
|
||||
pm_genpd_syscore_poweron(&ced_to_sh_mtu2(ced)->mtu->pdev->dev);
|
||||
}
|
||||
|
||||
static void sh_mtu2_register_clockevent(struct sh_mtu2_priv *p,
|
||||
char *name, unsigned long rating)
|
||||
static void sh_mtu2_register_clockevent(struct sh_mtu2_channel *ch,
|
||||
const char *name)
|
||||
{
|
||||
struct clock_event_device *ced = &p->ced;
|
||||
struct clock_event_device *ced = &ch->ced;
|
||||
int ret;
|
||||
|
||||
memset(ced, 0, sizeof(*ced));
|
||||
|
||||
ced->name = name;
|
||||
ced->features = CLOCK_EVT_FEAT_PERIODIC;
|
||||
ced->rating = rating;
|
||||
ced->cpumask = cpumask_of(0);
|
||||
ced->rating = 200;
|
||||
ced->cpumask = cpu_possible_mask;
|
||||
ced->set_mode = sh_mtu2_clock_event_mode;
|
||||
ced->suspend = sh_mtu2_clock_event_suspend;
|
||||
ced->resume = sh_mtu2_clock_event_resume;
|
||||
|
||||
dev_info(&p->pdev->dev, "used for clock events\n");
|
||||
dev_info(&ch->mtu->pdev->dev, "ch%u: used for clock events\n",
|
||||
ch->index);
|
||||
clockevents_register_device(ced);
|
||||
|
||||
ret = setup_irq(p->irqaction.irq, &p->irqaction);
|
||||
ret = request_irq(ch->irq, sh_mtu2_interrupt,
|
||||
IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
|
||||
dev_name(&ch->mtu->pdev->dev), ch);
|
||||
if (ret) {
|
||||
dev_err(&p->pdev->dev, "failed to request irq %d\n",
|
||||
p->irqaction.irq);
|
||||
dev_err(&ch->mtu->pdev->dev, "ch%u: failed to request irq %d\n",
|
||||
ch->index, ch->irq);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int sh_mtu2_register(struct sh_mtu2_priv *p, char *name,
|
||||
unsigned long clockevent_rating)
|
||||
static int sh_mtu2_register(struct sh_mtu2_channel *ch, const char *name,
|
||||
bool clockevent)
|
||||
{
|
||||
if (clockevent_rating)
|
||||
sh_mtu2_register_clockevent(p, name, clockevent_rating);
|
||||
if (clockevent) {
|
||||
ch->mtu->has_clockevent = true;
|
||||
sh_mtu2_register_clockevent(ch, name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_mtu2_setup(struct sh_mtu2_priv *p, struct platform_device *pdev)
|
||||
static int sh_mtu2_setup_channel(struct sh_mtu2_channel *ch, unsigned int index,
|
||||
struct sh_mtu2_device *mtu)
|
||||
{
|
||||
static const unsigned int channel_offsets[] = {
|
||||
0x300, 0x380, 0x000,
|
||||
};
|
||||
bool clockevent;
|
||||
|
||||
ch->mtu = mtu;
|
||||
|
||||
if (mtu->legacy) {
|
||||
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||
|
||||
clockevent = cfg->clockevent_rating != 0;
|
||||
|
||||
ch->irq = platform_get_irq(mtu->pdev, 0);
|
||||
ch->base = mtu->mapbase - cfg->channel_offset;
|
||||
ch->index = cfg->timer_bit;
|
||||
} else {
|
||||
char name[6];
|
||||
|
||||
clockevent = true;
|
||||
|
||||
sprintf(name, "tgi%ua", index);
|
||||
ch->irq = platform_get_irq_byname(mtu->pdev, name);
|
||||
ch->base = mtu->mapbase + channel_offsets[index];
|
||||
ch->index = index;
|
||||
}
|
||||
|
||||
if (ch->irq < 0) {
|
||||
/* Skip channels with no declared interrupt. */
|
||||
if (!mtu->legacy)
|
||||
return 0;
|
||||
|
||||
dev_err(&mtu->pdev->dev, "ch%u: failed to get irq\n",
|
||||
ch->index);
|
||||
return ch->irq;
|
||||
}
|
||||
|
||||
return sh_mtu2_register(ch, dev_name(&mtu->pdev->dev), clockevent);
|
||||
}
|
||||
|
||||
static int sh_mtu2_map_memory(struct sh_mtu2_device *mtu)
|
||||
{
|
||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||
struct resource *res;
|
||||
int irq, ret;
|
||||
ret = -ENXIO;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->pdev = pdev;
|
||||
|
||||
if (!cfg) {
|
||||
dev_err(&p->pdev->dev, "missing platform data\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, p);
|
||||
|
||||
res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0);
|
||||
res = platform_get_resource(mtu->pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&p->pdev->dev, "failed to get I/O memory\n");
|
||||
goto err0;
|
||||
dev_err(&mtu->pdev->dev, "failed to get I/O memory\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(p->pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&p->pdev->dev, "failed to get irq\n");
|
||||
goto err0;
|
||||
mtu->mapbase = ioremap_nocache(res->start, resource_size(res));
|
||||
if (mtu->mapbase == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
/*
|
||||
* In legacy platform device configuration (with one device per channel)
|
||||
* the resource points to the channel base address.
|
||||
*/
|
||||
if (mtu->legacy) {
|
||||
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||
mtu->mapbase += cfg->channel_offset;
|
||||
}
|
||||
|
||||
/* map memory, let mapbase point to our channel */
|
||||
p->mapbase = ioremap_nocache(res->start, resource_size(res));
|
||||
if (p->mapbase == NULL) {
|
||||
dev_err(&p->pdev->dev, "failed to remap I/O memory\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
/* setup data for setup_irq() (too early for request_irq()) */
|
||||
p->irqaction.name = dev_name(&p->pdev->dev);
|
||||
p->irqaction.handler = sh_mtu2_interrupt;
|
||||
p->irqaction.dev_id = p;
|
||||
p->irqaction.irq = irq;
|
||||
p->irqaction.flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING;
|
||||
|
||||
/* get hold of clock */
|
||||
p->clk = clk_get(&p->pdev->dev, "mtu2_fck");
|
||||
if (IS_ERR(p->clk)) {
|
||||
dev_err(&p->pdev->dev, "cannot get clock\n");
|
||||
ret = PTR_ERR(p->clk);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
ret = clk_prepare(p->clk);
|
||||
if (ret < 0)
|
||||
goto err2;
|
||||
|
||||
ret = sh_mtu2_register(p, (char *)dev_name(&p->pdev->dev),
|
||||
cfg->clockevent_rating);
|
||||
if (ret < 0)
|
||||
goto err3;
|
||||
|
||||
return 0;
|
||||
err3:
|
||||
clk_unprepare(p->clk);
|
||||
err2:
|
||||
clk_put(p->clk);
|
||||
err1:
|
||||
iounmap(p->mapbase);
|
||||
err0:
|
||||
}
|
||||
|
||||
static void sh_mtu2_unmap_memory(struct sh_mtu2_device *mtu)
|
||||
{
|
||||
if (mtu->legacy) {
|
||||
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||
mtu->mapbase -= cfg->channel_offset;
|
||||
}
|
||||
|
||||
iounmap(mtu->mapbase);
|
||||
}
|
||||
|
||||
static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||
const struct platform_device_id *id = pdev->id_entry;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
mtu->pdev = pdev;
|
||||
mtu->legacy = id->driver_data;
|
||||
|
||||
if (mtu->legacy && !cfg) {
|
||||
dev_err(&mtu->pdev->dev, "missing platform data\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* Get hold of clock. */
|
||||
mtu->clk = clk_get(&mtu->pdev->dev, mtu->legacy ? "mtu2_fck" : "fck");
|
||||
if (IS_ERR(mtu->clk)) {
|
||||
dev_err(&mtu->pdev->dev, "cannot get clock\n");
|
||||
return PTR_ERR(mtu->clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare(mtu->clk);
|
||||
if (ret < 0)
|
||||
goto err_clk_put;
|
||||
|
||||
/* Map the memory resource. */
|
||||
ret = sh_mtu2_map_memory(mtu);
|
||||
if (ret < 0) {
|
||||
dev_err(&mtu->pdev->dev, "failed to remap I/O memory\n");
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
/* Allocate and setup the channels. */
|
||||
if (mtu->legacy)
|
||||
mtu->num_channels = 1;
|
||||
else
|
||||
mtu->num_channels = 3;
|
||||
|
||||
mtu->channels = kzalloc(sizeof(*mtu->channels) * mtu->num_channels,
|
||||
GFP_KERNEL);
|
||||
if (mtu->channels == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
if (mtu->legacy) {
|
||||
ret = sh_mtu2_setup_channel(&mtu->channels[0], 0, mtu);
|
||||
if (ret < 0)
|
||||
goto err_unmap;
|
||||
} else {
|
||||
for (i = 0; i < mtu->num_channels; ++i) {
|
||||
ret = sh_mtu2_setup_channel(&mtu->channels[i], i, mtu);
|
||||
if (ret < 0)
|
||||
goto err_unmap;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, mtu);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
kfree(mtu->channels);
|
||||
sh_mtu2_unmap_memory(mtu);
|
||||
err_clk_unprepare:
|
||||
clk_unprepare(mtu->clk);
|
||||
err_clk_put:
|
||||
clk_put(mtu->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_mtu2_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sh_mtu2_priv *p = platform_get_drvdata(pdev);
|
||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||
struct sh_mtu2_device *mtu = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
if (!is_early_platform_device(pdev)) {
|
||||
@ -343,20 +527,18 @@ static int sh_mtu2_probe(struct platform_device *pdev)
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
}
|
||||
|
||||
if (p) {
|
||||
if (mtu) {
|
||||
dev_info(&pdev->dev, "kept as earlytimer\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = kmalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (p == NULL) {
|
||||
dev_err(&pdev->dev, "failed to allocate driver data\n");
|
||||
mtu = kzalloc(sizeof(*mtu), GFP_KERNEL);
|
||||
if (mtu == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = sh_mtu2_setup(p, pdev);
|
||||
ret = sh_mtu2_setup(mtu, pdev);
|
||||
if (ret) {
|
||||
kfree(p);
|
||||
kfree(mtu);
|
||||
pm_runtime_idle(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
@ -364,7 +546,7 @@ static int sh_mtu2_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (cfg->clockevent_rating)
|
||||
if (mtu->has_clockevent)
|
||||
pm_runtime_irq_safe(&pdev->dev);
|
||||
else
|
||||
pm_runtime_idle(&pdev->dev);
|
||||
@ -377,12 +559,20 @@ static int sh_mtu2_remove(struct platform_device *pdev)
|
||||
return -EBUSY; /* cannot unregister clockevent */
|
||||
}
|
||||
|
||||
static const struct platform_device_id sh_mtu2_id_table[] = {
|
||||
{ "sh_mtu2", 1 },
|
||||
{ "sh-mtu2", 0 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, sh_mtu2_id_table);
|
||||
|
||||
static struct platform_driver sh_mtu2_device_driver = {
|
||||
.probe = sh_mtu2_probe,
|
||||
.remove = sh_mtu2_remove,
|
||||
.driver = {
|
||||
.name = "sh_mtu2",
|
||||
}
|
||||
},
|
||||
.id_table = sh_mtu2_id_table,
|
||||
};
|
||||
|
||||
static int __init sh_mtu2_init(void)
|
||||
|
@ -11,35 +11,41 @@
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/sh_timer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/sh_timer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
enum sh_tmu_model {
|
||||
SH_TMU_LEGACY,
|
||||
SH_TMU,
|
||||
SH_TMU_SH3,
|
||||
};
|
||||
|
||||
struct sh_tmu_device;
|
||||
|
||||
struct sh_tmu_channel {
|
||||
struct sh_tmu_device *tmu;
|
||||
unsigned int index;
|
||||
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
|
||||
struct sh_tmu_priv {
|
||||
void __iomem *mapbase;
|
||||
struct clk *clk;
|
||||
struct irqaction irqaction;
|
||||
struct platform_device *pdev;
|
||||
unsigned long rate;
|
||||
unsigned long periodic;
|
||||
struct clock_event_device ced;
|
||||
@ -48,6 +54,21 @@ struct sh_tmu_priv {
|
||||
unsigned int enable_count;
|
||||
};
|
||||
|
||||
struct sh_tmu_device {
|
||||
struct platform_device *pdev;
|
||||
|
||||
void __iomem *mapbase;
|
||||
struct clk *clk;
|
||||
|
||||
enum sh_tmu_model model;
|
||||
|
||||
struct sh_tmu_channel *channels;
|
||||
unsigned int num_channels;
|
||||
|
||||
bool has_clockevent;
|
||||
bool has_clocksource;
|
||||
};
|
||||
|
||||
static DEFINE_RAW_SPINLOCK(sh_tmu_lock);
|
||||
|
||||
#define TSTR -1 /* shared register */
|
||||
@ -55,189 +76,208 @@ static DEFINE_RAW_SPINLOCK(sh_tmu_lock);
|
||||
#define TCNT 1 /* channel register */
|
||||
#define TCR 2 /* channel register */
|
||||
|
||||
static inline unsigned long sh_tmu_read(struct sh_tmu_priv *p, int reg_nr)
|
||||
#define TCR_UNF (1 << 8)
|
||||
#define TCR_UNIE (1 << 5)
|
||||
#define TCR_TPSC_CLK4 (0 << 0)
|
||||
#define TCR_TPSC_CLK16 (1 << 0)
|
||||
#define TCR_TPSC_CLK64 (2 << 0)
|
||||
#define TCR_TPSC_CLK256 (3 << 0)
|
||||
#define TCR_TPSC_CLK1024 (4 << 0)
|
||||
#define TCR_TPSC_MASK (7 << 0)
|
||||
|
||||
static inline unsigned long sh_tmu_read(struct sh_tmu_channel *ch, int reg_nr)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
void __iomem *base = p->mapbase;
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR)
|
||||
return ioread8(base - cfg->channel_offset);
|
||||
|
||||
offs = reg_nr << 2;
|
||||
|
||||
if (reg_nr == TCR)
|
||||
return ioread16(base + offs);
|
||||
else
|
||||
return ioread32(base + offs);
|
||||
}
|
||||
|
||||
static inline void sh_tmu_write(struct sh_tmu_priv *p, int reg_nr,
|
||||
unsigned long value)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
void __iomem *base = p->mapbase;
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR) {
|
||||
iowrite8(value, base - cfg->channel_offset);
|
||||
return;
|
||||
switch (ch->tmu->model) {
|
||||
case SH_TMU_LEGACY:
|
||||
return ioread8(ch->tmu->mapbase);
|
||||
case SH_TMU_SH3:
|
||||
return ioread8(ch->tmu->mapbase + 2);
|
||||
case SH_TMU:
|
||||
return ioread8(ch->tmu->mapbase + 4);
|
||||
}
|
||||
}
|
||||
|
||||
offs = reg_nr << 2;
|
||||
|
||||
if (reg_nr == TCR)
|
||||
iowrite16(value, base + offs);
|
||||
return ioread16(ch->base + offs);
|
||||
else
|
||||
iowrite32(value, base + offs);
|
||||
return ioread32(ch->base + offs);
|
||||
}
|
||||
|
||||
static void sh_tmu_start_stop_ch(struct sh_tmu_priv *p, int start)
|
||||
static inline void sh_tmu_write(struct sh_tmu_channel *ch, int reg_nr,
|
||||
unsigned long value)
|
||||
{
|
||||
unsigned long offs;
|
||||
|
||||
if (reg_nr == TSTR) {
|
||||
switch (ch->tmu->model) {
|
||||
case SH_TMU_LEGACY:
|
||||
return iowrite8(value, ch->tmu->mapbase);
|
||||
case SH_TMU_SH3:
|
||||
return iowrite8(value, ch->tmu->mapbase + 2);
|
||||
case SH_TMU:
|
||||
return iowrite8(value, ch->tmu->mapbase + 4);
|
||||
}
|
||||
}
|
||||
|
||||
offs = reg_nr << 2;
|
||||
|
||||
if (reg_nr == TCR)
|
||||
iowrite16(value, ch->base + offs);
|
||||
else
|
||||
iowrite32(value, ch->base + offs);
|
||||
}
|
||||
|
||||
static void sh_tmu_start_stop_ch(struct sh_tmu_channel *ch, int start)
|
||||
{
|
||||
struct sh_timer_config *cfg = p->pdev->dev.platform_data;
|
||||
unsigned long flags, value;
|
||||
|
||||
/* start stop register shared by multiple timer channels */
|
||||
raw_spin_lock_irqsave(&sh_tmu_lock, flags);
|
||||
value = sh_tmu_read(p, TSTR);
|
||||
value = sh_tmu_read(ch, TSTR);
|
||||
|
||||
if (start)
|
||||
value |= 1 << cfg->timer_bit;
|
||||
value |= 1 << ch->index;
|
||||
else
|
||||
value &= ~(1 << cfg->timer_bit);
|
||||
value &= ~(1 << ch->index);
|
||||
|
||||
sh_tmu_write(p, TSTR, value);
|
||||
sh_tmu_write(ch, TSTR, value);
|
||||
raw_spin_unlock_irqrestore(&sh_tmu_lock, flags);
|
||||
}
|
||||
|
||||
static int __sh_tmu_enable(struct sh_tmu_priv *p)
|
||||
static int __sh_tmu_enable(struct sh_tmu_channel *ch)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* enable clock */
|
||||
ret = clk_enable(p->clk);
|
||||
ret = clk_enable(ch->tmu->clk);
|
||||
if (ret) {
|
||||
dev_err(&p->pdev->dev, "cannot enable clock\n");
|
||||
dev_err(&ch->tmu->pdev->dev, "ch%u: cannot enable clock\n",
|
||||
ch->index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* make sure channel is disabled */
|
||||
sh_tmu_start_stop_ch(p, 0);
|
||||
sh_tmu_start_stop_ch(ch, 0);
|
||||
|
||||
/* maximum timeout */
|
||||
sh_tmu_write(p, TCOR, 0xffffffff);
|
||||
sh_tmu_write(p, TCNT, 0xffffffff);
|
||||
sh_tmu_write(ch, TCOR, 0xffffffff);
|
||||
sh_tmu_write(ch, TCNT, 0xffffffff);
|
||||
|
||||
/* configure channel to parent clock / 4, irq off */
|
||||
p->rate = clk_get_rate(p->clk) / 4;
|
||||
sh_tmu_write(p, TCR, 0x0000);
|
||||
ch->rate = clk_get_rate(ch->tmu->clk) / 4;
|
||||
sh_tmu_write(ch, TCR, TCR_TPSC_CLK4);
|
||||
|
||||
/* enable channel */
|
||||
sh_tmu_start_stop_ch(p, 1);
|
||||
sh_tmu_start_stop_ch(ch, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_tmu_enable(struct sh_tmu_priv *p)
|
||||
static int sh_tmu_enable(struct sh_tmu_channel *ch)
|
||||
{
|
||||
if (p->enable_count++ > 0)
|
||||
if (ch->enable_count++ > 0)
|
||||
return 0;
|
||||
|
||||
pm_runtime_get_sync(&p->pdev->dev);
|
||||
dev_pm_syscore_device(&p->pdev->dev, true);
|
||||
pm_runtime_get_sync(&ch->tmu->pdev->dev);
|
||||
dev_pm_syscore_device(&ch->tmu->pdev->dev, true);
|
||||
|
||||
return __sh_tmu_enable(p);
|
||||
return __sh_tmu_enable(ch);
|
||||
}
|
||||
|
||||
static void __sh_tmu_disable(struct sh_tmu_priv *p)
|
||||
static void __sh_tmu_disable(struct sh_tmu_channel *ch)
|
||||
{
|
||||
/* disable channel */
|
||||
sh_tmu_start_stop_ch(p, 0);
|
||||
sh_tmu_start_stop_ch(ch, 0);
|
||||
|
||||
/* disable interrupts in TMU block */
|
||||
sh_tmu_write(p, TCR, 0x0000);
|
||||
sh_tmu_write(ch, TCR, TCR_TPSC_CLK4);
|
||||
|
||||
/* stop clock */
|
||||
clk_disable(p->clk);
|
||||
clk_disable(ch->tmu->clk);
|
||||
}
|
||||
|
||||
static void sh_tmu_disable(struct sh_tmu_priv *p)
|
||||
static void sh_tmu_disable(struct sh_tmu_channel *ch)
|
||||
{
|
||||
if (WARN_ON(p->enable_count == 0))
|
||||
if (WARN_ON(ch->enable_count == 0))
|
||||
return;
|
||||
|
||||
if (--p->enable_count > 0)
|
||||
if (--ch->enable_count > 0)
|
||||
return;
|
||||
|
||||
__sh_tmu_disable(p);
|
||||
__sh_tmu_disable(ch);
|
||||
|
||||
dev_pm_syscore_device(&p->pdev->dev, false);
|
||||
pm_runtime_put(&p->pdev->dev);
|
||||
dev_pm_syscore_device(&ch->tmu->pdev->dev, false);
|
||||
pm_runtime_put(&ch->tmu->pdev->dev);
|
||||
}
|
||||
|
||||
static void sh_tmu_set_next(struct sh_tmu_priv *p, unsigned long delta,
|
||||
static void sh_tmu_set_next(struct sh_tmu_channel *ch, unsigned long delta,
|
||||
int periodic)
|
||||
{
|
||||
/* stop timer */
|
||||
sh_tmu_start_stop_ch(p, 0);
|
||||
sh_tmu_start_stop_ch(ch, 0);
|
||||
|
||||
/* acknowledge interrupt */
|
||||
sh_tmu_read(p, TCR);
|
||||
sh_tmu_read(ch, TCR);
|
||||
|
||||
/* enable interrupt */
|
||||
sh_tmu_write(p, TCR, 0x0020);
|
||||
sh_tmu_write(ch, TCR, TCR_UNIE | TCR_TPSC_CLK4);
|
||||
|
||||
/* reload delta value in case of periodic timer */
|
||||
if (periodic)
|
||||
sh_tmu_write(p, TCOR, delta);
|
||||
sh_tmu_write(ch, TCOR, delta);
|
||||
else
|
||||
sh_tmu_write(p, TCOR, 0xffffffff);
|
||||
sh_tmu_write(ch, TCOR, 0xffffffff);
|
||||
|
||||
sh_tmu_write(p, TCNT, delta);
|
||||
sh_tmu_write(ch, TCNT, delta);
|
||||
|
||||
/* start timer */
|
||||
sh_tmu_start_stop_ch(p, 1);
|
||||
sh_tmu_start_stop_ch(ch, 1);
|
||||
}
|
||||
|
||||
static irqreturn_t sh_tmu_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct sh_tmu_priv *p = dev_id;
|
||||
struct sh_tmu_channel *ch = dev_id;
|
||||
|
||||
/* disable or acknowledge interrupt */
|
||||
if (p->ced.mode == CLOCK_EVT_MODE_ONESHOT)
|
||||
sh_tmu_write(p, TCR, 0x0000);
|
||||
if (ch->ced.mode == CLOCK_EVT_MODE_ONESHOT)
|
||||
sh_tmu_write(ch, TCR, TCR_TPSC_CLK4);
|
||||
else
|
||||
sh_tmu_write(p, TCR, 0x0020);
|
||||
sh_tmu_write(ch, TCR, TCR_UNIE | TCR_TPSC_CLK4);
|
||||
|
||||
/* notify clockevent layer */
|
||||
p->ced.event_handler(&p->ced);
|
||||
ch->ced.event_handler(&ch->ced);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct sh_tmu_priv *cs_to_sh_tmu(struct clocksource *cs)
|
||||
static struct sh_tmu_channel *cs_to_sh_tmu(struct clocksource *cs)
|
||||
{
|
||||
return container_of(cs, struct sh_tmu_priv, cs);
|
||||
return container_of(cs, struct sh_tmu_channel, cs);
|
||||
}
|
||||
|
||||
static cycle_t sh_tmu_clocksource_read(struct clocksource *cs)
|
||||
{
|
||||
struct sh_tmu_priv *p = cs_to_sh_tmu(cs);
|
||||
struct sh_tmu_channel *ch = cs_to_sh_tmu(cs);
|
||||
|
||||
return sh_tmu_read(p, TCNT) ^ 0xffffffff;
|
||||
return sh_tmu_read(ch, TCNT) ^ 0xffffffff;
|
||||
}
|
||||
|
||||
static int sh_tmu_clocksource_enable(struct clocksource *cs)
|
||||
{
|
||||
struct sh_tmu_priv *p = cs_to_sh_tmu(cs);
|
||||
struct sh_tmu_channel *ch = cs_to_sh_tmu(cs);
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(p->cs_enabled))
|
||||
if (WARN_ON(ch->cs_enabled))
|
||||
return 0;
|
||||
|
||||
ret = sh_tmu_enable(p);
|
||||
ret = sh_tmu_enable(ch);
|
||||
if (!ret) {
|
||||
__clocksource_updatefreq_hz(cs, p->rate);
|
||||
p->cs_enabled = true;
|
||||
__clocksource_updatefreq_hz(cs, ch->rate);
|
||||
ch->cs_enabled = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -245,48 +285,48 @@ static int sh_tmu_clocksource_enable(struct clocksource *cs)
|
||||
|
||||
static void sh_tmu_clocksource_disable(struct clocksource *cs)
|
||||
{
|
||||
struct sh_tmu_priv *p = cs_to_sh_tmu(cs);
|
||||
struct sh_tmu_channel *ch = cs_to_sh_tmu(cs);
|
||||
|
||||
if (WARN_ON(!p->cs_enabled))
|
||||
if (WARN_ON(!ch->cs_enabled))
|
||||
return;
|
||||
|
||||
sh_tmu_disable(p);
|
||||
p->cs_enabled = false;
|
||||
sh_tmu_disable(ch);
|
||||
ch->cs_enabled = false;
|
||||
}
|
||||
|
||||
static void sh_tmu_clocksource_suspend(struct clocksource *cs)
|
||||
{
|
||||
struct sh_tmu_priv *p = cs_to_sh_tmu(cs);
|
||||
struct sh_tmu_channel *ch = cs_to_sh_tmu(cs);
|
||||
|
||||
if (!p->cs_enabled)
|
||||
if (!ch->cs_enabled)
|
||||
return;
|
||||
|
||||
if (--p->enable_count == 0) {
|
||||
__sh_tmu_disable(p);
|
||||
pm_genpd_syscore_poweroff(&p->pdev->dev);
|
||||
if (--ch->enable_count == 0) {
|
||||
__sh_tmu_disable(ch);
|
||||
pm_genpd_syscore_poweroff(&ch->tmu->pdev->dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void sh_tmu_clocksource_resume(struct clocksource *cs)
|
||||
{
|
||||
struct sh_tmu_priv *p = cs_to_sh_tmu(cs);
|
||||
struct sh_tmu_channel *ch = cs_to_sh_tmu(cs);
|
||||
|
||||
if (!p->cs_enabled)
|
||||
if (!ch->cs_enabled)
|
||||
return;
|
||||
|
||||
if (p->enable_count++ == 0) {
|
||||
pm_genpd_syscore_poweron(&p->pdev->dev);
|
||||
__sh_tmu_enable(p);
|
||||
if (ch->enable_count++ == 0) {
|
||||
pm_genpd_syscore_poweron(&ch->tmu->pdev->dev);
|
||||
__sh_tmu_enable(ch);
|
||||
}
|
||||
}
|
||||
|
||||
static int sh_tmu_register_clocksource(struct sh_tmu_priv *p,
|
||||
char *name, unsigned long rating)
|
||||
static int sh_tmu_register_clocksource(struct sh_tmu_channel *ch,
|
||||
const char *name)
|
||||
{
|
||||
struct clocksource *cs = &p->cs;
|
||||
struct clocksource *cs = &ch->cs;
|
||||
|
||||
cs->name = name;
|
||||
cs->rating = rating;
|
||||
cs->rating = 200;
|
||||
cs->read = sh_tmu_clocksource_read;
|
||||
cs->enable = sh_tmu_clocksource_enable;
|
||||
cs->disable = sh_tmu_clocksource_disable;
|
||||
@ -295,43 +335,44 @@ static int sh_tmu_register_clocksource(struct sh_tmu_priv *p,
|
||||
cs->mask = CLOCKSOURCE_MASK(32);
|
||||
cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
|
||||
dev_info(&p->pdev->dev, "used as clock source\n");
|
||||
dev_info(&ch->tmu->pdev->dev, "ch%u: used as clock source\n",
|
||||
ch->index);
|
||||
|
||||
/* Register with dummy 1 Hz value, gets updated in ->enable() */
|
||||
clocksource_register_hz(cs, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sh_tmu_priv *ced_to_sh_tmu(struct clock_event_device *ced)
|
||||
static struct sh_tmu_channel *ced_to_sh_tmu(struct clock_event_device *ced)
|
||||
{
|
||||
return container_of(ced, struct sh_tmu_priv, ced);
|
||||
return container_of(ced, struct sh_tmu_channel, ced);
|
||||
}
|
||||
|
||||
static void sh_tmu_clock_event_start(struct sh_tmu_priv *p, int periodic)
|
||||
static void sh_tmu_clock_event_start(struct sh_tmu_channel *ch, int periodic)
|
||||
{
|
||||
struct clock_event_device *ced = &p->ced;
|
||||
struct clock_event_device *ced = &ch->ced;
|
||||
|
||||
sh_tmu_enable(p);
|
||||
sh_tmu_enable(ch);
|
||||
|
||||
clockevents_config(ced, p->rate);
|
||||
clockevents_config(ced, ch->rate);
|
||||
|
||||
if (periodic) {
|
||||
p->periodic = (p->rate + HZ/2) / HZ;
|
||||
sh_tmu_set_next(p, p->periodic, 1);
|
||||
ch->periodic = (ch->rate + HZ/2) / HZ;
|
||||
sh_tmu_set_next(ch, ch->periodic, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void sh_tmu_clock_event_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *ced)
|
||||
{
|
||||
struct sh_tmu_priv *p = ced_to_sh_tmu(ced);
|
||||
struct sh_tmu_channel *ch = ced_to_sh_tmu(ced);
|
||||
int disabled = 0;
|
||||
|
||||
/* deal with old setting first */
|
||||
switch (ced->mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
case CLOCK_EVT_MODE_ONESHOT:
|
||||
sh_tmu_disable(p);
|
||||
sh_tmu_disable(ch);
|
||||
disabled = 1;
|
||||
break;
|
||||
default:
|
||||
@ -340,16 +381,18 @@ static void sh_tmu_clock_event_mode(enum clock_event_mode mode,
|
||||
|
||||
switch (mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
dev_info(&p->pdev->dev, "used for periodic clock events\n");
|
||||
sh_tmu_clock_event_start(p, 1);
|
||||
dev_info(&ch->tmu->pdev->dev,
|
||||
"ch%u: used for periodic clock events\n", ch->index);
|
||||
sh_tmu_clock_event_start(ch, 1);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_ONESHOT:
|
||||
dev_info(&p->pdev->dev, "used for oneshot clock events\n");
|
||||
sh_tmu_clock_event_start(p, 0);
|
||||
dev_info(&ch->tmu->pdev->dev,
|
||||
"ch%u: used for oneshot clock events\n", ch->index);
|
||||
sh_tmu_clock_event_start(ch, 0);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_UNUSED:
|
||||
if (!disabled)
|
||||
sh_tmu_disable(p);
|
||||
sh_tmu_disable(ch);
|
||||
break;
|
||||
case CLOCK_EVT_MODE_SHUTDOWN:
|
||||
default:
|
||||
@ -360,147 +403,234 @@ static void sh_tmu_clock_event_mode(enum clock_event_mode mode,
|
||||
static int sh_tmu_clock_event_next(unsigned long delta,
|
||||
struct clock_event_device *ced)
|
||||
{
|
||||
struct sh_tmu_priv *p = ced_to_sh_tmu(ced);
|
||||
struct sh_tmu_channel *ch = ced_to_sh_tmu(ced);
|
||||
|
||||
BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT);
|
||||
|
||||
/* program new delta value */
|
||||
sh_tmu_set_next(p, delta, 0);
|
||||
sh_tmu_set_next(ch, delta, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh_tmu_clock_event_suspend(struct clock_event_device *ced)
|
||||
{
|
||||
pm_genpd_syscore_poweroff(&ced_to_sh_tmu(ced)->pdev->dev);
|
||||
pm_genpd_syscore_poweroff(&ced_to_sh_tmu(ced)->tmu->pdev->dev);
|
||||
}
|
||||
|
||||
static void sh_tmu_clock_event_resume(struct clock_event_device *ced)
|
||||
{
|
||||
pm_genpd_syscore_poweron(&ced_to_sh_tmu(ced)->pdev->dev);
|
||||
pm_genpd_syscore_poweron(&ced_to_sh_tmu(ced)->tmu->pdev->dev);
|
||||
}
|
||||
|
||||
static void sh_tmu_register_clockevent(struct sh_tmu_priv *p,
|
||||
char *name, unsigned long rating)
|
||||
static void sh_tmu_register_clockevent(struct sh_tmu_channel *ch,
|
||||
const char *name)
|
||||
{
|
||||
struct clock_event_device *ced = &p->ced;
|
||||
struct clock_event_device *ced = &ch->ced;
|
||||
int ret;
|
||||
|
||||
memset(ced, 0, sizeof(*ced));
|
||||
|
||||
ced->name = name;
|
||||
ced->features = CLOCK_EVT_FEAT_PERIODIC;
|
||||
ced->features |= CLOCK_EVT_FEAT_ONESHOT;
|
||||
ced->rating = rating;
|
||||
ced->rating = 200;
|
||||
ced->cpumask = cpumask_of(0);
|
||||
ced->set_next_event = sh_tmu_clock_event_next;
|
||||
ced->set_mode = sh_tmu_clock_event_mode;
|
||||
ced->suspend = sh_tmu_clock_event_suspend;
|
||||
ced->resume = sh_tmu_clock_event_resume;
|
||||
|
||||
dev_info(&p->pdev->dev, "used for clock events\n");
|
||||
dev_info(&ch->tmu->pdev->dev, "ch%u: used for clock events\n",
|
||||
ch->index);
|
||||
|
||||
clockevents_config_and_register(ced, 1, 0x300, 0xffffffff);
|
||||
|
||||
ret = setup_irq(p->irqaction.irq, &p->irqaction);
|
||||
ret = request_irq(ch->irq, sh_tmu_interrupt,
|
||||
IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
|
||||
dev_name(&ch->tmu->pdev->dev), ch);
|
||||
if (ret) {
|
||||
dev_err(&p->pdev->dev, "failed to request irq %d\n",
|
||||
p->irqaction.irq);
|
||||
dev_err(&ch->tmu->pdev->dev, "ch%u: failed to request irq %d\n",
|
||||
ch->index, ch->irq);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int sh_tmu_register(struct sh_tmu_priv *p, char *name,
|
||||
unsigned long clockevent_rating,
|
||||
unsigned long clocksource_rating)
|
||||
static int sh_tmu_register(struct sh_tmu_channel *ch, const char *name,
|
||||
bool clockevent, bool clocksource)
|
||||
{
|
||||
if (clockevent_rating)
|
||||
sh_tmu_register_clockevent(p, name, clockevent_rating);
|
||||
else if (clocksource_rating)
|
||||
sh_tmu_register_clocksource(p, name, clocksource_rating);
|
||||
if (clockevent) {
|
||||
ch->tmu->has_clockevent = true;
|
||||
sh_tmu_register_clockevent(ch, name);
|
||||
} else if (clocksource) {
|
||||
ch->tmu->has_clocksource = true;
|
||||
sh_tmu_register_clocksource(ch, name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_tmu_setup(struct sh_tmu_priv *p, struct platform_device *pdev)
|
||||
static int sh_tmu_channel_setup(struct sh_tmu_channel *ch, unsigned int index,
|
||||
bool clockevent, bool clocksource,
|
||||
struct sh_tmu_device *tmu)
|
||||
{
|
||||
/* Skip unused channels. */
|
||||
if (!clockevent && !clocksource)
|
||||
return 0;
|
||||
|
||||
ch->tmu = tmu;
|
||||
|
||||
if (tmu->model == SH_TMU_LEGACY) {
|
||||
struct sh_timer_config *cfg = tmu->pdev->dev.platform_data;
|
||||
|
||||
/*
|
||||
* The SH3 variant (SH770x, SH7705, SH7710 and SH7720) maps
|
||||
* channel registers blocks at base + 2 + 12 * index, while all
|
||||
* other variants map them at base + 4 + 12 * index. We can
|
||||
* compute the index by just dividing by 12, the 2 bytes or 4
|
||||
* bytes offset being hidden by the integer division.
|
||||
*/
|
||||
ch->index = cfg->channel_offset / 12;
|
||||
ch->base = tmu->mapbase + cfg->channel_offset;
|
||||
} else {
|
||||
ch->index = index;
|
||||
|
||||
if (tmu->model == SH_TMU_SH3)
|
||||
ch->base = tmu->mapbase + 4 + ch->index * 12;
|
||||
else
|
||||
ch->base = tmu->mapbase + 8 + ch->index * 12;
|
||||
}
|
||||
|
||||
ch->irq = platform_get_irq(tmu->pdev, index);
|
||||
if (ch->irq < 0) {
|
||||
dev_err(&tmu->pdev->dev, "ch%u: failed to get irq\n",
|
||||
ch->index);
|
||||
return ch->irq;
|
||||
}
|
||||
|
||||
ch->cs_enabled = false;
|
||||
ch->enable_count = 0;
|
||||
|
||||
return sh_tmu_register(ch, dev_name(&tmu->pdev->dev),
|
||||
clockevent, clocksource);
|
||||
}
|
||||
|
||||
static int sh_tmu_map_memory(struct sh_tmu_device *tmu)
|
||||
{
|
||||
struct resource *res;
|
||||
|
||||
res = platform_get_resource(tmu->pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&tmu->pdev->dev, "failed to get I/O memory\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
tmu->mapbase = ioremap_nocache(res->start, resource_size(res));
|
||||
if (tmu->mapbase == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
/*
|
||||
* In legacy platform device configuration (with one device per channel)
|
||||
* the resource points to the channel base address.
|
||||
*/
|
||||
if (tmu->model == SH_TMU_LEGACY) {
|
||||
struct sh_timer_config *cfg = tmu->pdev->dev.platform_data;
|
||||
tmu->mapbase -= cfg->channel_offset;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh_tmu_unmap_memory(struct sh_tmu_device *tmu)
|
||||
{
|
||||
if (tmu->model == SH_TMU_LEGACY) {
|
||||
struct sh_timer_config *cfg = tmu->pdev->dev.platform_data;
|
||||
tmu->mapbase += cfg->channel_offset;
|
||||
}
|
||||
|
||||
iounmap(tmu->mapbase);
|
||||
}
|
||||
|
||||
static int sh_tmu_setup(struct sh_tmu_device *tmu, struct platform_device *pdev)
|
||||
{
|
||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||
struct resource *res;
|
||||
int irq, ret;
|
||||
ret = -ENXIO;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->pdev = pdev;
|
||||
const struct platform_device_id *id = pdev->id_entry;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
if (!cfg) {
|
||||
dev_err(&p->pdev->dev, "missing platform data\n");
|
||||
goto err0;
|
||||
dev_err(&tmu->pdev->dev, "missing platform data\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, p);
|
||||
tmu->pdev = pdev;
|
||||
tmu->model = id->driver_data;
|
||||
|
||||
res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&p->pdev->dev, "failed to get I/O memory\n");
|
||||
goto err0;
|
||||
/* Get hold of clock. */
|
||||
tmu->clk = clk_get(&tmu->pdev->dev,
|
||||
tmu->model == SH_TMU_LEGACY ? "tmu_fck" : "fck");
|
||||
if (IS_ERR(tmu->clk)) {
|
||||
dev_err(&tmu->pdev->dev, "cannot get clock\n");
|
||||
return PTR_ERR(tmu->clk);
|
||||
}
|
||||
|
||||
irq = platform_get_irq(p->pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&p->pdev->dev, "failed to get irq\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
/* map memory, let mapbase point to our channel */
|
||||
p->mapbase = ioremap_nocache(res->start, resource_size(res));
|
||||
if (p->mapbase == NULL) {
|
||||
dev_err(&p->pdev->dev, "failed to remap I/O memory\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
/* setup data for setup_irq() (too early for request_irq()) */
|
||||
p->irqaction.name = dev_name(&p->pdev->dev);
|
||||
p->irqaction.handler = sh_tmu_interrupt;
|
||||
p->irqaction.dev_id = p;
|
||||
p->irqaction.irq = irq;
|
||||
p->irqaction.flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING;
|
||||
|
||||
/* get hold of clock */
|
||||
p->clk = clk_get(&p->pdev->dev, "tmu_fck");
|
||||
if (IS_ERR(p->clk)) {
|
||||
dev_err(&p->pdev->dev, "cannot get clock\n");
|
||||
ret = PTR_ERR(p->clk);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
ret = clk_prepare(p->clk);
|
||||
ret = clk_prepare(tmu->clk);
|
||||
if (ret < 0)
|
||||
goto err2;
|
||||
goto err_clk_put;
|
||||
|
||||
p->cs_enabled = false;
|
||||
p->enable_count = 0;
|
||||
/* Map the memory resource. */
|
||||
ret = sh_tmu_map_memory(tmu);
|
||||
if (ret < 0) {
|
||||
dev_err(&tmu->pdev->dev, "failed to remap I/O memory\n");
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
ret = sh_tmu_register(p, (char *)dev_name(&p->pdev->dev),
|
||||
cfg->clockevent_rating,
|
||||
cfg->clocksource_rating);
|
||||
if (ret < 0)
|
||||
goto err3;
|
||||
/* Allocate and setup the channels. */
|
||||
if (tmu->model == SH_TMU_LEGACY)
|
||||
tmu->num_channels = 1;
|
||||
else
|
||||
tmu->num_channels = hweight8(cfg->channels_mask);
|
||||
|
||||
tmu->channels = kzalloc(sizeof(*tmu->channels) * tmu->num_channels,
|
||||
GFP_KERNEL);
|
||||
if (tmu->channels == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
if (tmu->model == SH_TMU_LEGACY) {
|
||||
ret = sh_tmu_channel_setup(&tmu->channels[0], 0,
|
||||
cfg->clockevent_rating != 0,
|
||||
cfg->clocksource_rating != 0, tmu);
|
||||
if (ret < 0)
|
||||
goto err_unmap;
|
||||
} else {
|
||||
/*
|
||||
* Use the first channel as a clock event device and the second
|
||||
* channel as a clock source.
|
||||
*/
|
||||
for (i = 0; i < tmu->num_channels; ++i) {
|
||||
ret = sh_tmu_channel_setup(&tmu->channels[i], i,
|
||||
i == 0, i == 1, tmu);
|
||||
if (ret < 0)
|
||||
goto err_unmap;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, tmu);
|
||||
|
||||
return 0;
|
||||
|
||||
err3:
|
||||
clk_unprepare(p->clk);
|
||||
err2:
|
||||
clk_put(p->clk);
|
||||
err1:
|
||||
iounmap(p->mapbase);
|
||||
err0:
|
||||
err_unmap:
|
||||
kfree(tmu->channels);
|
||||
sh_tmu_unmap_memory(tmu);
|
||||
err_clk_unprepare:
|
||||
clk_unprepare(tmu->clk);
|
||||
err_clk_put:
|
||||
clk_put(tmu->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_tmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sh_tmu_priv *p = platform_get_drvdata(pdev);
|
||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||
struct sh_tmu_device *tmu = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
if (!is_early_platform_device(pdev)) {
|
||||
@ -508,20 +638,18 @@ static int sh_tmu_probe(struct platform_device *pdev)
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
}
|
||||
|
||||
if (p) {
|
||||
if (tmu) {
|
||||
dev_info(&pdev->dev, "kept as earlytimer\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = kmalloc(sizeof(*p), GFP_KERNEL);
|
||||
if (p == NULL) {
|
||||
dev_err(&pdev->dev, "failed to allocate driver data\n");
|
||||
tmu = kzalloc(sizeof(*tmu), GFP_KERNEL);
|
||||
if (tmu == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = sh_tmu_setup(p, pdev);
|
||||
ret = sh_tmu_setup(tmu, pdev);
|
||||
if (ret) {
|
||||
kfree(p);
|
||||
kfree(tmu);
|
||||
pm_runtime_idle(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
@ -529,7 +657,7 @@ static int sh_tmu_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (cfg->clockevent_rating || cfg->clocksource_rating)
|
||||
if (tmu->has_clockevent || tmu->has_clocksource)
|
||||
pm_runtime_irq_safe(&pdev->dev);
|
||||
else
|
||||
pm_runtime_idle(&pdev->dev);
|
||||
@ -542,12 +670,21 @@ static int sh_tmu_remove(struct platform_device *pdev)
|
||||
return -EBUSY; /* cannot unregister clockevent and clocksource */
|
||||
}
|
||||
|
||||
static const struct platform_device_id sh_tmu_id_table[] = {
|
||||
{ "sh_tmu", SH_TMU_LEGACY },
|
||||
{ "sh-tmu", SH_TMU },
|
||||
{ "sh-tmu-sh3", SH_TMU_SH3 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, sh_tmu_id_table);
|
||||
|
||||
static struct platform_driver sh_tmu_device_driver = {
|
||||
.probe = sh_tmu_probe,
|
||||
.remove = sh_tmu_remove,
|
||||
.driver = {
|
||||
.name = "sh_tmu",
|
||||
}
|
||||
},
|
||||
.id_table = sh_tmu_id_table,
|
||||
};
|
||||
|
||||
static int __init sh_tmu_init(void)
|
||||
|
@ -272,4 +272,5 @@ static void __init efm32_timer_init(struct device_node *np)
|
||||
}
|
||||
}
|
||||
}
|
||||
CLOCKSOURCE_OF_DECLARE(efm32, "efm32,timer", efm32_timer_init);
|
||||
CLOCKSOURCE_OF_DECLARE(efm32compat, "efm32,timer", efm32_timer_init);
|
||||
CLOCKSOURCE_OF_DECLARE(efm32, "energymicro,efm32-timer", efm32_timer_init);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sched_clock.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
@ -143,6 +144,7 @@ static u64 sun5i_timer_sched_read(void)
|
||||
|
||||
static void __init sun5i_timer_init(struct device_node *node)
|
||||
{
|
||||
struct reset_control *rstc;
|
||||
unsigned long rate;
|
||||
struct clk *clk;
|
||||
int ret, irq;
|
||||
@ -162,6 +164,10 @@ static void __init sun5i_timer_init(struct device_node *node)
|
||||
clk_prepare_enable(clk);
|
||||
rate = clk_get_rate(clk);
|
||||
|
||||
rstc = of_reset_control_get(node, NULL);
|
||||
if (!IS_ERR(rstc))
|
||||
reset_control_deassert(rstc);
|
||||
|
||||
writel(~0, timer_base + TIMER_INTVAL_LO_REG(1));
|
||||
writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
|
||||
timer_base + TIMER_CTL_REG(1));
|
||||
|
@ -7,6 +7,7 @@ struct sh_timer_config {
|
||||
int timer_bit;
|
||||
unsigned long clockevent_rating;
|
||||
unsigned long clocksource_rating;
|
||||
unsigned int channels_mask;
|
||||
};
|
||||
|
||||
#endif /* __SH_TIMER_H__ */
|
||||
|
Loading…
Reference in New Issue
Block a user