mirror of https://gitee.com/openkylin/linux.git
Merge branch 'ingenic-tcu-v5.4' into mips-next
Merge the Ingenic TCU patchset from the ingenic-tcu-v5.4 branch which was created to enable follow-on changes in other subsystems. Signed-off-by: Paul Burton <paul.burton@mips.com>
This commit is contained in:
commit
75b7329a4f
|
@ -1,22 +0,0 @@
|
|||
Ingenic JZ47xx PWM Controller
|
||||
=============================
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "ingenic,jz4740-pwm"
|
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description
|
||||
of the cells format.
|
||||
- clocks : phandle to the external clock.
|
||||
- clock-names : Should be "ext".
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
pwm: pwm@10002000 {
|
||||
compatible = "ingenic,jz4740-pwm";
|
||||
reg = <0x10002000 0x1000>;
|
||||
|
||||
#pwm-cells = <3>;
|
||||
|
||||
clocks = <&ext>;
|
||||
clock-names = "ext";
|
||||
};
|
|
@ -0,0 +1,137 @@
|
|||
Ingenic JZ47xx SoCs Timer/Counter Unit devicetree bindings
|
||||
==========================================================
|
||||
|
||||
For a description of the TCU hardware and drivers, have a look at
|
||||
Documentation/mips/ingenic-tcu.txt.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be one of:
|
||||
* ingenic,jz4740-tcu
|
||||
* ingenic,jz4725b-tcu
|
||||
* ingenic,jz4770-tcu
|
||||
followed by "simple-mfd".
|
||||
- reg: Should be the offset/length value corresponding to the TCU registers
|
||||
- clocks: List of phandle & clock specifiers for clocks external to the TCU.
|
||||
The "pclk", "rtc" and "ext" clocks should be provided. The "tcu" clock
|
||||
should be provided if the SoC has it.
|
||||
- clock-names: List of name strings for the external clocks.
|
||||
- #clock-cells: Should be <1>;
|
||||
Clock consumers specify this argument to identify a clock. The valid values
|
||||
may be found in <dt-bindings/clock/ingenic,tcu.h>.
|
||||
- interrupt-controller : Identifies the node as an interrupt controller
|
||||
- #interrupt-cells : Specifies the number of cells needed to encode an
|
||||
interrupt source. The value should be 1.
|
||||
- interrupts : Specifies the interrupt the controller is connected to.
|
||||
|
||||
Optional properties:
|
||||
|
||||
- ingenic,pwm-channels-mask: Bitmask of TCU channels reserved for PWM use.
|
||||
Default value is 0xfc.
|
||||
|
||||
|
||||
Children nodes
|
||||
==========================================================
|
||||
|
||||
|
||||
PWM node:
|
||||
---------
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be one of:
|
||||
* ingenic,jz4740-pwm
|
||||
* ingenic,jz4725b-pwm
|
||||
- #pwm-cells: Should be 3. See ../pwm/pwm.txt for a description of the cell
|
||||
format.
|
||||
- clocks: List of phandle & clock specifiers for the TCU clocks.
|
||||
- clock-names: List of name strings for the TCU clocks.
|
||||
|
||||
|
||||
Watchdog node:
|
||||
--------------
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be "ingenic,jz4740-watchdog"
|
||||
- clocks: phandle to the WDT clock
|
||||
- clock-names: should be "wdt"
|
||||
|
||||
|
||||
OS Timer node:
|
||||
---------
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Must be one of:
|
||||
* ingenic,jz4725b-ost
|
||||
* ingenic,jz4770-ost
|
||||
- clocks: phandle to the OST clock
|
||||
- clock-names: should be "ost"
|
||||
- interrupts : Specifies the interrupt the OST is connected to.
|
||||
|
||||
|
||||
Example
|
||||
==========================================================
|
||||
|
||||
#include <dt-bindings/clock/jz4770-cgu.h>
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
|
||||
/ {
|
||||
tcu: timer@10002000 {
|
||||
compatible = "ingenic,jz4770-tcu", "simple-mfd";
|
||||
reg = <0x10002000 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x10002000 0x1000>;
|
||||
|
||||
#clock-cells = <1>;
|
||||
|
||||
clocks = <&cgu JZ4770_CLK_RTC
|
||||
&cgu JZ4770_CLK_EXT
|
||||
&cgu JZ4770_CLK_PCLK>;
|
||||
clock-names = "rtc", "ext", "pclk";
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <27 26 25>;
|
||||
|
||||
watchdog: watchdog@0 {
|
||||
compatible = "ingenic,jz4740-watchdog";
|
||||
reg = <0x0 0xc>;
|
||||
|
||||
clocks = <&tcu TCU_CLK_WDT>;
|
||||
clock-names = "wdt";
|
||||
};
|
||||
|
||||
pwm: pwm@40 {
|
||||
compatible = "ingenic,jz4740-pwm";
|
||||
reg = <0x40 0x80>;
|
||||
|
||||
#pwm-cells = <3>;
|
||||
|
||||
clocks = <&tcu TCU_CLK_TIMER0
|
||||
&tcu TCU_CLK_TIMER1
|
||||
&tcu TCU_CLK_TIMER2
|
||||
&tcu TCU_CLK_TIMER3
|
||||
&tcu TCU_CLK_TIMER4
|
||||
&tcu TCU_CLK_TIMER5
|
||||
&tcu TCU_CLK_TIMER6
|
||||
&tcu TCU_CLK_TIMER7>;
|
||||
clock-names = "timer0", "timer1", "timer2", "timer3",
|
||||
"timer4", "timer5", "timer6", "timer7";
|
||||
};
|
||||
|
||||
ost: timer@e0 {
|
||||
compatible = "ingenic,jz4770-ost";
|
||||
reg = <0xe0 0x20>;
|
||||
|
||||
clocks = <&tcu TCU_CLK_OST>;
|
||||
clock-names = "ost";
|
||||
|
||||
interrupts = <15>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
Ingenic Watchdog Timer (WDT) Controller for JZ4740 & JZ4780
|
||||
|
||||
Required properties:
|
||||
compatible: "ingenic,jz4740-watchdog" or "ingenic,jz4780-watchdog"
|
||||
reg: Register address and length for watchdog registers
|
||||
clocks: phandle to the RTC clock
|
||||
clock-names: should be "rtc"
|
||||
|
||||
Example:
|
||||
|
||||
watchdog: jz4740-watchdog@10002000 {
|
||||
compatible = "ingenic,jz4740-watchdog";
|
||||
reg = <0x10002000 0x10>;
|
||||
|
||||
clocks = <&cgu JZ4740_CLK_RTC>;
|
||||
clock-names = "rtc";
|
||||
};
|
|
@ -143,6 +143,7 @@ implementation.
|
|||
arm64/index
|
||||
ia64/index
|
||||
m68k/index
|
||||
mips/index
|
||||
riscv/index
|
||||
s390/index
|
||||
sh/index
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===========================
|
||||
MIPS-specific Documentation
|
||||
===========================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:numbered:
|
||||
|
||||
ingenic-tcu
|
|
@ -0,0 +1,71 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===============================================
|
||||
Ingenic JZ47xx SoCs Timer/Counter Unit hardware
|
||||
===============================================
|
||||
|
||||
The Timer/Counter Unit (TCU) in Ingenic JZ47xx SoCs is a multi-function
|
||||
hardware block. It features up to to eight channels, that can be used as
|
||||
counters, timers, or PWM.
|
||||
|
||||
- JZ4725B, JZ4750, JZ4755 only have six TCU channels. The other SoCs all
|
||||
have eight channels.
|
||||
|
||||
- JZ4725B introduced a separate channel, called Operating System Timer
|
||||
(OST). It is a 32-bit programmable timer. On JZ4760B and above, it is
|
||||
64-bit.
|
||||
|
||||
- Each one of the TCU channels has its own clock, which can be reparented to three
|
||||
different clocks (pclk, ext, rtc), gated, and reclocked, through their TCSR register.
|
||||
|
||||
- The watchdog and OST hardware blocks also feature a TCSR register with the same
|
||||
format in their register space.
|
||||
- The TCU registers used to gate/ungate can also gate/ungate the watchdog and
|
||||
OST clocks.
|
||||
|
||||
- Each TCU channel works in one of two modes:
|
||||
|
||||
- mode TCU1: channels cannot work in sleep mode, but are easier to
|
||||
operate.
|
||||
- mode TCU2: channels can work in sleep mode, but the operation is a bit
|
||||
more complicated than with TCU1 channels.
|
||||
|
||||
- The mode of each TCU channel depends on the SoC used:
|
||||
|
||||
- On the oldest SoCs (up to JZ4740), all of the eight channels operate in
|
||||
TCU1 mode.
|
||||
- On JZ4725B, channel 5 operates as TCU2, the others operate as TCU1.
|
||||
- On newest SoCs (JZ4750 and above), channels 1-2 operate as TCU2, the
|
||||
others operate as TCU1.
|
||||
|
||||
- Each channel can generate an interrupt. Some channels share an interrupt
|
||||
line, some don't, and this changes between SoC versions:
|
||||
|
||||
- on older SoCs (JZ4740 and below), channel 0 and channel 1 have their
|
||||
own interrupt line; channels 2-7 share the last interrupt line.
|
||||
- On JZ4725B, channel 0 has its own interrupt; channels 1-5 share one
|
||||
interrupt line; the OST uses the last interrupt line.
|
||||
- on newer SoCs (JZ4750 and above), channel 5 has its own interrupt;
|
||||
channels 0-4 and (if eight channels) 6-7 all share one interrupt line;
|
||||
the OST uses the last interrupt line.
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
The functionalities of the TCU hardware are spread across multiple drivers:
|
||||
|
||||
=========== =====
|
||||
clocks drivers/clk/ingenic/tcu.c
|
||||
interrupts drivers/irqchip/irq-ingenic-tcu.c
|
||||
timers drivers/clocksource/ingenic-timer.c
|
||||
OST drivers/clocksource/ingenic-ost.c
|
||||
PWM drivers/pwm/pwm-jz4740.c
|
||||
watchdog drivers/watchdog/jz4740_wdt.c
|
||||
=========== =====
|
||||
|
||||
Because various functionalities of the TCU that belong to different drivers
|
||||
and frameworks can be controlled from the same registers, all of these
|
||||
drivers access their registers through the same regmap.
|
||||
|
||||
For more information regarding the devicetree bindings of the TCU drivers,
|
||||
have a look at Documentation/devicetree/bindings/mfd/ingenic,tcu.txt.
|
|
@ -2,6 +2,7 @@
|
|||
/dts-v1/;
|
||||
|
||||
#include "jz4780.dtsi"
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
/ {
|
||||
|
@ -238,3 +239,9 @@ pins_mmc1: mmc1 {
|
|||
bias-disable;
|
||||
};
|
||||
};
|
||||
|
||||
&tcu {
|
||||
/* 3 MHz for the system timer and clocksource */
|
||||
assigned-clocks = <&tcu TCU_CLK_TIMER0>, <&tcu TCU_CLK_TIMER1>;
|
||||
assigned-clock-rates = <3000000>, <3000000>;
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
/dts-v1/;
|
||||
|
||||
#include "jz4770.dtsi"
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
|
||||
/ {
|
||||
compatible = "gcw,zero", "ingenic,jz4770";
|
||||
|
@ -60,3 +61,12 @@ &uhc {
|
|||
/* The WiFi module is connected to the UHC. */
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&tcu {
|
||||
/* 750 kHz for the system timer and clocksource */
|
||||
assigned-clocks = <&tcu TCU_CLK_TIMER0>, <&tcu TCU_CLK_TIMER2>;
|
||||
assigned-clock-rates = <750000>, <750000>;
|
||||
|
||||
/* PWM1 is in use, so reserve channel #2 for the clocksource */
|
||||
ingenic,pwm-channels-mask = <0xfa>;
|
||||
};
|
||||
|
|
|
@ -53,6 +53,28 @@ watchdog: watchdog@10002000 {
|
|||
clock-names = "rtc";
|
||||
};
|
||||
|
||||
tcu: timer@10002000 {
|
||||
compatible = "ingenic,jz4740-tcu", "simple-mfd";
|
||||
reg = <0x10002000 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x10002000 0x1000>;
|
||||
|
||||
#clock-cells = <1>;
|
||||
|
||||
clocks = <&cgu JZ4740_CLK_RTC
|
||||
&cgu JZ4740_CLK_EXT
|
||||
&cgu JZ4740_CLK_PCLK
|
||||
&cgu JZ4740_CLK_TCU>;
|
||||
clock-names = "rtc", "ext", "pclk", "tcu";
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <23 22 21>;
|
||||
};
|
||||
|
||||
rtc_dev: rtc@10003000 {
|
||||
compatible = "ingenic,jz4740-rtc";
|
||||
reg = <0x10003000 0x40>;
|
||||
|
|
|
@ -46,6 +46,27 @@ cgu: jz4770-cgu@10000000 {
|
|||
#clock-cells = <1>;
|
||||
};
|
||||
|
||||
tcu: timer@10002000 {
|
||||
compatible = "ingenic,jz4770-tcu", "simple-mfd";
|
||||
reg = <0x10002000 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x10002000 0x1000>;
|
||||
|
||||
#clock-cells = <1>;
|
||||
|
||||
clocks = <&cgu JZ4770_CLK_RTC
|
||||
&cgu JZ4770_CLK_EXT
|
||||
&cgu JZ4770_CLK_PCLK>;
|
||||
clock-names = "rtc", "ext", "pclk";
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <27 26 25>;
|
||||
};
|
||||
|
||||
pinctrl: pin-controller@10010000 {
|
||||
compatible = "ingenic,jz4770-pinctrl";
|
||||
reg = <0x10010000 0x600>;
|
||||
|
|
|
@ -46,6 +46,29 @@ cgu: jz4780-cgu@10000000 {
|
|||
#clock-cells = <1>;
|
||||
};
|
||||
|
||||
tcu: timer@10002000 {
|
||||
compatible = "ingenic,jz4780-tcu",
|
||||
"ingenic,jz4770-tcu",
|
||||
"simple-mfd";
|
||||
reg = <0x10002000 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0x10002000 0x1000>;
|
||||
|
||||
#clock-cells = <1>;
|
||||
|
||||
clocks = <&cgu JZ4780_CLK_RTCLK
|
||||
&cgu JZ4780_CLK_EXCLK
|
||||
&cgu JZ4780_CLK_PCLK>;
|
||||
clock-names = "rtc", "ext", "pclk";
|
||||
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <27 26 25>;
|
||||
};
|
||||
|
||||
rtc_dev: rtc@10003000 {
|
||||
compatible = "ingenic,jz4780-rtc";
|
||||
reg = <0x10003000 0x4c>;
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
/dts-v1/;
|
||||
|
||||
#include "jz4740.dtsi"
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/iio/adc/ingenic,adc.h>
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
#include <dt-bindings/input/input.h>
|
||||
|
||||
#define KEY_QI_QI KEY_F13
|
||||
|
@ -350,3 +350,9 @@ &mmc {
|
|||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pins_mmc>;
|
||||
};
|
||||
|
||||
&tcu {
|
||||
/* 750 kHz for the system timer and clocksource */
|
||||
assigned-clocks = <&tcu TCU_CLK_TIMER0>, <&tcu TCU_CLK_TIMER1>;
|
||||
assigned-clock-rates = <750000>, <750000>;
|
||||
};
|
||||
|
|
|
@ -4,161 +4,14 @@
|
|||
* JZ4740 platform time support
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/clocksource.h>
|
||||
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <asm/mach-jz4740/irq.h>
|
||||
#include <asm/mach-jz4740/timer.h>
|
||||
#include <asm/time.h>
|
||||
|
||||
#define TIMER_CLOCKEVENT 0
|
||||
#define TIMER_CLOCKSOURCE 1
|
||||
|
||||
static uint16_t jz4740_jiffies_per_tick;
|
||||
|
||||
static u64 jz4740_clocksource_read(struct clocksource *cs)
|
||||
{
|
||||
return jz4740_timer_get_count(TIMER_CLOCKSOURCE);
|
||||
}
|
||||
|
||||
static struct clocksource jz4740_clocksource = {
|
||||
.name = "jz4740-timer",
|
||||
.rating = 200,
|
||||
.read = jz4740_clocksource_read,
|
||||
.mask = CLOCKSOURCE_MASK(16),
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
};
|
||||
|
||||
static u64 notrace jz4740_read_sched_clock(void)
|
||||
{
|
||||
return jz4740_timer_get_count(TIMER_CLOCKSOURCE);
|
||||
}
|
||||
|
||||
static irqreturn_t jz4740_clockevent_irq(int irq, void *devid)
|
||||
{
|
||||
struct clock_event_device *cd = devid;
|
||||
|
||||
jz4740_timer_ack_full(TIMER_CLOCKEVENT);
|
||||
|
||||
if (!clockevent_state_periodic(cd))
|
||||
jz4740_timer_disable(TIMER_CLOCKEVENT);
|
||||
|
||||
cd->event_handler(cd);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int jz4740_clockevent_set_periodic(struct clock_event_device *evt)
|
||||
{
|
||||
jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
|
||||
jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
|
||||
jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
|
||||
jz4740_timer_enable(TIMER_CLOCKEVENT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jz4740_clockevent_resume(struct clock_event_device *evt)
|
||||
{
|
||||
jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
|
||||
jz4740_timer_enable(TIMER_CLOCKEVENT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jz4740_clockevent_shutdown(struct clock_event_device *evt)
|
||||
{
|
||||
jz4740_timer_disable(TIMER_CLOCKEVENT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jz4740_clockevent_set_next(unsigned long evt,
|
||||
struct clock_event_device *cd)
|
||||
{
|
||||
jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
|
||||
jz4740_timer_set_period(TIMER_CLOCKEVENT, evt);
|
||||
jz4740_timer_enable(TIMER_CLOCKEVENT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct clock_event_device jz4740_clockevent = {
|
||||
.name = "jz4740-timer",
|
||||
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
||||
.set_next_event = jz4740_clockevent_set_next,
|
||||
.set_state_shutdown = jz4740_clockevent_shutdown,
|
||||
.set_state_periodic = jz4740_clockevent_set_periodic,
|
||||
.set_state_oneshot = jz4740_clockevent_shutdown,
|
||||
.tick_resume = jz4740_clockevent_resume,
|
||||
.rating = 200,
|
||||
#ifdef CONFIG_MACH_JZ4740
|
||||
.irq = JZ4740_IRQ_TCU0,
|
||||
#endif
|
||||
#if defined(CONFIG_MACH_JZ4770) || defined(CONFIG_MACH_JZ4780)
|
||||
.irq = JZ4780_IRQ_TCU2,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct irqaction timer_irqaction = {
|
||||
.handler = jz4740_clockevent_irq,
|
||||
.flags = IRQF_PERCPU | IRQF_TIMER,
|
||||
.name = "jz4740-timerirq",
|
||||
.dev_id = &jz4740_clockevent,
|
||||
};
|
||||
|
||||
void __init plat_time_init(void)
|
||||
{
|
||||
int ret;
|
||||
uint32_t clk_rate;
|
||||
uint16_t ctrl;
|
||||
struct clk *ext_clk;
|
||||
|
||||
of_clk_init(NULL);
|
||||
jz4740_timer_init();
|
||||
|
||||
ext_clk = clk_get(NULL, "ext");
|
||||
if (IS_ERR(ext_clk))
|
||||
panic("unable to get ext clock");
|
||||
clk_rate = clk_get_rate(ext_clk) >> 4;
|
||||
clk_put(ext_clk);
|
||||
|
||||
jz4740_jiffies_per_tick = DIV_ROUND_CLOSEST(clk_rate, HZ);
|
||||
|
||||
clockevent_set_clock(&jz4740_clockevent, clk_rate);
|
||||
jz4740_clockevent.min_delta_ns = clockevent_delta2ns(100, &jz4740_clockevent);
|
||||
jz4740_clockevent.min_delta_ticks = 100;
|
||||
jz4740_clockevent.max_delta_ns = clockevent_delta2ns(0xffff, &jz4740_clockevent);
|
||||
jz4740_clockevent.max_delta_ticks = 0xffff;
|
||||
jz4740_clockevent.cpumask = cpumask_of(0);
|
||||
|
||||
clockevents_register_device(&jz4740_clockevent);
|
||||
|
||||
ret = clocksource_register_hz(&jz4740_clocksource, clk_rate);
|
||||
|
||||
if (ret)
|
||||
printk(KERN_ERR "Failed to register clocksource: %d\n", ret);
|
||||
|
||||
sched_clock_register(jz4740_read_sched_clock, 16, clk_rate);
|
||||
|
||||
setup_irq(jz4740_clockevent.irq, &timer_irqaction);
|
||||
|
||||
ctrl = JZ_TIMER_CTRL_PRESCALE_16 | JZ_TIMER_CTRL_SRC_EXT;
|
||||
|
||||
jz4740_timer_set_ctrl(TIMER_CLOCKEVENT, ctrl);
|
||||
jz4740_timer_set_ctrl(TIMER_CLOCKSOURCE, ctrl);
|
||||
|
||||
jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
|
||||
jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
|
||||
|
||||
jz4740_timer_set_period(TIMER_CLOCKSOURCE, 0xffff);
|
||||
|
||||
jz4740_timer_enable(TIMER_CLOCKEVENT);
|
||||
jz4740_timer_enable(TIMER_CLOCKSOURCE);
|
||||
timer_probe();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
menu "Ingenic JZ47xx CGU drivers"
|
||||
menu "Ingenic SoCs drivers"
|
||||
depends on MIPS
|
||||
|
||||
config INGENIC_CGU_COMMON
|
||||
|
@ -45,4 +45,12 @@ config INGENIC_CGU_JZ4780
|
|||
|
||||
If building for a JZ4780 SoC, you want to say Y here.
|
||||
|
||||
config INGENIC_TCU_CLK
|
||||
bool "Ingenic JZ47xx TCU clocks driver"
|
||||
default MACH_INGENIC
|
||||
select MFD_SYSCON
|
||||
help
|
||||
Support the clocks of the Timer/Counter Unit (TCU) of the Ingenic
|
||||
JZ47xx SoCs.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -4,3 +4,4 @@ obj-$(CONFIG_INGENIC_CGU_JZ4740) += jz4740-cgu.o
|
|||
obj-$(CONFIG_INGENIC_CGU_JZ4725B) += jz4725b-cgu.o
|
||||
obj-$(CONFIG_INGENIC_CGU_JZ4770) += jz4770-cgu.o
|
||||
obj-$(CONFIG_INGENIC_CGU_JZ4780) += jz4780-cgu.o
|
||||
obj-$(CONFIG_INGENIC_TCU_CLK) += tcu.o
|
||||
|
|
|
@ -222,6 +222,12 @@ static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = {
|
|||
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
||||
.gate = { CGU_REG_CLKGR, 5 },
|
||||
},
|
||||
|
||||
[JZ4740_CLK_TCU] = {
|
||||
"tcu", CGU_CLK_GATE,
|
||||
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
||||
.gate = { CGU_REG_CLKGR, 1 },
|
||||
},
|
||||
};
|
||||
|
||||
static void __init jz4740_cgu_init(struct device_node *np)
|
||||
|
|
|
@ -0,0 +1,474 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* JZ47xx SoCs TCU clocks driver
|
||||
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/mfd/ingenic-tcu.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/syscore_ops.h>
|
||||
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
|
||||
/* 8 channels max + watchdog + OST */
|
||||
#define TCU_CLK_COUNT 10
|
||||
|
||||
#undef pr_fmt
|
||||
#define pr_fmt(fmt) "ingenic-tcu-clk: " fmt
|
||||
|
||||
enum tcu_clk_parent {
|
||||
TCU_PARENT_PCLK,
|
||||
TCU_PARENT_RTC,
|
||||
TCU_PARENT_EXT,
|
||||
};
|
||||
|
||||
struct ingenic_soc_info {
|
||||
unsigned int num_channels;
|
||||
bool has_ost;
|
||||
bool has_tcu_clk;
|
||||
};
|
||||
|
||||
struct ingenic_tcu_clk_info {
|
||||
struct clk_init_data init_data;
|
||||
u8 gate_bit;
|
||||
u8 tcsr_reg;
|
||||
};
|
||||
|
||||
struct ingenic_tcu_clk {
|
||||
struct clk_hw hw;
|
||||
unsigned int idx;
|
||||
struct ingenic_tcu *tcu;
|
||||
const struct ingenic_tcu_clk_info *info;
|
||||
};
|
||||
|
||||
struct ingenic_tcu {
|
||||
const struct ingenic_soc_info *soc_info;
|
||||
struct regmap *map;
|
||||
struct clk *clk;
|
||||
|
||||
struct clk_hw_onecell_data *clocks;
|
||||
};
|
||||
|
||||
static struct ingenic_tcu *ingenic_tcu;
|
||||
|
||||
static inline struct ingenic_tcu_clk *to_tcu_clk(struct clk_hw *hw)
|
||||
{
|
||||
return container_of(hw, struct ingenic_tcu_clk, hw);
|
||||
}
|
||||
|
||||
static int ingenic_tcu_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
struct ingenic_tcu *tcu = tcu_clk->tcu;
|
||||
|
||||
regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ingenic_tcu_disable(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
struct ingenic_tcu *tcu = tcu_clk->tcu;
|
||||
|
||||
regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
|
||||
}
|
||||
|
||||
static int ingenic_tcu_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
unsigned int value;
|
||||
|
||||
regmap_read(tcu_clk->tcu->map, TCU_REG_TSR, &value);
|
||||
|
||||
return !(value & BIT(info->gate_bit));
|
||||
}
|
||||
|
||||
static bool ingenic_tcu_enable_regs(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
struct ingenic_tcu *tcu = tcu_clk->tcu;
|
||||
bool enabled = false;
|
||||
|
||||
/*
|
||||
* If the SoC has no global TCU clock, we must ungate the channel's
|
||||
* clock to be able to access its registers.
|
||||
* If we have a TCU clock, it will be enabled automatically as it has
|
||||
* been attached to the regmap.
|
||||
*/
|
||||
if (!tcu->clk) {
|
||||
enabled = !!ingenic_tcu_is_enabled(hw);
|
||||
regmap_write(tcu->map, TCU_REG_TSCR, BIT(info->gate_bit));
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
static void ingenic_tcu_disable_regs(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
struct ingenic_tcu *tcu = tcu_clk->tcu;
|
||||
|
||||
if (!tcu->clk)
|
||||
regmap_write(tcu->map, TCU_REG_TSSR, BIT(info->gate_bit));
|
||||
}
|
||||
|
||||
static u8 ingenic_tcu_get_parent(struct clk_hw *hw)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
unsigned int val = 0;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(tcu_clk->tcu->map, info->tcsr_reg, &val);
|
||||
WARN_ONCE(ret < 0, "Unable to read TCSR %d", tcu_clk->idx);
|
||||
|
||||
return ffs(val & TCU_TCSR_PARENT_CLOCK_MASK) - 1;
|
||||
}
|
||||
|
||||
static int ingenic_tcu_set_parent(struct clk_hw *hw, u8 idx)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
bool was_enabled;
|
||||
int ret;
|
||||
|
||||
was_enabled = ingenic_tcu_enable_regs(hw);
|
||||
|
||||
ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
|
||||
TCU_TCSR_PARENT_CLOCK_MASK, BIT(idx));
|
||||
WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx);
|
||||
|
||||
if (!was_enabled)
|
||||
ingenic_tcu_disable_regs(hw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long ingenic_tcu_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
unsigned int prescale;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(tcu_clk->tcu->map, info->tcsr_reg, &prescale);
|
||||
WARN_ONCE(ret < 0, "Unable to read TCSR %d", tcu_clk->idx);
|
||||
|
||||
prescale = (prescale & TCU_TCSR_PRESCALE_MASK) >> TCU_TCSR_PRESCALE_LSB;
|
||||
|
||||
return parent_rate >> (prescale * 2);
|
||||
}
|
||||
|
||||
static u8 ingenic_tcu_get_prescale(unsigned long rate, unsigned long req_rate)
|
||||
{
|
||||
u8 prescale;
|
||||
|
||||
for (prescale = 0; prescale < 5; prescale++)
|
||||
if ((rate >> (prescale * 2)) <= req_rate)
|
||||
return prescale;
|
||||
|
||||
return 5; /* /1024 divider */
|
||||
}
|
||||
|
||||
static long ingenic_tcu_round_rate(struct clk_hw *hw, unsigned long req_rate,
|
||||
unsigned long *parent_rate)
|
||||
{
|
||||
unsigned long rate = *parent_rate;
|
||||
u8 prescale;
|
||||
|
||||
if (req_rate > rate)
|
||||
return -EINVAL;
|
||||
|
||||
prescale = ingenic_tcu_get_prescale(rate, req_rate);
|
||||
|
||||
return rate >> (prescale * 2);
|
||||
}
|
||||
|
||||
static int ingenic_tcu_set_rate(struct clk_hw *hw, unsigned long req_rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk = to_tcu_clk(hw);
|
||||
const struct ingenic_tcu_clk_info *info = tcu_clk->info;
|
||||
u8 prescale = ingenic_tcu_get_prescale(parent_rate, req_rate);
|
||||
bool was_enabled;
|
||||
int ret;
|
||||
|
||||
was_enabled = ingenic_tcu_enable_regs(hw);
|
||||
|
||||
ret = regmap_update_bits(tcu_clk->tcu->map, info->tcsr_reg,
|
||||
TCU_TCSR_PRESCALE_MASK,
|
||||
prescale << TCU_TCSR_PRESCALE_LSB);
|
||||
WARN_ONCE(ret < 0, "Unable to update TCSR %d", tcu_clk->idx);
|
||||
|
||||
if (!was_enabled)
|
||||
ingenic_tcu_disable_regs(hw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops ingenic_tcu_clk_ops = {
|
||||
.get_parent = ingenic_tcu_get_parent,
|
||||
.set_parent = ingenic_tcu_set_parent,
|
||||
|
||||
.recalc_rate = ingenic_tcu_recalc_rate,
|
||||
.round_rate = ingenic_tcu_round_rate,
|
||||
.set_rate = ingenic_tcu_set_rate,
|
||||
|
||||
.enable = ingenic_tcu_enable,
|
||||
.disable = ingenic_tcu_disable,
|
||||
.is_enabled = ingenic_tcu_is_enabled,
|
||||
};
|
||||
|
||||
static const char * const ingenic_tcu_timer_parents[] = {
|
||||
[TCU_PARENT_PCLK] = "pclk",
|
||||
[TCU_PARENT_RTC] = "rtc",
|
||||
[TCU_PARENT_EXT] = "ext",
|
||||
};
|
||||
|
||||
#define DEF_TIMER(_name, _gate_bit, _tcsr) \
|
||||
{ \
|
||||
.init_data = { \
|
||||
.name = _name, \
|
||||
.parent_names = ingenic_tcu_timer_parents, \
|
||||
.num_parents = ARRAY_SIZE(ingenic_tcu_timer_parents),\
|
||||
.ops = &ingenic_tcu_clk_ops, \
|
||||
.flags = CLK_SET_RATE_UNGATE, \
|
||||
}, \
|
||||
.gate_bit = _gate_bit, \
|
||||
.tcsr_reg = _tcsr, \
|
||||
}
|
||||
static const struct ingenic_tcu_clk_info ingenic_tcu_clk_info[] = {
|
||||
[TCU_CLK_TIMER0] = DEF_TIMER("timer0", 0, TCU_REG_TCSRc(0)),
|
||||
[TCU_CLK_TIMER1] = DEF_TIMER("timer1", 1, TCU_REG_TCSRc(1)),
|
||||
[TCU_CLK_TIMER2] = DEF_TIMER("timer2", 2, TCU_REG_TCSRc(2)),
|
||||
[TCU_CLK_TIMER3] = DEF_TIMER("timer3", 3, TCU_REG_TCSRc(3)),
|
||||
[TCU_CLK_TIMER4] = DEF_TIMER("timer4", 4, TCU_REG_TCSRc(4)),
|
||||
[TCU_CLK_TIMER5] = DEF_TIMER("timer5", 5, TCU_REG_TCSRc(5)),
|
||||
[TCU_CLK_TIMER6] = DEF_TIMER("timer6", 6, TCU_REG_TCSRc(6)),
|
||||
[TCU_CLK_TIMER7] = DEF_TIMER("timer7", 7, TCU_REG_TCSRc(7)),
|
||||
};
|
||||
|
||||
static const struct ingenic_tcu_clk_info ingenic_tcu_watchdog_clk_info =
|
||||
DEF_TIMER("wdt", 16, TCU_REG_WDT_TCSR);
|
||||
static const struct ingenic_tcu_clk_info ingenic_tcu_ost_clk_info =
|
||||
DEF_TIMER("ost", 15, TCU_REG_OST_TCSR);
|
||||
#undef DEF_TIMER
|
||||
|
||||
static int __init ingenic_tcu_register_clock(struct ingenic_tcu *tcu,
|
||||
unsigned int idx, enum tcu_clk_parent parent,
|
||||
const struct ingenic_tcu_clk_info *info,
|
||||
struct clk_hw_onecell_data *clocks)
|
||||
{
|
||||
struct ingenic_tcu_clk *tcu_clk;
|
||||
int err;
|
||||
|
||||
tcu_clk = kzalloc(sizeof(*tcu_clk), GFP_KERNEL);
|
||||
if (!tcu_clk)
|
||||
return -ENOMEM;
|
||||
|
||||
tcu_clk->hw.init = &info->init_data;
|
||||
tcu_clk->idx = idx;
|
||||
tcu_clk->info = info;
|
||||
tcu_clk->tcu = tcu;
|
||||
|
||||
/* Reset channel and clock divider, set default parent */
|
||||
ingenic_tcu_enable_regs(&tcu_clk->hw);
|
||||
regmap_update_bits(tcu->map, info->tcsr_reg, 0xffff, BIT(parent));
|
||||
ingenic_tcu_disable_regs(&tcu_clk->hw);
|
||||
|
||||
err = clk_hw_register(NULL, &tcu_clk->hw);
|
||||
if (err) {
|
||||
kfree(tcu_clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
clocks->hws[idx] = &tcu_clk->hw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ingenic_soc_info jz4740_soc_info = {
|
||||
.num_channels = 8,
|
||||
.has_ost = false,
|
||||
.has_tcu_clk = true,
|
||||
};
|
||||
|
||||
static const struct ingenic_soc_info jz4725b_soc_info = {
|
||||
.num_channels = 6,
|
||||
.has_ost = true,
|
||||
.has_tcu_clk = true,
|
||||
};
|
||||
|
||||
static const struct ingenic_soc_info jz4770_soc_info = {
|
||||
.num_channels = 8,
|
||||
.has_ost = true,
|
||||
.has_tcu_clk = false,
|
||||
};
|
||||
|
||||
static const struct of_device_id ingenic_tcu_of_match[] __initconst = {
|
||||
{ .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
|
||||
{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
|
||||
{ .compatible = "ingenic,jz4770-tcu", .data = &jz4770_soc_info, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static int __init ingenic_tcu_probe(struct device_node *np)
|
||||
{
|
||||
const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
|
||||
struct ingenic_tcu *tcu;
|
||||
struct regmap *map;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
map = device_node_to_regmap(np);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
|
||||
if (!tcu)
|
||||
return -ENOMEM;
|
||||
|
||||
tcu->map = map;
|
||||
tcu->soc_info = id->data;
|
||||
|
||||
if (tcu->soc_info->has_tcu_clk) {
|
||||
tcu->clk = of_clk_get_by_name(np, "tcu");
|
||||
if (IS_ERR(tcu->clk)) {
|
||||
ret = PTR_ERR(tcu->clk);
|
||||
pr_crit("Cannot get TCU clock\n");
|
||||
goto err_free_tcu;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(tcu->clk);
|
||||
if (ret) {
|
||||
pr_crit("Unable to enable TCU clock\n");
|
||||
goto err_put_clk;
|
||||
}
|
||||
}
|
||||
|
||||
tcu->clocks = kzalloc(sizeof(*tcu->clocks) +
|
||||
sizeof(*tcu->clocks->hws) * TCU_CLK_COUNT,
|
||||
GFP_KERNEL);
|
||||
if (!tcu->clocks) {
|
||||
ret = -ENOMEM;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
tcu->clocks->num = TCU_CLK_COUNT;
|
||||
|
||||
for (i = 0; i < tcu->soc_info->num_channels; i++) {
|
||||
ret = ingenic_tcu_register_clock(tcu, i, TCU_PARENT_EXT,
|
||||
&ingenic_tcu_clk_info[i],
|
||||
tcu->clocks);
|
||||
if (ret) {
|
||||
pr_crit("cannot register clock %d\n", i);
|
||||
goto err_unregister_timer_clocks;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We set EXT as the default parent clock for all the TCU clocks
|
||||
* except for the watchdog one, where we set the RTC clock as the
|
||||
* parent. Since the EXT and PCLK are much faster than the RTC clock,
|
||||
* the watchdog would kick after a maximum time of 5s, and we might
|
||||
* want a slower kicking time.
|
||||
*/
|
||||
ret = ingenic_tcu_register_clock(tcu, TCU_CLK_WDT, TCU_PARENT_RTC,
|
||||
&ingenic_tcu_watchdog_clk_info,
|
||||
tcu->clocks);
|
||||
if (ret) {
|
||||
pr_crit("cannot register watchdog clock\n");
|
||||
goto err_unregister_timer_clocks;
|
||||
}
|
||||
|
||||
if (tcu->soc_info->has_ost) {
|
||||
ret = ingenic_tcu_register_clock(tcu, TCU_CLK_OST,
|
||||
TCU_PARENT_EXT,
|
||||
&ingenic_tcu_ost_clk_info,
|
||||
tcu->clocks);
|
||||
if (ret) {
|
||||
pr_crit("cannot register ost clock\n");
|
||||
goto err_unregister_watchdog_clock;
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, tcu->clocks);
|
||||
if (ret) {
|
||||
pr_crit("cannot add OF clock provider\n");
|
||||
goto err_unregister_ost_clock;
|
||||
}
|
||||
|
||||
ingenic_tcu = tcu;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_ost_clock:
|
||||
if (tcu->soc_info->has_ost)
|
||||
clk_hw_unregister(tcu->clocks->hws[i + 1]);
|
||||
err_unregister_watchdog_clock:
|
||||
clk_hw_unregister(tcu->clocks->hws[i]);
|
||||
err_unregister_timer_clocks:
|
||||
for (i = 0; i < tcu->clocks->num; i++)
|
||||
if (tcu->clocks->hws[i])
|
||||
clk_hw_unregister(tcu->clocks->hws[i]);
|
||||
kfree(tcu->clocks);
|
||||
err_clk_disable:
|
||||
if (tcu->soc_info->has_tcu_clk)
|
||||
clk_disable_unprepare(tcu->clk);
|
||||
err_put_clk:
|
||||
if (tcu->soc_info->has_tcu_clk)
|
||||
clk_put(tcu->clk);
|
||||
err_free_tcu:
|
||||
kfree(tcu);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused tcu_pm_suspend(void)
|
||||
{
|
||||
struct ingenic_tcu *tcu = ingenic_tcu;
|
||||
|
||||
if (tcu->clk)
|
||||
clk_disable(tcu->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __maybe_unused tcu_pm_resume(void)
|
||||
{
|
||||
struct ingenic_tcu *tcu = ingenic_tcu;
|
||||
|
||||
if (tcu->clk)
|
||||
clk_enable(tcu->clk);
|
||||
}
|
||||
|
||||
static struct syscore_ops __maybe_unused tcu_pm_ops = {
|
||||
.suspend = tcu_pm_suspend,
|
||||
.resume = tcu_pm_resume,
|
||||
};
|
||||
|
||||
static void __init ingenic_tcu_init(struct device_node *np)
|
||||
{
|
||||
int ret = ingenic_tcu_probe(np);
|
||||
|
||||
if (ret)
|
||||
pr_crit("Failed to initialize TCU clocks: %d\n", ret);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PM_SLEEP))
|
||||
register_syscore_ops(&tcu_pm_ops);
|
||||
}
|
||||
|
||||
CLK_OF_DECLARE_DRIVER(jz4740_cgu, "ingenic,jz4740-tcu", ingenic_tcu_init);
|
||||
CLK_OF_DECLARE_DRIVER(jz4725b_cgu, "ingenic,jz4725b-tcu", ingenic_tcu_init);
|
||||
CLK_OF_DECLARE_DRIVER(jz4770_cgu, "ingenic,jz4770-tcu", ingenic_tcu_init);
|
|
@ -685,4 +685,15 @@ config MILBEAUT_TIMER
|
|||
help
|
||||
Enables the support for Milbeaut timer driver.
|
||||
|
||||
config INGENIC_TIMER
|
||||
bool "Clocksource/timer using the TCU in Ingenic JZ SoCs"
|
||||
default MACH_INGENIC
|
||||
depends on MIPS || COMPILE_TEST
|
||||
depends on COMMON_CLK
|
||||
select MFD_SYSCON
|
||||
select TIMER_OF
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
Support for the timer/counter unit of the Ingenic JZ SoCs.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -80,6 +80,7 @@ obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o
|
|||
obj-$(CONFIG_H8300_TMR8) += h8300_timer8.o
|
||||
obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o
|
||||
obj-$(CONFIG_H8300_TPU) += h8300_tpu.o
|
||||
obj-$(CONFIG_INGENIC_TIMER) += ingenic-timer.o
|
||||
obj-$(CONFIG_CLKSRC_ST_LPC) += clksrc_st_lpc.o
|
||||
obj-$(CONFIG_X86_NUMACHIP) += numachip.o
|
||||
obj-$(CONFIG_ATCPIT100_TIMER) += timer-atcpit100.o
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* JZ47xx SoCs TCU IRQ driver
|
||||
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/ingenic-tcu.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||
|
||||
struct ingenic_soc_info {
|
||||
unsigned int num_channels;
|
||||
};
|
||||
|
||||
struct ingenic_tcu {
|
||||
struct regmap *map;
|
||||
struct clk *timer_clk, *cs_clk;
|
||||
unsigned int timer_channel, cs_channel;
|
||||
struct clock_event_device cevt;
|
||||
struct clocksource cs;
|
||||
char name[4];
|
||||
unsigned long pwm_channels_mask;
|
||||
};
|
||||
|
||||
static struct ingenic_tcu *ingenic_tcu;
|
||||
|
||||
static u64 notrace ingenic_tcu_timer_read(void)
|
||||
{
|
||||
struct ingenic_tcu *tcu = ingenic_tcu;
|
||||
unsigned int count;
|
||||
|
||||
regmap_read(tcu->map, TCU_REG_TCNTc(tcu->cs_channel), &count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static u64 notrace ingenic_tcu_timer_cs_read(struct clocksource *cs)
|
||||
{
|
||||
return ingenic_tcu_timer_read();
|
||||
}
|
||||
|
||||
static inline struct ingenic_tcu *to_ingenic_tcu(struct clock_event_device *evt)
|
||||
{
|
||||
return container_of(evt, struct ingenic_tcu, cevt);
|
||||
}
|
||||
|
||||
static int ingenic_tcu_cevt_set_state_shutdown(struct clock_event_device *evt)
|
||||
{
|
||||
struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
|
||||
|
||||
regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ingenic_tcu_cevt_set_next(unsigned long next,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
|
||||
|
||||
if (next > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
regmap_write(tcu->map, TCU_REG_TDFRc(tcu->timer_channel), next);
|
||||
regmap_write(tcu->map, TCU_REG_TCNTc(tcu->timer_channel), 0);
|
||||
regmap_write(tcu->map, TCU_REG_TESR, BIT(tcu->timer_channel));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t ingenic_tcu_cevt_cb(int irq, void *dev_id)
|
||||
{
|
||||
struct clock_event_device *evt = dev_id;
|
||||
struct ingenic_tcu *tcu = to_ingenic_tcu(evt);
|
||||
|
||||
regmap_write(tcu->map, TCU_REG_TECR, BIT(tcu->timer_channel));
|
||||
|
||||
if (evt->event_handler)
|
||||
evt->event_handler(evt);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct clk * __init ingenic_tcu_get_clock(struct device_node *np, int id)
|
||||
{
|
||||
struct of_phandle_args args;
|
||||
|
||||
args.np = np;
|
||||
args.args_count = 1;
|
||||
args.args[0] = id;
|
||||
|
||||
return of_clk_get_from_provider(&args);
|
||||
}
|
||||
|
||||
static int __init ingenic_tcu_timer_init(struct device_node *np,
|
||||
struct ingenic_tcu *tcu)
|
||||
{
|
||||
unsigned int timer_virq, channel = tcu->timer_channel;
|
||||
struct irq_domain *domain;
|
||||
unsigned long rate;
|
||||
int err;
|
||||
|
||||
tcu->timer_clk = ingenic_tcu_get_clock(np, channel);
|
||||
if (IS_ERR(tcu->timer_clk))
|
||||
return PTR_ERR(tcu->timer_clk);
|
||||
|
||||
err = clk_prepare_enable(tcu->timer_clk);
|
||||
if (err)
|
||||
goto err_clk_put;
|
||||
|
||||
rate = clk_get_rate(tcu->timer_clk);
|
||||
if (!rate) {
|
||||
err = -EINVAL;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
domain = irq_find_host(np);
|
||||
if (!domain) {
|
||||
err = -ENODEV;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
timer_virq = irq_create_mapping(domain, channel);
|
||||
if (!timer_virq) {
|
||||
err = -EINVAL;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
snprintf(tcu->name, sizeof(tcu->name), "TCU");
|
||||
|
||||
err = request_irq(timer_virq, ingenic_tcu_cevt_cb, IRQF_TIMER,
|
||||
tcu->name, &tcu->cevt);
|
||||
if (err)
|
||||
goto err_irq_dispose_mapping;
|
||||
|
||||
tcu->cevt.cpumask = cpumask_of(smp_processor_id());
|
||||
tcu->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
|
||||
tcu->cevt.name = tcu->name;
|
||||
tcu->cevt.rating = 200;
|
||||
tcu->cevt.set_state_shutdown = ingenic_tcu_cevt_set_state_shutdown;
|
||||
tcu->cevt.set_next_event = ingenic_tcu_cevt_set_next;
|
||||
|
||||
clockevents_config_and_register(&tcu->cevt, rate, 10, 0xffff);
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq_dispose_mapping:
|
||||
irq_dispose_mapping(timer_virq);
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(tcu->timer_clk);
|
||||
err_clk_put:
|
||||
clk_put(tcu->timer_clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init ingenic_tcu_clocksource_init(struct device_node *np,
|
||||
struct ingenic_tcu *tcu)
|
||||
{
|
||||
unsigned int channel = tcu->cs_channel;
|
||||
struct clocksource *cs = &tcu->cs;
|
||||
unsigned long rate;
|
||||
int err;
|
||||
|
||||
tcu->cs_clk = ingenic_tcu_get_clock(np, channel);
|
||||
if (IS_ERR(tcu->cs_clk))
|
||||
return PTR_ERR(tcu->cs_clk);
|
||||
|
||||
err = clk_prepare_enable(tcu->cs_clk);
|
||||
if (err)
|
||||
goto err_clk_put;
|
||||
|
||||
rate = clk_get_rate(tcu->cs_clk);
|
||||
if (!rate) {
|
||||
err = -EINVAL;
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
/* Reset channel */
|
||||
regmap_update_bits(tcu->map, TCU_REG_TCSRc(channel),
|
||||
0xffff & ~TCU_TCSR_RESERVED_BITS, 0);
|
||||
|
||||
/* Reset counter */
|
||||
regmap_write(tcu->map, TCU_REG_TDFRc(channel), 0xffff);
|
||||
regmap_write(tcu->map, TCU_REG_TCNTc(channel), 0);
|
||||
|
||||
/* Enable channel */
|
||||
regmap_write(tcu->map, TCU_REG_TESR, BIT(channel));
|
||||
|
||||
cs->name = "ingenic-timer";
|
||||
cs->rating = 200;
|
||||
cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
cs->mask = CLOCKSOURCE_MASK(16);
|
||||
cs->read = ingenic_tcu_timer_cs_read;
|
||||
|
||||
err = clocksource_register_hz(cs, rate);
|
||||
if (err)
|
||||
goto err_clk_disable;
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(tcu->cs_clk);
|
||||
err_clk_put:
|
||||
clk_put(tcu->cs_clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct ingenic_soc_info jz4740_soc_info = {
|
||||
.num_channels = 8,
|
||||
};
|
||||
|
||||
static const struct ingenic_soc_info jz4725b_soc_info = {
|
||||
.num_channels = 6,
|
||||
};
|
||||
|
||||
static const struct of_device_id ingenic_tcu_of_match[] = {
|
||||
{ .compatible = "ingenic,jz4740-tcu", .data = &jz4740_soc_info, },
|
||||
{ .compatible = "ingenic,jz4725b-tcu", .data = &jz4725b_soc_info, },
|
||||
{ .compatible = "ingenic,jz4770-tcu", .data = &jz4740_soc_info, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static int __init ingenic_tcu_init(struct device_node *np)
|
||||
{
|
||||
const struct of_device_id *id = of_match_node(ingenic_tcu_of_match, np);
|
||||
const struct ingenic_soc_info *soc_info = id->data;
|
||||
struct ingenic_tcu *tcu;
|
||||
struct regmap *map;
|
||||
long rate;
|
||||
int ret;
|
||||
|
||||
of_node_clear_flag(np, OF_POPULATED);
|
||||
|
||||
map = device_node_to_regmap(np);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
|
||||
if (!tcu)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Enable all TCU channels for PWM use by default except channels 0/1 */
|
||||
tcu->pwm_channels_mask = GENMASK(soc_info->num_channels - 1, 2);
|
||||
of_property_read_u32(np, "ingenic,pwm-channels-mask",
|
||||
(u32 *)&tcu->pwm_channels_mask);
|
||||
|
||||
/* Verify that we have at least two free channels */
|
||||
if (hweight8(tcu->pwm_channels_mask) > soc_info->num_channels - 2) {
|
||||
pr_crit("%s: Invalid PWM channel mask: 0x%02lx\n", __func__,
|
||||
tcu->pwm_channels_mask);
|
||||
ret = -EINVAL;
|
||||
goto err_free_ingenic_tcu;
|
||||
}
|
||||
|
||||
tcu->map = map;
|
||||
ingenic_tcu = tcu;
|
||||
|
||||
tcu->timer_channel = find_first_zero_bit(&tcu->pwm_channels_mask,
|
||||
soc_info->num_channels);
|
||||
tcu->cs_channel = find_next_zero_bit(&tcu->pwm_channels_mask,
|
||||
soc_info->num_channels,
|
||||
tcu->timer_channel + 1);
|
||||
|
||||
ret = ingenic_tcu_clocksource_init(np, tcu);
|
||||
if (ret) {
|
||||
pr_crit("%s: Unable to init clocksource: %d\n", __func__, ret);
|
||||
goto err_free_ingenic_tcu;
|
||||
}
|
||||
|
||||
ret = ingenic_tcu_timer_init(np, tcu);
|
||||
if (ret)
|
||||
goto err_tcu_clocksource_cleanup;
|
||||
|
||||
/* Register the sched_clock at the end as there's no way to undo it */
|
||||
rate = clk_get_rate(tcu->cs_clk);
|
||||
sched_clock_register(ingenic_tcu_timer_read, 16, rate);
|
||||
|
||||
return 0;
|
||||
|
||||
err_tcu_clocksource_cleanup:
|
||||
clocksource_unregister(&tcu->cs);
|
||||
clk_disable_unprepare(tcu->cs_clk);
|
||||
clk_put(tcu->cs_clk);
|
||||
err_free_ingenic_tcu:
|
||||
kfree(tcu);
|
||||
return ret;
|
||||
}
|
||||
|
||||
TIMER_OF_DECLARE(jz4740_tcu_intc, "ingenic,jz4740-tcu", ingenic_tcu_init);
|
||||
TIMER_OF_DECLARE(jz4725b_tcu_intc, "ingenic,jz4725b-tcu", ingenic_tcu_init);
|
||||
TIMER_OF_DECLARE(jz4770_tcu_intc, "ingenic,jz4770-tcu", ingenic_tcu_init);
|
||||
|
||||
|
||||
static int __init ingenic_tcu_probe(struct platform_device *pdev)
|
||||
{
|
||||
platform_set_drvdata(pdev, ingenic_tcu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ingenic_tcu_suspend(struct device *dev)
|
||||
{
|
||||
struct ingenic_tcu *tcu = dev_get_drvdata(dev);
|
||||
|
||||
clk_disable(tcu->cs_clk);
|
||||
clk_disable(tcu->timer_clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ingenic_tcu_resume(struct device *dev)
|
||||
{
|
||||
struct ingenic_tcu *tcu = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(tcu->timer_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_enable(tcu->cs_clk);
|
||||
if (ret) {
|
||||
clk_disable(tcu->timer_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops __maybe_unused ingenic_tcu_pm_ops = {
|
||||
/* _noirq: We want the TCU clocks to be gated last / ungated first */
|
||||
.suspend_noirq = ingenic_tcu_suspend,
|
||||
.resume_noirq = ingenic_tcu_resume,
|
||||
};
|
||||
|
||||
static struct platform_driver ingenic_tcu_driver = {
|
||||
.driver = {
|
||||
.name = "ingenic-tcu-timer",
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
.pm = &ingenic_tcu_pm_ops,
|
||||
#endif
|
||||
.of_match_table = ingenic_tcu_of_match,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver_probe(ingenic_tcu_driver, ingenic_tcu_probe);
|
|
@ -315,6 +315,17 @@ config INGENIC_IRQ
|
|||
depends on MACH_INGENIC
|
||||
default y
|
||||
|
||||
config INGENIC_TCU_IRQ
|
||||
bool "Ingenic JZ47xx TCU interrupt controller"
|
||||
default MACH_INGENIC
|
||||
depends on MIPS || COMPILE_TEST
|
||||
select MFD_SYSCON
|
||||
help
|
||||
Support for interrupts in the Timer/Counter Unit (TCU) of the Ingenic
|
||||
JZ47xx SoCs.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config RENESAS_H8300H_INTC
|
||||
bool
|
||||
select IRQ_DOMAIN
|
||||
|
|
|
@ -75,6 +75,7 @@ obj-$(CONFIG_RENESAS_H8300H_INTC) += irq-renesas-h8300h.o
|
|||
obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o
|
||||
obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
|
||||
obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
|
||||
obj-$(CONFIG_INGENIC_TCU_IRQ) += irq-ingenic-tcu.o
|
||||
obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
|
||||
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
|
||||
obj-$(CONFIG_MSCC_OCELOT_IRQ) += irq-mscc-ocelot.o
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* JZ47xx SoCs TCU IRQ driver
|
||||
* Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/mfd/ingenic-tcu.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
struct ingenic_tcu {
|
||||
struct regmap *map;
|
||||
struct clk *clk;
|
||||
struct irq_domain *domain;
|
||||
unsigned int nb_parent_irqs;
|
||||
u32 parent_irqs[3];
|
||||
};
|
||||
|
||||
static void ingenic_tcu_intc_cascade(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
|
||||
struct irq_domain *domain = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
|
||||
struct regmap *map = gc->private;
|
||||
uint32_t irq_reg, irq_mask;
|
||||
unsigned int i;
|
||||
|
||||
regmap_read(map, TCU_REG_TFR, &irq_reg);
|
||||
regmap_read(map, TCU_REG_TMR, &irq_mask);
|
||||
|
||||
chained_irq_enter(irq_chip, desc);
|
||||
|
||||
irq_reg &= ~irq_mask;
|
||||
|
||||
for_each_set_bit(i, (unsigned long *)&irq_reg, 32)
|
||||
generic_handle_irq(irq_linear_revmap(domain, i));
|
||||
|
||||
chained_irq_exit(irq_chip, desc);
|
||||
}
|
||||
|
||||
static void ingenic_tcu_gc_unmask_enable_reg(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
struct regmap *map = gc->private;
|
||||
u32 mask = d->mask;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
regmap_write(map, ct->regs.ack, mask);
|
||||
regmap_write(map, ct->regs.enable, mask);
|
||||
*ct->mask_cache |= mask;
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static void ingenic_tcu_gc_mask_disable_reg(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
struct regmap *map = gc->private;
|
||||
u32 mask = d->mask;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
regmap_write(map, ct->regs.disable, mask);
|
||||
*ct->mask_cache &= ~mask;
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static void ingenic_tcu_gc_mask_disable_reg_and_ack(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
struct regmap *map = gc->private;
|
||||
u32 mask = d->mask;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
regmap_write(map, ct->regs.ack, mask);
|
||||
regmap_write(map, ct->regs.disable, mask);
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static int __init ingenic_tcu_irq_init(struct device_node *np,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct irq_chip_generic *gc;
|
||||
struct irq_chip_type *ct;
|
||||
struct ingenic_tcu *tcu;
|
||||
struct regmap *map;
|
||||
unsigned int i;
|
||||
int ret, irqs;
|
||||
|
||||
map = device_node_to_regmap(np);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
tcu = kzalloc(sizeof(*tcu), GFP_KERNEL);
|
||||
if (!tcu)
|
||||
return -ENOMEM;
|
||||
|
||||
tcu->map = map;
|
||||
|
||||
irqs = of_property_count_elems_of_size(np, "interrupts", sizeof(u32));
|
||||
if (irqs < 0 || irqs > ARRAY_SIZE(tcu->parent_irqs)) {
|
||||
pr_crit("%s: Invalid 'interrupts' property\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto err_free_tcu;
|
||||
}
|
||||
|
||||
tcu->nb_parent_irqs = irqs;
|
||||
|
||||
tcu->domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops,
|
||||
NULL);
|
||||
if (!tcu->domain) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_tcu;
|
||||
}
|
||||
|
||||
ret = irq_alloc_domain_generic_chips(tcu->domain, 32, 1, "TCU",
|
||||
handle_level_irq, 0,
|
||||
IRQ_NOPROBE | IRQ_LEVEL, 0);
|
||||
if (ret) {
|
||||
pr_crit("%s: Invalid 'interrupts' property\n", __func__);
|
||||
goto out_domain_remove;
|
||||
}
|
||||
|
||||
gc = irq_get_domain_generic_chip(tcu->domain, 0);
|
||||
ct = gc->chip_types;
|
||||
|
||||
gc->wake_enabled = IRQ_MSK(32);
|
||||
gc->private = tcu->map;
|
||||
|
||||
ct->regs.disable = TCU_REG_TMSR;
|
||||
ct->regs.enable = TCU_REG_TMCR;
|
||||
ct->regs.ack = TCU_REG_TFCR;
|
||||
ct->chip.irq_unmask = ingenic_tcu_gc_unmask_enable_reg;
|
||||
ct->chip.irq_mask = ingenic_tcu_gc_mask_disable_reg;
|
||||
ct->chip.irq_mask_ack = ingenic_tcu_gc_mask_disable_reg_and_ack;
|
||||
ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE;
|
||||
|
||||
/* Mask all IRQs by default */
|
||||
regmap_write(tcu->map, TCU_REG_TMSR, IRQ_MSK(32));
|
||||
|
||||
/*
|
||||
* On JZ4740, timer 0 and timer 1 have their own interrupt line;
|
||||
* timers 2-7 share one interrupt.
|
||||
* On SoCs >= JZ4770, timer 5 has its own interrupt line;
|
||||
* timers 0-4 and 6-7 share one single interrupt.
|
||||
*
|
||||
* To keep things simple, we just register the same handler to
|
||||
* all parent interrupts. The handler will properly detect which
|
||||
* channel fired the interrupt.
|
||||
*/
|
||||
for (i = 0; i < irqs; i++) {
|
||||
tcu->parent_irqs[i] = irq_of_parse_and_map(np, i);
|
||||
if (!tcu->parent_irqs[i]) {
|
||||
ret = -EINVAL;
|
||||
goto out_unmap_irqs;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(tcu->parent_irqs[i],
|
||||
ingenic_tcu_intc_cascade,
|
||||
tcu->domain);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_unmap_irqs:
|
||||
for (; i > 0; i--)
|
||||
irq_dispose_mapping(tcu->parent_irqs[i - 1]);
|
||||
out_domain_remove:
|
||||
irq_domain_remove(tcu->domain);
|
||||
err_free_tcu:
|
||||
kfree(tcu);
|
||||
return ret;
|
||||
}
|
||||
IRQCHIP_DECLARE(jz4740_tcu_irq, "ingenic,jz4740-tcu", ingenic_tcu_irq_init);
|
||||
IRQCHIP_DECLARE(jz4725b_tcu_irq, "ingenic,jz4725b-tcu", ingenic_tcu_irq_init);
|
||||
IRQCHIP_DECLARE(jz4770_tcu_irq, "ingenic,jz4770-tcu", ingenic_tcu_irq_init);
|
|
@ -40,7 +40,7 @@ static const struct regmap_config syscon_regmap_config = {
|
|||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
static struct syscon *of_syscon_register(struct device_node *np)
|
||||
static struct syscon *of_syscon_register(struct device_node *np, bool check_clk)
|
||||
{
|
||||
struct clk *clk;
|
||||
struct syscon *syscon;
|
||||
|
@ -51,9 +51,6 @@ static struct syscon *of_syscon_register(struct device_node *np)
|
|||
struct regmap_config syscon_config = syscon_regmap_config;
|
||||
struct resource res;
|
||||
|
||||
if (!of_device_is_compatible(np, "syscon"))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
|
||||
if (!syscon)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
@ -117,16 +114,18 @@ static struct syscon *of_syscon_register(struct device_node *np)
|
|||
goto err_regmap;
|
||||
}
|
||||
|
||||
clk = of_clk_get(np, 0);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
/* clock is optional */
|
||||
if (ret != -ENOENT)
|
||||
goto err_clk;
|
||||
} else {
|
||||
ret = regmap_mmio_attach_clk(regmap, clk);
|
||||
if (ret)
|
||||
goto err_attach;
|
||||
if (check_clk) {
|
||||
clk = of_clk_get(np, 0);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = PTR_ERR(clk);
|
||||
/* clock is optional */
|
||||
if (ret != -ENOENT)
|
||||
goto err_clk;
|
||||
} else {
|
||||
ret = regmap_mmio_attach_clk(regmap, clk);
|
||||
if (ret)
|
||||
goto err_attach;
|
||||
}
|
||||
}
|
||||
|
||||
syscon->regmap = regmap;
|
||||
|
@ -150,7 +149,8 @@ static struct syscon *of_syscon_register(struct device_node *np)
|
|||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||
static struct regmap *device_node_get_regmap(struct device_node *np,
|
||||
bool check_clk)
|
||||
{
|
||||
struct syscon *entry, *syscon = NULL;
|
||||
|
||||
|
@ -165,13 +165,27 @@ struct regmap *syscon_node_to_regmap(struct device_node *np)
|
|||
spin_unlock(&syscon_list_slock);
|
||||
|
||||
if (!syscon)
|
||||
syscon = of_syscon_register(np);
|
||||
syscon = of_syscon_register(np, check_clk);
|
||||
|
||||
if (IS_ERR(syscon))
|
||||
return ERR_CAST(syscon);
|
||||
|
||||
return syscon->regmap;
|
||||
}
|
||||
|
||||
struct regmap *device_node_to_regmap(struct device_node *np)
|
||||
{
|
||||
return device_node_get_regmap(np, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_node_to_regmap);
|
||||
|
||||
struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||
{
|
||||
if (!of_device_is_compatible(np, "syscon"))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return device_node_get_regmap(np, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
|
||||
|
||||
struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* This header provides clock numbers for the ingenic,tcu DT binding.
|
||||
*/
|
||||
|
||||
#ifndef __DT_BINDINGS_CLOCK_INGENIC_TCU_H__
|
||||
#define __DT_BINDINGS_CLOCK_INGENIC_TCU_H__
|
||||
|
||||
#define TCU_CLK_TIMER0 0
|
||||
#define TCU_CLK_TIMER1 1
|
||||
#define TCU_CLK_TIMER2 2
|
||||
#define TCU_CLK_TIMER3 3
|
||||
#define TCU_CLK_TIMER4 4
|
||||
#define TCU_CLK_TIMER5 5
|
||||
#define TCU_CLK_TIMER6 6
|
||||
#define TCU_CLK_TIMER7 7
|
||||
#define TCU_CLK_WDT 8
|
||||
#define TCU_CLK_OST 9
|
||||
|
||||
#endif /* __DT_BINDINGS_CLOCK_INGENIC_TCU_H__ */
|
|
@ -34,5 +34,6 @@
|
|||
#define JZ4740_CLK_ADC 19
|
||||
#define JZ4740_CLK_I2C 20
|
||||
#define JZ4740_CLK_AIC 21
|
||||
#define JZ4740_CLK_TCU 22
|
||||
|
||||
#endif /* __DT_BINDINGS_CLOCK_JZ4740_CGU_H__ */
|
||||
|
|
|
@ -17,12 +17,18 @@
|
|||
struct device_node;
|
||||
|
||||
#ifdef CONFIG_MFD_SYSCON
|
||||
extern struct regmap *device_node_to_regmap(struct device_node *np);
|
||||
extern struct regmap *syscon_node_to_regmap(struct device_node *np);
|
||||
extern struct regmap *syscon_regmap_lookup_by_compatible(const char *s);
|
||||
extern struct regmap *syscon_regmap_lookup_by_phandle(
|
||||
struct device_node *np,
|
||||
const char *property);
|
||||
#else
|
||||
static inline struct regmap *device_node_to_regmap(struct device_node *np)
|
||||
{
|
||||
return ERR_PTR(-ENOTSUPP);
|
||||
}
|
||||
|
||||
static inline struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||
{
|
||||
return ERR_PTR(-ENOTSUPP);
|
||||
|
|
Loading…
Reference in New Issue