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>alistair/sunxi64-5.4-dsi
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
|
arm64/index
|
||||||
ia64/index
|
ia64/index
|
||||||
m68k/index
|
m68k/index
|
||||||
|
mips/index
|
||||||
riscv/index
|
riscv/index
|
||||||
s390/index
|
s390/index
|
||||||
sh/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/;
|
/dts-v1/;
|
||||||
|
|
||||||
#include "jz4780.dtsi"
|
#include "jz4780.dtsi"
|
||||||
|
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||||
#include <dt-bindings/gpio/gpio.h>
|
#include <dt-bindings/gpio/gpio.h>
|
||||||
|
|
||||||
/ {
|
/ {
|
||||||
|
@ -238,3 +239,9 @@
|
||||||
bias-disable;
|
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/;
|
/dts-v1/;
|
||||||
|
|
||||||
#include "jz4770.dtsi"
|
#include "jz4770.dtsi"
|
||||||
|
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||||
|
|
||||||
/ {
|
/ {
|
||||||
compatible = "gcw,zero", "ingenic,jz4770";
|
compatible = "gcw,zero", "ingenic,jz4770";
|
||||||
|
@ -60,3 +61,12 @@
|
||||||
/* The WiFi module is connected to the UHC. */
|
/* The WiFi module is connected to the UHC. */
|
||||||
status = "okay";
|
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 @@
|
||||||
clock-names = "rtc";
|
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 {
|
rtc_dev: rtc@10003000 {
|
||||||
compatible = "ingenic,jz4740-rtc";
|
compatible = "ingenic,jz4740-rtc";
|
||||||
reg = <0x10003000 0x40>;
|
reg = <0x10003000 0x40>;
|
||||||
|
|
|
@ -46,6 +46,27 @@
|
||||||
#clock-cells = <1>;
|
#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 {
|
pinctrl: pin-controller@10010000 {
|
||||||
compatible = "ingenic,jz4770-pinctrl";
|
compatible = "ingenic,jz4770-pinctrl";
|
||||||
reg = <0x10010000 0x600>;
|
reg = <0x10010000 0x600>;
|
||||||
|
|
|
@ -46,6 +46,29 @@
|
||||||
#clock-cells = <1>;
|
#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 {
|
rtc_dev: rtc@10003000 {
|
||||||
compatible = "ingenic,jz4780-rtc";
|
compatible = "ingenic,jz4780-rtc";
|
||||||
reg = <0x10003000 0x4c>;
|
reg = <0x10003000 0x4c>;
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
/dts-v1/;
|
/dts-v1/;
|
||||||
|
|
||||||
#include "jz4740.dtsi"
|
#include "jz4740.dtsi"
|
||||||
#include <dt-bindings/gpio/gpio.h>
|
|
||||||
|
|
||||||
#include <dt-bindings/gpio/gpio.h>
|
#include <dt-bindings/gpio/gpio.h>
|
||||||
#include <dt-bindings/iio/adc/ingenic,adc.h>
|
#include <dt-bindings/iio/adc/ingenic,adc.h>
|
||||||
|
#include <dt-bindings/clock/ingenic,tcu.h>
|
||||||
#include <dt-bindings/input/input.h>
|
#include <dt-bindings/input/input.h>
|
||||||
|
|
||||||
#define KEY_QI_QI KEY_F13
|
#define KEY_QI_QI KEY_F13
|
||||||
|
@ -350,3 +350,9 @@
|
||||||
pinctrl-names = "default";
|
pinctrl-names = "default";
|
||||||
pinctrl-0 = <&pins_mmc>;
|
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
|
* JZ4740 platform time support
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/clk.h>
|
|
||||||
#include <linux/clk-provider.h>
|
#include <linux/clk-provider.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/clocksource.h>
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/time.h>
|
|
||||||
|
|
||||||
#include <linux/clockchips.h>
|
|
||||||
#include <linux/sched_clock.h>
|
|
||||||
|
|
||||||
#include <asm/mach-jz4740/irq.h>
|
|
||||||
#include <asm/mach-jz4740/timer.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)
|
void __init plat_time_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
uint32_t clk_rate;
|
|
||||||
uint16_t ctrl;
|
|
||||||
struct clk *ext_clk;
|
|
||||||
|
|
||||||
of_clk_init(NULL);
|
of_clk_init(NULL);
|
||||||
jz4740_timer_init();
|
jz4740_timer_init();
|
||||||
|
timer_probe();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
menu "Ingenic JZ47xx CGU drivers"
|
menu "Ingenic SoCs drivers"
|
||||||
depends on MIPS
|
depends on MIPS
|
||||||
|
|
||||||
config INGENIC_CGU_COMMON
|
config INGENIC_CGU_COMMON
|
||||||
|
@ -45,4 +45,12 @@ config INGENIC_CGU_JZ4780
|
||||||
|
|
||||||
If building for a JZ4780 SoC, you want to say Y here.
|
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
|
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_JZ4725B) += jz4725b-cgu.o
|
||||||
obj-$(CONFIG_INGENIC_CGU_JZ4770) += jz4770-cgu.o
|
obj-$(CONFIG_INGENIC_CGU_JZ4770) += jz4770-cgu.o
|
||||||
obj-$(CONFIG_INGENIC_CGU_JZ4780) += jz4780-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 },
|
.parents = { JZ4740_CLK_EXT, -1, -1, -1 },
|
||||||
.gate = { CGU_REG_CLKGR, 5 },
|
.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)
|
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
|
help
|
||||||
Enables the support for Milbeaut timer driver.
|
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
|
endmenu
|
||||||
|
|
|
@ -80,6 +80,7 @@ obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o
|
||||||
obj-$(CONFIG_H8300_TMR8) += h8300_timer8.o
|
obj-$(CONFIG_H8300_TMR8) += h8300_timer8.o
|
||||||
obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o
|
obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o
|
||||||
obj-$(CONFIG_H8300_TPU) += h8300_tpu.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_CLKSRC_ST_LPC) += clksrc_st_lpc.o
|
||||||
obj-$(CONFIG_X86_NUMACHIP) += numachip.o
|
obj-$(CONFIG_X86_NUMACHIP) += numachip.o
|
||||||
obj-$(CONFIG_ATCPIT100_TIMER) += timer-atcpit100.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
|
depends on MACH_INGENIC
|
||||||
default y
|
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
|
config RENESAS_H8300H_INTC
|
||||||
bool
|
bool
|
||||||
select IRQ_DOMAIN
|
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_RENESAS_H8S_INTC) += irq-renesas-h8s.o
|
||||||
obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
|
obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
|
||||||
obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.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_IMX_GPCV2) += irq-imx-gpcv2.o
|
||||||
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
|
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
|
||||||
obj-$(CONFIG_MSCC_OCELOT_IRQ) += irq-mscc-ocelot.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,
|
.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 clk *clk;
|
||||||
struct syscon *syscon;
|
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 regmap_config syscon_config = syscon_regmap_config;
|
||||||
struct resource res;
|
struct resource res;
|
||||||
|
|
||||||
if (!of_device_is_compatible(np, "syscon"))
|
|
||||||
return ERR_PTR(-EINVAL);
|
|
||||||
|
|
||||||
syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
|
syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
|
||||||
if (!syscon)
|
if (!syscon)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
@ -117,16 +114,18 @@ static struct syscon *of_syscon_register(struct device_node *np)
|
||||||
goto err_regmap;
|
goto err_regmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
clk = of_clk_get(np, 0);
|
if (check_clk) {
|
||||||
if (IS_ERR(clk)) {
|
clk = of_clk_get(np, 0);
|
||||||
ret = PTR_ERR(clk);
|
if (IS_ERR(clk)) {
|
||||||
/* clock is optional */
|
ret = PTR_ERR(clk);
|
||||||
if (ret != -ENOENT)
|
/* clock is optional */
|
||||||
goto err_clk;
|
if (ret != -ENOENT)
|
||||||
} else {
|
goto err_clk;
|
||||||
ret = regmap_mmio_attach_clk(regmap, clk);
|
} else {
|
||||||
if (ret)
|
ret = regmap_mmio_attach_clk(regmap, clk);
|
||||||
goto err_attach;
|
if (ret)
|
||||||
|
goto err_attach;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syscon->regmap = regmap;
|
syscon->regmap = regmap;
|
||||||
|
@ -150,7 +149,8 @@ err_map:
|
||||||
return ERR_PTR(ret);
|
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;
|
struct syscon *entry, *syscon = NULL;
|
||||||
|
|
||||||
|
@ -165,13 +165,27 @@ struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||||
spin_unlock(&syscon_list_slock);
|
spin_unlock(&syscon_list_slock);
|
||||||
|
|
||||||
if (!syscon)
|
if (!syscon)
|
||||||
syscon = of_syscon_register(np);
|
syscon = of_syscon_register(np, check_clk);
|
||||||
|
|
||||||
if (IS_ERR(syscon))
|
if (IS_ERR(syscon))
|
||||||
return ERR_CAST(syscon);
|
return ERR_CAST(syscon);
|
||||||
|
|
||||||
return syscon->regmap;
|
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);
|
EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
|
||||||
|
|
||||||
struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
|
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_ADC 19
|
||||||
#define JZ4740_CLK_I2C 20
|
#define JZ4740_CLK_I2C 20
|
||||||
#define JZ4740_CLK_AIC 21
|
#define JZ4740_CLK_AIC 21
|
||||||
|
#define JZ4740_CLK_TCU 22
|
||||||
|
|
||||||
#endif /* __DT_BINDINGS_CLOCK_JZ4740_CGU_H__ */
|
#endif /* __DT_BINDINGS_CLOCK_JZ4740_CGU_H__ */
|
||||||
|
|
|
@ -17,12 +17,18 @@
|
||||||
struct device_node;
|
struct device_node;
|
||||||
|
|
||||||
#ifdef CONFIG_MFD_SYSCON
|
#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_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_compatible(const char *s);
|
||||||
extern struct regmap *syscon_regmap_lookup_by_phandle(
|
extern struct regmap *syscon_regmap_lookup_by_phandle(
|
||||||
struct device_node *np,
|
struct device_node *np,
|
||||||
const char *property);
|
const char *property);
|
||||||
#else
|
#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)
|
static inline struct regmap *syscon_node_to_regmap(struct device_node *np)
|
||||||
{
|
{
|
||||||
return ERR_PTR(-ENOTSUPP);
|
return ERR_PTR(-ENOTSUPP);
|
||||||
|
|
Loading…
Reference in New Issue