All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/6] clocksource: rework Atmel TCB timer driver
@ 2018-06-19 21:19 ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

Hi,

This series reworks the Atmel TCB drivers. It introduces a new driver to handle
the clocksource and clockevent devices.

As a reminder, this is necessary because:
 - the current tcb_clksrc driver is probed too late to be able to be used at
   boot and we now have SoCs that don't have a PIT. They currently are not able
   to boot a mainline kernel.
 - using the PIT doesn't work well with preempt-rt because its interrupt is
   shared (in particular with the UART and their interrupt flags are
   incompatible)
 - the current solution is wasting some TCB channels

The plan is to get this driver upstream, then convert the TCB PWM driver to be
able to get rid of the tcb_clksrc driver along with atmel_tclib now that AVR32
is gone.

changes in v5:
 - rebased on v4.18-rc1
 - fixed the clock enabling/disabling in atomic context under preempt-rt

Changes in v4:
 - rebased on top of v4.17-rc1
 - fixed an issue when setting max_delta for clockevents_config_and_register

Alexandre Belloni (6):
  ARM: at91: add TCB registers definitions
  clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  clocksource/drivers: atmel-pit: make option silent
  ARM: at91: Implement clocksource selection
  ARM: configs: at91: use new TCB timer driver
  ARM: configs: at91: unselect PIT

 arch/arm/configs/at91_dt_defconfig    |   2 +-
 arch/arm/configs/sama5_defconfig      |   2 +-
 arch/arm/mach-at91/Kconfig            |  25 +
 drivers/clocksource/Kconfig           |  13 +-
 drivers/clocksource/Makefile          |   3 +-
 drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
 include/soc/at91/atmel_tcb.h          | 216 +++++++++
 7 files changed, 887 insertions(+), 4 deletions(-)
 create mode 100644 drivers/clocksource/timer-atmel-tcb.c
 create mode 100644 include/soc/at91/atmel_tcb.h

-- 
2.17.1


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 0/6] clocksource: rework Atmel TCB timer driver
@ 2018-06-19 21:19 ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

This series reworks the Atmel TCB drivers. It introduces a new driver to handle
the clocksource and clockevent devices.

As a reminder, this is necessary because:
 - the current tcb_clksrc driver is probed too late to be able to be used at
   boot and we now have SoCs that don't have a PIT. They currently are not able
   to boot a mainline kernel.
 - using the PIT doesn't work well with preempt-rt because its interrupt is
   shared (in particular with the UART and their interrupt flags are
   incompatible)
 - the current solution is wasting some TCB channels

The plan is to get this driver upstream, then convert the TCB PWM driver to be
able to get rid of the tcb_clksrc driver along with atmel_tclib now that AVR32
is gone.

changes in v5:
 - rebased on v4.18-rc1
 - fixed the clock enabling/disabling in atomic context under preempt-rt

Changes in v4:
 - rebased on top of v4.17-rc1
 - fixed an issue when setting max_delta for clockevents_config_and_register

Alexandre Belloni (6):
  ARM: at91: add TCB registers definitions
  clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  clocksource/drivers: atmel-pit: make option silent
  ARM: at91: Implement clocksource selection
  ARM: configs: at91: use new TCB timer driver
  ARM: configs: at91: unselect PIT

 arch/arm/configs/at91_dt_defconfig    |   2 +-
 arch/arm/configs/sama5_defconfig      |   2 +-
 arch/arm/mach-at91/Kconfig            |  25 +
 drivers/clocksource/Kconfig           |  13 +-
 drivers/clocksource/Makefile          |   3 +-
 drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
 include/soc/at91/atmel_tcb.h          | 216 +++++++++
 7 files changed, 887 insertions(+), 4 deletions(-)
 create mode 100644 drivers/clocksource/timer-atmel-tcb.c
 create mode 100644 include/soc/at91/atmel_tcb.h

-- 
2.17.1

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 1/6] ARM: at91: add TCB registers definitions
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

Add registers and bits definitions for the timer counter blocks found on
Atmel ARM SoCs.

Tested-by: Alexander Dahl <ada@thorsis.com>
Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
 1 file changed, 216 insertions(+)
 create mode 100644 include/soc/at91/atmel_tcb.h

diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
new file mode 100644
index 000000000000..3ed66031fc76
--- /dev/null
+++ b/include/soc/at91/atmel_tcb.h
@@ -0,0 +1,216 @@
+//SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018 Microchip */
+
+#ifndef __SOC_ATMEL_TCB_H
+#define __SOC_ATMEL_TCB_H
+
+/* Channel registers */
+#define ATMEL_TC_COFFS(c)		((c) * 0x40)
+#define ATMEL_TC_CCR(c)			ATMEL_TC_COFFS(c)
+#define ATMEL_TC_CMR(c)			(ATMEL_TC_COFFS(c) + 0x4)
+#define ATMEL_TC_SMMR(c)		(ATMEL_TC_COFFS(c) + 0x8)
+#define ATMEL_TC_RAB(c)			(ATMEL_TC_COFFS(c) + 0xc)
+#define ATMEL_TC_CV(c)			(ATMEL_TC_COFFS(c) + 0x10)
+#define ATMEL_TC_RA(c)			(ATMEL_TC_COFFS(c) + 0x14)
+#define ATMEL_TC_RB(c)			(ATMEL_TC_COFFS(c) + 0x18)
+#define ATMEL_TC_RC(c)			(ATMEL_TC_COFFS(c) + 0x1c)
+#define ATMEL_TC_SR(c)			(ATMEL_TC_COFFS(c) + 0x20)
+#define ATMEL_TC_IER(c)			(ATMEL_TC_COFFS(c) + 0x24)
+#define ATMEL_TC_IDR(c)			(ATMEL_TC_COFFS(c) + 0x28)
+#define ATMEL_TC_IMR(c)			(ATMEL_TC_COFFS(c) + 0x2c)
+#define ATMEL_TC_EMR(c)			(ATMEL_TC_COFFS(c) + 0x30)
+
+/* Block registers */
+#define ATMEL_TC_BCR			0xc0
+#define ATMEL_TC_BMR			0xc4
+#define ATMEL_TC_QIER			0xc8
+#define ATMEL_TC_QIDR			0xcc
+#define ATMEL_TC_QIMR			0xd0
+#define ATMEL_TC_QISR			0xd4
+#define ATMEL_TC_FMR			0xd8
+#define ATMEL_TC_WPMR			0xe4
+
+/* CCR fields */
+#define ATMEL_TC_CCR_CLKEN		BIT(0)
+#define ATMEL_TC_CCR_CLKDIS		BIT(1)
+#define ATMEL_TC_CCR_SWTRG		BIT(2)
+
+/* Common CMR fields */
+#define ATMEL_TC_CMR_TCLKS_MSK		GENMASK(2, 0)
+#define ATMEL_TC_CMR_TCLK(x)		(x)
+#define ATMEL_TC_CMR_XC(x)		((x) + 5)
+#define ATMEL_TC_CMR_CLKI		BIT(3)
+#define ATMEL_TC_CMR_BURST_MSK		GENMASK(5, 4)
+#define ATMEL_TC_CMR_BURST_XC(x)	(((x) + 1) << 4)
+#define ATMEL_TC_CMR_WAVE		BIT(15)
+
+/* Capture mode CMR fields */
+#define ATMEL_TC_CMR_LDBSTOP		BIT(6)
+#define ATMEL_TC_CMR_LDBDIS		BIT(7)
+#define ATMEL_TC_CMR_ETRGEDG_MSK	GENMASK(9, 8)
+#define ATMEL_TC_CMR_ETRGEDG_NONE	(0 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_RISING	(1 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_FALLING	(2 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_BOTH	(3 << 8)
+#define ATMEL_TC_CMR_ABETRG		BIT(10)
+#define ATMEL_TC_CMR_CPCTRG		BIT(14)
+#define ATMEL_TC_CMR_LDRA_MSK		GENMASK(17, 16)
+#define ATMEL_TC_CMR_LDRA_NONE		(0 << 16)
+#define ATMEL_TC_CMR_LDRA_RISING	(1 << 16)
+#define ATMEL_TC_CMR_LDRA_FALLING	(2 << 16)
+#define ATMEL_TC_CMR_LDRA_BOTH		(3 << 16)
+#define ATMEL_TC_CMR_LDRB_MSK		GENMASK(19, 18)
+#define ATMEL_TC_CMR_LDRB_NONE		(0 << 18)
+#define ATMEL_TC_CMR_LDRB_RISING	(1 << 18)
+#define ATMEL_TC_CMR_LDRB_FALLING	(2 << 18)
+#define ATMEL_TC_CMR_LDRB_BOTH		(3 << 18)
+#define ATMEL_TC_CMR_SBSMPLR_MSK	GENMASK(22, 20)
+#define ATMEL_TC_CMR_SBSMPLR(x)		((x) << 20)
+
+/* Waveform mode CMR fields */
+#define ATMEL_TC_CMR_CPCSTOP		BIT(6)
+#define ATMEL_TC_CMR_CPCDIS		BIT(7)
+#define ATMEL_TC_CMR_EEVTEDG_MSK	GENMASK(9, 8)
+#define ATMEL_TC_CMR_EEVTEDG_NONE	(0 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_RISING	(1 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_FALLING	(2 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_BOTH	(3 << 8)
+#define ATMEL_TC_CMR_EEVT_MSK		GENMASK(11, 10)
+#define ATMEL_TC_CMR_EEVT_XC(x)		(((x) + 1) << 10)
+#define ATMEL_TC_CMR_ENETRG		BIT(12)
+#define ATMEL_TC_CMR_WAVESEL_MSK	GENMASK(14, 13)
+#define ATMEL_TC_CMR_WAVESEL_UP		(0 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPDOWN	(1 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPRC	(2 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPDOWNRC	(3 << 13)
+#define ATMEL_TC_CMR_ACPA_MSK		GENMASK(17, 16)
+#define ATMEL_TC_CMR_ACPA(a)		(ATMEL_TC_CMR_ACTION_##a << 16)
+#define ATMEL_TC_CMR_ACPC_MSK		GENMASK(19, 18)
+#define ATMEL_TC_CMR_ACPC(a)		(ATMEL_TC_CMR_ACTION_##a << 18)
+#define ATMEL_TC_CMR_AEEVT_MSK		GENMASK(21, 20)
+#define ATMEL_TC_CMR_AEEVT(a)		(ATMEL_TC_CMR_ACTION_##a << 20)
+#define ATMEL_TC_CMR_ASWTRG_MSK		GENMASK(23, 22)
+#define ATMEL_TC_CMR_ASWTRG(a)		(ATMEL_TC_CMR_ACTION_##a << 22)
+#define ATMEL_TC_CMR_BCPB_MSK		GENMASK(25, 24)
+#define ATMEL_TC_CMR_BCPB(a)		(ATMEL_TC_CMR_ACTION_##a << 24)
+#define ATMEL_TC_CMR_BCPC_MSK		GENMASK(27, 26)
+#define ATMEL_TC_CMR_BCPC(a)		(ATMEL_TC_CMR_ACTION_##a << 26)
+#define ATMEL_TC_CMR_BEEVT_MSK		GENMASK(29, 28)
+#define ATMEL_TC_CMR_BEEVT(a)		(ATMEL_TC_CMR_ACTION_##a << 28)
+#define ATMEL_TC_CMR_BSWTRG_MSK		GENMASK(31, 30)
+#define ATMEL_TC_CMR_BSWTRG(a)		(ATMEL_TC_CMR_ACTION_##a << 30)
+#define ATMEL_TC_CMR_ACTION_NONE	0
+#define ATMEL_TC_CMR_ACTION_SET		1
+#define ATMEL_TC_CMR_ACTION_CLEAR	2
+#define ATMEL_TC_CMR_ACTION_TOGGLE	3
+
+/* SMMR fields */
+#define ATMEL_TC_SMMR_GCEN		BIT(0)
+#define ATMEL_TC_SMMR_DOWN		BIT(1)
+
+/* SR/IER/IDR/IMR fields */
+#define ATMEL_TC_COVFS			BIT(0)
+#define ATMEL_TC_LOVRS			BIT(1)
+#define ATMEL_TC_CPAS			BIT(2)
+#define ATMEL_TC_CPBS			BIT(3)
+#define ATMEL_TC_CPCS			BIT(4)
+#define ATMEL_TC_LDRAS			BIT(5)
+#define ATMEL_TC_LDRBS			BIT(6)
+#define ATMEL_TC_ETRGS			BIT(7)
+#define ATMEL_TC_CLKSTA			BIT(16)
+#define ATMEL_TC_MTIOA			BIT(17)
+#define ATMEL_TC_MTIOB			BIT(18)
+
+/* EMR fields */
+#define ATMEL_TC_EMR_TRIGSRCA_MSK	GENMASK(1, 0)
+#define ATMEL_TC_EMR_TRIGSRCA_TIOA	0
+#define ATMEL_TC_EMR_TRIGSRCA_PWMX	1
+#define ATMEL_TC_EMR_TRIGSRCB_MSK	GENMASK(5, 4)
+#define ATMEL_TC_EMR_TRIGSRCB_TIOB	(0 << 4)
+#define ATMEL_TC_EMR_TRIGSRCB_PWM	(1 << 4)
+#define ATMEL_TC_EMR_NOCLKDIV		BIT(8)
+
+/* BCR fields */
+#define ATMEL_TC_BCR_SYNC		BIT(0)
+
+/* BMR fields */
+#define ATMEL_TC_BMR_TCXC_MSK(c)	GENMASK(((c) * 2) + 1, (c) * 2)
+#define ATMEL_TC_BMR_TCXC(x, c)		((x) << (2 * (c)))
+#define ATMEL_TC_BMR_QDEN		BIT(8)
+#define ATMEL_TC_BMR_POSEN		BIT(9)
+#define ATMEL_TC_BMR_SPEEDEN		BIT(10)
+#define ATMEL_TC_BMR_QDTRANS		BIT(11)
+#define ATMEL_TC_BMR_EDGPHA		BIT(12)
+#define ATMEL_TC_BMR_INVA		BIT(13)
+#define ATMEL_TC_BMR_INVB		BIT(14)
+#define ATMEL_TC_BMR_INVIDX		BIT(15)
+#define ATMEL_TC_BMR_SWAP		BIT(16)
+#define ATMEL_TC_BMR_IDXPHB		BIT(17)
+#define ATMEL_TC_BMR_AUTOC		BIT(18)
+#define ATMEL_TC_MAXFILT_MSK		GENMASK(25, 20)
+#define ATMEL_TC_MAXFILT(x)		(((x) - 1) << 20)
+#define ATMEL_TC_MAXCMP_MSK		GENMASK(29, 26)
+#define ATMEL_TC_MAXCMP(x)		((x) << 26)
+
+/* QEDC fields */
+#define ATMEL_TC_QEDC_IDX		BIT(0)
+#define ATMEL_TC_QEDC_DIRCHG		BIT(1)
+#define ATMEL_TC_QEDC_QERR		BIT(2)
+#define ATMEL_TC_QEDC_MPE		BIT(3)
+#define ATMEL_TC_QEDC_DIR		BIT(8)
+
+/* FMR fields */
+#define ATMEL_TC_FMR_ENCF(x)		BIT(x)
+
+/* WPMR fields */
+#define ATMEL_TC_WPMR_WPKEY		(0x54494d << 8)
+#define ATMEL_TC_WPMR_WPEN		BIT(0)
+
+static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
+{
+	struct clk *clk;
+	char clk_name[] = "t0_clk";
+
+	clk_name[1] += channel;
+	clk = of_clk_get_by_name(node->parent, clk_name);
+	if (!IS_ERR(clk))
+		return clk;
+
+	return of_clk_get_by_name(node->parent, "t0_clk");
+}
+
+static inline int tcb_irq_get(struct device_node *node, int channel)
+{
+	int irq;
+
+	irq = of_irq_get(node->parent, channel);
+	if (irq > 0)
+		return irq;
+
+	return of_irq_get(node->parent, 0);
+}
+
+static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
+
+struct atmel_tcb_info {
+	int bits;
+};
+
+static const struct atmel_tcb_info atmel_tcb_infos[] = {
+	{ .bits = 16 },
+	{ .bits = 32 },
+};
+
+static const struct of_device_id atmel_tcb_dt_ids[] = {
+	{
+		.compatible = "atmel,at91rm9200-tcb",
+		.data = &atmel_tcb_infos[0],
+	}, {
+		.compatible = "atmel,at91sam9x5-tcb",
+		.data = &atmel_tcb_infos[1],
+	}, {
+		/* sentinel */
+	}
+};
+
+#endif /* __SOC_ATMEL_TCB_H */
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 1/6] ARM: at91: add TCB registers definitions
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

Add registers and bits definitions for the timer counter blocks found on
Atmel ARM SoCs.

Tested-by: Alexander Dahl <ada@thorsis.com>
Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
 1 file changed, 216 insertions(+)
 create mode 100644 include/soc/at91/atmel_tcb.h

diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
new file mode 100644
index 000000000000..3ed66031fc76
--- /dev/null
+++ b/include/soc/at91/atmel_tcb.h
@@ -0,0 +1,216 @@
+//SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018 Microchip */
+
+#ifndef __SOC_ATMEL_TCB_H
+#define __SOC_ATMEL_TCB_H
+
+/* Channel registers */
+#define ATMEL_TC_COFFS(c)		((c) * 0x40)
+#define ATMEL_TC_CCR(c)			ATMEL_TC_COFFS(c)
+#define ATMEL_TC_CMR(c)			(ATMEL_TC_COFFS(c) + 0x4)
+#define ATMEL_TC_SMMR(c)		(ATMEL_TC_COFFS(c) + 0x8)
+#define ATMEL_TC_RAB(c)			(ATMEL_TC_COFFS(c) + 0xc)
+#define ATMEL_TC_CV(c)			(ATMEL_TC_COFFS(c) + 0x10)
+#define ATMEL_TC_RA(c)			(ATMEL_TC_COFFS(c) + 0x14)
+#define ATMEL_TC_RB(c)			(ATMEL_TC_COFFS(c) + 0x18)
+#define ATMEL_TC_RC(c)			(ATMEL_TC_COFFS(c) + 0x1c)
+#define ATMEL_TC_SR(c)			(ATMEL_TC_COFFS(c) + 0x20)
+#define ATMEL_TC_IER(c)			(ATMEL_TC_COFFS(c) + 0x24)
+#define ATMEL_TC_IDR(c)			(ATMEL_TC_COFFS(c) + 0x28)
+#define ATMEL_TC_IMR(c)			(ATMEL_TC_COFFS(c) + 0x2c)
+#define ATMEL_TC_EMR(c)			(ATMEL_TC_COFFS(c) + 0x30)
+
+/* Block registers */
+#define ATMEL_TC_BCR			0xc0
+#define ATMEL_TC_BMR			0xc4
+#define ATMEL_TC_QIER			0xc8
+#define ATMEL_TC_QIDR			0xcc
+#define ATMEL_TC_QIMR			0xd0
+#define ATMEL_TC_QISR			0xd4
+#define ATMEL_TC_FMR			0xd8
+#define ATMEL_TC_WPMR			0xe4
+
+/* CCR fields */
+#define ATMEL_TC_CCR_CLKEN		BIT(0)
+#define ATMEL_TC_CCR_CLKDIS		BIT(1)
+#define ATMEL_TC_CCR_SWTRG		BIT(2)
+
+/* Common CMR fields */
+#define ATMEL_TC_CMR_TCLKS_MSK		GENMASK(2, 0)
+#define ATMEL_TC_CMR_TCLK(x)		(x)
+#define ATMEL_TC_CMR_XC(x)		((x) + 5)
+#define ATMEL_TC_CMR_CLKI		BIT(3)
+#define ATMEL_TC_CMR_BURST_MSK		GENMASK(5, 4)
+#define ATMEL_TC_CMR_BURST_XC(x)	(((x) + 1) << 4)
+#define ATMEL_TC_CMR_WAVE		BIT(15)
+
+/* Capture mode CMR fields */
+#define ATMEL_TC_CMR_LDBSTOP		BIT(6)
+#define ATMEL_TC_CMR_LDBDIS		BIT(7)
+#define ATMEL_TC_CMR_ETRGEDG_MSK	GENMASK(9, 8)
+#define ATMEL_TC_CMR_ETRGEDG_NONE	(0 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_RISING	(1 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_FALLING	(2 << 8)
+#define ATMEL_TC_CMR_ETRGEDG_BOTH	(3 << 8)
+#define ATMEL_TC_CMR_ABETRG		BIT(10)
+#define ATMEL_TC_CMR_CPCTRG		BIT(14)
+#define ATMEL_TC_CMR_LDRA_MSK		GENMASK(17, 16)
+#define ATMEL_TC_CMR_LDRA_NONE		(0 << 16)
+#define ATMEL_TC_CMR_LDRA_RISING	(1 << 16)
+#define ATMEL_TC_CMR_LDRA_FALLING	(2 << 16)
+#define ATMEL_TC_CMR_LDRA_BOTH		(3 << 16)
+#define ATMEL_TC_CMR_LDRB_MSK		GENMASK(19, 18)
+#define ATMEL_TC_CMR_LDRB_NONE		(0 << 18)
+#define ATMEL_TC_CMR_LDRB_RISING	(1 << 18)
+#define ATMEL_TC_CMR_LDRB_FALLING	(2 << 18)
+#define ATMEL_TC_CMR_LDRB_BOTH		(3 << 18)
+#define ATMEL_TC_CMR_SBSMPLR_MSK	GENMASK(22, 20)
+#define ATMEL_TC_CMR_SBSMPLR(x)		((x) << 20)
+
+/* Waveform mode CMR fields */
+#define ATMEL_TC_CMR_CPCSTOP		BIT(6)
+#define ATMEL_TC_CMR_CPCDIS		BIT(7)
+#define ATMEL_TC_CMR_EEVTEDG_MSK	GENMASK(9, 8)
+#define ATMEL_TC_CMR_EEVTEDG_NONE	(0 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_RISING	(1 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_FALLING	(2 << 8)
+#define ATMEL_TC_CMR_EEVTEDG_BOTH	(3 << 8)
+#define ATMEL_TC_CMR_EEVT_MSK		GENMASK(11, 10)
+#define ATMEL_TC_CMR_EEVT_XC(x)		(((x) + 1) << 10)
+#define ATMEL_TC_CMR_ENETRG		BIT(12)
+#define ATMEL_TC_CMR_WAVESEL_MSK	GENMASK(14, 13)
+#define ATMEL_TC_CMR_WAVESEL_UP		(0 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPDOWN	(1 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPRC	(2 << 13)
+#define ATMEL_TC_CMR_WAVESEL_UPDOWNRC	(3 << 13)
+#define ATMEL_TC_CMR_ACPA_MSK		GENMASK(17, 16)
+#define ATMEL_TC_CMR_ACPA(a)		(ATMEL_TC_CMR_ACTION_##a << 16)
+#define ATMEL_TC_CMR_ACPC_MSK		GENMASK(19, 18)
+#define ATMEL_TC_CMR_ACPC(a)		(ATMEL_TC_CMR_ACTION_##a << 18)
+#define ATMEL_TC_CMR_AEEVT_MSK		GENMASK(21, 20)
+#define ATMEL_TC_CMR_AEEVT(a)		(ATMEL_TC_CMR_ACTION_##a << 20)
+#define ATMEL_TC_CMR_ASWTRG_MSK		GENMASK(23, 22)
+#define ATMEL_TC_CMR_ASWTRG(a)		(ATMEL_TC_CMR_ACTION_##a << 22)
+#define ATMEL_TC_CMR_BCPB_MSK		GENMASK(25, 24)
+#define ATMEL_TC_CMR_BCPB(a)		(ATMEL_TC_CMR_ACTION_##a << 24)
+#define ATMEL_TC_CMR_BCPC_MSK		GENMASK(27, 26)
+#define ATMEL_TC_CMR_BCPC(a)		(ATMEL_TC_CMR_ACTION_##a << 26)
+#define ATMEL_TC_CMR_BEEVT_MSK		GENMASK(29, 28)
+#define ATMEL_TC_CMR_BEEVT(a)		(ATMEL_TC_CMR_ACTION_##a << 28)
+#define ATMEL_TC_CMR_BSWTRG_MSK		GENMASK(31, 30)
+#define ATMEL_TC_CMR_BSWTRG(a)		(ATMEL_TC_CMR_ACTION_##a << 30)
+#define ATMEL_TC_CMR_ACTION_NONE	0
+#define ATMEL_TC_CMR_ACTION_SET		1
+#define ATMEL_TC_CMR_ACTION_CLEAR	2
+#define ATMEL_TC_CMR_ACTION_TOGGLE	3
+
+/* SMMR fields */
+#define ATMEL_TC_SMMR_GCEN		BIT(0)
+#define ATMEL_TC_SMMR_DOWN		BIT(1)
+
+/* SR/IER/IDR/IMR fields */
+#define ATMEL_TC_COVFS			BIT(0)
+#define ATMEL_TC_LOVRS			BIT(1)
+#define ATMEL_TC_CPAS			BIT(2)
+#define ATMEL_TC_CPBS			BIT(3)
+#define ATMEL_TC_CPCS			BIT(4)
+#define ATMEL_TC_LDRAS			BIT(5)
+#define ATMEL_TC_LDRBS			BIT(6)
+#define ATMEL_TC_ETRGS			BIT(7)
+#define ATMEL_TC_CLKSTA			BIT(16)
+#define ATMEL_TC_MTIOA			BIT(17)
+#define ATMEL_TC_MTIOB			BIT(18)
+
+/* EMR fields */
+#define ATMEL_TC_EMR_TRIGSRCA_MSK	GENMASK(1, 0)
+#define ATMEL_TC_EMR_TRIGSRCA_TIOA	0
+#define ATMEL_TC_EMR_TRIGSRCA_PWMX	1
+#define ATMEL_TC_EMR_TRIGSRCB_MSK	GENMASK(5, 4)
+#define ATMEL_TC_EMR_TRIGSRCB_TIOB	(0 << 4)
+#define ATMEL_TC_EMR_TRIGSRCB_PWM	(1 << 4)
+#define ATMEL_TC_EMR_NOCLKDIV		BIT(8)
+
+/* BCR fields */
+#define ATMEL_TC_BCR_SYNC		BIT(0)
+
+/* BMR fields */
+#define ATMEL_TC_BMR_TCXC_MSK(c)	GENMASK(((c) * 2) + 1, (c) * 2)
+#define ATMEL_TC_BMR_TCXC(x, c)		((x) << (2 * (c)))
+#define ATMEL_TC_BMR_QDEN		BIT(8)
+#define ATMEL_TC_BMR_POSEN		BIT(9)
+#define ATMEL_TC_BMR_SPEEDEN		BIT(10)
+#define ATMEL_TC_BMR_QDTRANS		BIT(11)
+#define ATMEL_TC_BMR_EDGPHA		BIT(12)
+#define ATMEL_TC_BMR_INVA		BIT(13)
+#define ATMEL_TC_BMR_INVB		BIT(14)
+#define ATMEL_TC_BMR_INVIDX		BIT(15)
+#define ATMEL_TC_BMR_SWAP		BIT(16)
+#define ATMEL_TC_BMR_IDXPHB		BIT(17)
+#define ATMEL_TC_BMR_AUTOC		BIT(18)
+#define ATMEL_TC_MAXFILT_MSK		GENMASK(25, 20)
+#define ATMEL_TC_MAXFILT(x)		(((x) - 1) << 20)
+#define ATMEL_TC_MAXCMP_MSK		GENMASK(29, 26)
+#define ATMEL_TC_MAXCMP(x)		((x) << 26)
+
+/* QEDC fields */
+#define ATMEL_TC_QEDC_IDX		BIT(0)
+#define ATMEL_TC_QEDC_DIRCHG		BIT(1)
+#define ATMEL_TC_QEDC_QERR		BIT(2)
+#define ATMEL_TC_QEDC_MPE		BIT(3)
+#define ATMEL_TC_QEDC_DIR		BIT(8)
+
+/* FMR fields */
+#define ATMEL_TC_FMR_ENCF(x)		BIT(x)
+
+/* WPMR fields */
+#define ATMEL_TC_WPMR_WPKEY		(0x54494d << 8)
+#define ATMEL_TC_WPMR_WPEN		BIT(0)
+
+static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
+{
+	struct clk *clk;
+	char clk_name[] = "t0_clk";
+
+	clk_name[1] += channel;
+	clk = of_clk_get_by_name(node->parent, clk_name);
+	if (!IS_ERR(clk))
+		return clk;
+
+	return of_clk_get_by_name(node->parent, "t0_clk");
+}
+
+static inline int tcb_irq_get(struct device_node *node, int channel)
+{
+	int irq;
+
+	irq = of_irq_get(node->parent, channel);
+	if (irq > 0)
+		return irq;
+
+	return of_irq_get(node->parent, 0);
+}
+
+static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
+
+struct atmel_tcb_info {
+	int bits;
+};
+
+static const struct atmel_tcb_info atmel_tcb_infos[] = {
+	{ .bits = 16 },
+	{ .bits = 32 },
+};
+
+static const struct of_device_id atmel_tcb_dt_ids[] = {
+	{
+		.compatible = "atmel,at91rm9200-tcb",
+		.data = &atmel_tcb_infos[0],
+	}, {
+		.compatible = "atmel,at91sam9x5-tcb",
+		.data = &atmel_tcb_infos[1],
+	}, {
+		/* sentinel */
+	}
+};
+
+#endif /* __SOC_ATMEL_TCB_H */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

Add a driver for the Atmel Timer Counter Blocks. This driver provides a
clocksource and two clockevent devices.

One of the clockevent device is linked to the clocksource counter and so it
will run at the same frequency. This will be used when there is only on TCB
channel available for timers.

The other clockevent device runs on a separate TCB channel when available.

This driver uses regmap and syscon to be able to probe early in the boot
and avoid having to switch on the TCB clocksource later. Using regmap also
means that unused TCB channels may be used by other drivers (PWM for
example). read/writel are still used to access channel specific registers
to avoid the performance impact of regmap (mainly locking).

Tested-by: Alexander Dahl <ada@thorsis.com>
Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 drivers/clocksource/Kconfig           |   8 +
 drivers/clocksource/Makefile          |   3 +-
 drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
 3 files changed, 640 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clocksource/timer-atmel-tcb.c

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index dec0dd88ec15..0b1b0de2abcc 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -403,6 +403,14 @@ config ATMEL_ST
 	help
 	  Support for the Atmel ST timer.
 
+config ATMEL_ARM_TCB_CLKSRC
+	bool "Microchip ARM TC Block" if COMPILE_TEST
+	select REGMAP_MMIO
+	depends on GENERIC_CLOCKEVENTS
+	help
+	  This enables build of clocksource and clockevent driver for
+	  the integrated Timer Counter Blocks in Microchip ARM SoCs.
+
 config CLKSRC_EXYNOS_MCT
 	bool "Exynos multi core timer driver" if COMPILE_TEST
 	depends on ARM || ARM64
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 00caf37e52f9..6991348aa24a 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -3,7 +3,8 @@ obj-$(CONFIG_TIMER_OF)		+= timer-of.o
 obj-$(CONFIG_TIMER_PROBE)	+= timer-probe.o
 obj-$(CONFIG_ATMEL_PIT)		+= timer-atmel-pit.o
 obj-$(CONFIG_ATMEL_ST)		+= timer-atmel-st.o
-obj-$(CONFIG_ATMEL_TCB_CLKSRC)	+= tcb_clksrc.o
+obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o
+obj-$(CONFIG_ATMEL_ARM_TCB_CLKSRC)	+= timer-atmel-tcb.o
 obj-$(CONFIG_X86_PM_TIMER)	+= acpi_pm.o
 obj-$(CONFIG_SCx200HR_TIMER)	+= scx200_hrt.o
 obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
new file mode 100644
index 000000000000..73e6b2783dda
--- /dev/null
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/sched_clock.h>
+#include <soc/at91/atmel_tcb.h>
+
+static struct atmel_tcb_clksrc {
+	struct clocksource clksrc;
+	struct clock_event_device clkevt;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct clk *clk[2];
+	char name[20];
+	int channels[2];
+	int bits;
+	int irq;
+	struct {
+		u32 cmr;
+		u32 imr;
+		u32 rc;
+		bool clken;
+	} cache[2];
+	u32 bmr_cache;
+	bool registered;
+} tc = {
+	.clksrc = {
+		.rating		= 200,
+		.mask		= CLOCKSOURCE_MASK(32),
+		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
+	},
+	.clkevt	= {
+		.features	= CLOCK_EVT_FEAT_ONESHOT,
+		/* Should be lower than at91rm9200's system timer */
+		.rating		= 125,
+	},
+};
+
+static struct tc_clkevt_device {
+	struct clock_event_device clkevt;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct clk *slow_clk;
+	struct clk *clk;
+	char name[20];
+	int channel;
+	int irq;
+	struct {
+		u32 cmr;
+		u32 imr;
+		u32 rc;
+		bool clken;
+	} cache;
+	bool registered;
+	bool clk_enabled;
+} tce = {
+	.clkevt	= {
+		.features		= CLOCK_EVT_FEAT_PERIODIC |
+					  CLOCK_EVT_FEAT_ONESHOT,
+		/*
+		 * Should be lower than at91rm9200's system timer
+		 * but higher than tc.clkevt.rating
+		 */
+		.rating			= 140,
+	},
+};
+
+/*
+ * Clockevent device using its own channel
+ */
+
+static void tc_clkevt2_clk_disable(struct clock_event_device *d)
+{
+	clk_disable(tce.clk);
+	tce.clk_enabled = false;
+}
+
+static void tc_clkevt2_clk_enable(struct clock_event_device *d)
+{
+	if (tce.clk_enabled)
+		return;
+	clk_enable(tce.clk);
+	tce.clk_enabled = true;
+}
+
+static int tc_clkevt2_stop(struct clock_event_device *d)
+{
+	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
+	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_shutdown(struct clock_event_device *d)
+{
+	tc_clkevt2_stop(d);
+	if (!clockevent_state_detached(d))
+		tc_clkevt2_clk_disable(d);
+
+	return 0;
+}
+
+/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
+ * because using one of the divided clocks would usually mean the
+ * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
+ *
+ * A divided clock could be good for high resolution timers, since
+ * 30.5 usec resolution can seem "low".
+ */
+static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
+{
+	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
+		tc_clkevt2_stop(d);
+
+	tc_clkevt2_clk_enable(d);
+
+	/* slow clock, count up to RC, then irq and stop */
+	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_CPCSTOP |
+	       ATMEL_TC_CMR_WAVE | ATMEL_TC_CMR_WAVESEL_UPRC,
+	       tce.base + ATMEL_TC_CMR(tce.channel));
+	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_set_periodic(struct clock_event_device *d)
+{
+	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
+		tc_clkevt2_stop(d);
+
+	/* By not making the gentime core emulate periodic mode on top
+	 * of oneshot, we get lower overhead and improved accuracy.
+	 */
+	tc_clkevt2_clk_enable(d);
+
+	/* slow clock, count up to RC, then irq and restart */
+	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
+	       ATMEL_TC_CMR_WAVESEL_UPRC,
+	       tce.base + ATMEL_TC_CMR(tce.channel));
+	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));
+
+	/* Enable clock and interrupts on RC compare */
+	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
+	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+	       tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_next_event(unsigned long delta,
+				 struct clock_event_device *d)
+{
+	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
+	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+	       tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
+{
+	unsigned int sr;
+
+	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
+	if (sr & ATMEL_TC_CPCS) {
+		tce.clkevt.event_handler(&tce.clkevt);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static void tc_clkevt2_suspend(struct clock_event_device *d)
+{
+	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
+	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
+	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
+	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
+				ATMEL_TC_CLKSTA);
+}
+
+static void tc_clkevt2_resume(struct clock_event_device *d)
+{
+	/* Restore registers for the channel, RA and RB are not used  */
+	writel(tce.cache.cmr, tc.base + ATMEL_TC_CMR(tce.channel));
+	writel(tce.cache.rc, tc.base + ATMEL_TC_RC(tce.channel));
+	writel(0, tc.base + ATMEL_TC_RA(tce.channel));
+	writel(0, tc.base + ATMEL_TC_RB(tce.channel));
+	/* Disable all the interrupts */
+	writel(0xff, tc.base + ATMEL_TC_IDR(tce.channel));
+	/* Reenable interrupts that were enabled before suspending */
+	writel(tce.cache.imr, tc.base + ATMEL_TC_IER(tce.channel));
+
+	/* Start the clock if it was used */
+	if (tce.cache.clken)
+		writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+		       tc.base + ATMEL_TC_CCR(tce.channel));
+}
+
+static int __init tc_clkevt_register(struct device_node *node,
+				     struct regmap *regmap, void __iomem *base,
+				     int channel, int irq, int bits)
+{
+	int ret;
+
+	tce.regmap = regmap;
+	tce.base = base;
+	tce.channel = channel;
+	tce.irq = irq;
+
+	tce.slow_clk = of_clk_get_by_name(node->parent, "slow_clk");
+	if (IS_ERR(tce.slow_clk))
+		return PTR_ERR(tce.slow_clk);
+
+	ret = clk_prepare_enable(tce.slow_clk);
+	if (ret)
+		return ret;
+
+	tce.clk = tcb_clk_get(node, tce.channel);
+	if (IS_ERR(tce.clk)) {
+		ret = PTR_ERR(tce.clk);
+		goto err_slow;
+	}
+
+	snprintf(tce.name, sizeof(tce.name), "%s:%d",
+		 kbasename(node->parent->full_name), channel);
+	tce.clkevt.cpumask = cpumask_of(0);
+	tce.clkevt.name = tce.name;
+	tce.clkevt.set_next_event = tc_clkevt2_next_event,
+	tce.clkevt.set_state_shutdown = tc_clkevt2_shutdown,
+	tce.clkevt.set_state_periodic = tc_clkevt2_set_periodic,
+	tce.clkevt.set_state_oneshot = tc_clkevt2_set_oneshot,
+	tce.clkevt.suspend = tc_clkevt2_suspend,
+	tce.clkevt.resume = tc_clkevt2_resume,
+
+	/* try to enable clk to avoid future errors in mode change */
+	ret = clk_prepare_enable(tce.clk);
+	if (ret)
+		goto err_slow;
+	clk_disable(tce.clk);
+
+	clockevents_config_and_register(&tce.clkevt, 32768, 1, BIT(bits) - 1);
+
+	ret = request_irq(tce.irq, tc_clkevt2_irq, IRQF_TIMER | IRQF_SHARED,
+			  tce.clkevt.name, &tce);
+	if (ret)
+		goto err_clk;
+
+	tce.registered = true;
+
+	return 0;
+
+err_clk:
+	clk_unprepare(tce.clk);
+err_slow:
+	clk_disable_unprepare(tce.slow_clk);
+
+	return ret;
+}
+
+/*
+ * Clocksource and clockevent using the same channel(s)
+ */
+static u64 tc_get_cycles(struct clocksource *cs)
+{
+	u32 lower, upper;
+
+	do {
+		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
+		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
+	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
+
+	return (upper << 16) | lower;
+}
+
+static u64 tc_get_cycles32(struct clocksource *cs)
+{
+	return readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
+}
+
+static u64 notrace tc_sched_clock_read(void)
+{
+	return tc_get_cycles(&tc.clksrc);
+}
+
+static u64 notrace tc_sched_clock_read32(void)
+{
+	return tc_get_cycles32(&tc.clksrc);
+}
+
+static int tcb_clkevt_next_event(unsigned long delta,
+				 struct clock_event_device *d)
+{
+	u32 old, next, cur;
+
+	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
+	next = old + delta;
+	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
+	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
+
+	/* check whether the delta elapsed while setting the register */
+	if ((next < old && cur < old && cur > next) ||
+	    (next > old && (cur < old || cur > next))) {
+		/*
+		 * Clear the CPCS bit in the status register to avoid
+		 * generating a spurious interrupt next time a valid
+		 * timer event is configured.
+		 */
+		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
+		return -ETIME;
+	}
+
+	writel(ATMEL_TC_CPCS, tc.base + ATMEL_TC_IER(tc.channels[0]));
+
+	return 0;
+}
+
+static irqreturn_t tc_clkevt_irq(int irq, void *handle)
+{
+	unsigned int sr;
+
+	sr = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
+	if (sr & ATMEL_TC_CPCS) {
+		tc.clkevt.event_handler(&tc.clkevt);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int tcb_clkevt_oneshot(struct clock_event_device *dev)
+{
+	if (clockevent_state_oneshot(dev))
+		return 0;
+
+	/*
+	 * Because both clockevent devices may share the same IRQ, we don't want
+	 * the less likely one to stay requested
+	 */
+	return request_irq(tc.irq, tc_clkevt_irq, IRQF_TIMER | IRQF_SHARED,
+			   tc.name, &tc);
+}
+
+static int tcb_clkevt_shutdown(struct clock_event_device *dev)
+{
+	writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[0]));
+	if (tc.bits == 16)
+		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[1]));
+
+	if (!clockevent_state_detached(dev))
+		free_irq(tc.irq, &tc);
+
+	return 0;
+}
+
+static void __init tcb_setup_dual_chan(struct atmel_tcb_clksrc *tc,
+				       int mck_divisor_idx)
+{
+	/* first channel: waveform mode, input mclk/8, clock TIOA on overflow */
+	writel(mck_divisor_idx			/* likely divide-by-8 */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP	/* free-run */
+	       | ATMEL_TC_CMR_ACPA(SET)		/* TIOA rises at 0 */
+	       | ATMEL_TC_CMR_ACPC(CLEAR),	/* (duty cycle 50%) */
+	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
+	writel(0x0000, tc->base + ATMEL_TC_RA(tc->channels[0]));
+	writel(0x8000, tc->base + ATMEL_TC_RC(tc->channels[0]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
+
+	/* second channel: waveform mode, input TIOA */
+	writel(ATMEL_TC_CMR_XC(tc->channels[1])		/* input: TIOA */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP,		/* free-run */
+	       tc->base + ATMEL_TC_CMR(tc->channels[1]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[1]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[1]));
+
+	/* chain both channel, we assume the previous channel */
+	regmap_write(tc->regmap, ATMEL_TC_BMR,
+		     ATMEL_TC_BMR_TCXC(1 + tc->channels[1], tc->channels[1]));
+	/* then reset all the timers */
+	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static void __init tcb_setup_single_chan(struct atmel_tcb_clksrc *tc,
+					 int mck_divisor_idx)
+{
+	/* channel 0:  waveform mode, input mclk/8 */
+	writel(mck_divisor_idx			/* likely divide-by-8 */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP,	/* free-run */
+	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
+
+	/* then reset all the timers */
+	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static void tc_clksrc_suspend(struct clocksource *cs)
+{
+	int i;
+
+	for (i = 0; i < 1 + (tc.bits == 16); i++) {
+		tc.cache[i].cmr = readl(tc.base + ATMEL_TC_CMR(tc.channels[i]));
+		tc.cache[i].imr = readl(tc.base + ATMEL_TC_IMR(tc.channels[i]));
+		tc.cache[i].rc = readl(tc.base + ATMEL_TC_RC(tc.channels[i]));
+		tc.cache[i].clken = !!(readl(tc.base +
+					     ATMEL_TC_SR(tc.channels[i])) &
+				       ATMEL_TC_CLKSTA);
+	}
+
+	if (tc.bits == 16)
+		regmap_read(tc.regmap, ATMEL_TC_BMR, &tc.bmr_cache);
+}
+
+static void tc_clksrc_resume(struct clocksource *cs)
+{
+	int i;
+
+	for (i = 0; i < 1 + (tc.bits == 16); i++) {
+		/* Restore registers for the channel, RA and RB are not used  */
+		writel(tc.cache[i].cmr, tc.base + ATMEL_TC_CMR(tc.channels[i]));
+		writel(tc.cache[i].rc, tc.base + ATMEL_TC_RC(tc.channels[i]));
+		writel(0, tc.base + ATMEL_TC_RA(tc.channels[i]));
+		writel(0, tc.base + ATMEL_TC_RB(tc.channels[i]));
+		/* Disable all the interrupts */
+		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[i]));
+		/* Reenable interrupts that were enabled before suspending */
+		writel(tc.cache[i].imr, tc.base + ATMEL_TC_IER(tc.channels[i]));
+
+		/* Start the clock if it was used */
+		if (tc.cache[i].clken)
+			writel(ATMEL_TC_CCR_CLKEN, tc.base +
+			       ATMEL_TC_CCR(tc.channels[i]));
+	}
+
+	/* in case of dual channel, chain channels */
+	if (tc.bits == 16)
+		regmap_write(tc.regmap, ATMEL_TC_BMR, tc.bmr_cache);
+	/* Finally, trigger all the channels*/
+	regmap_write(tc.regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static int __init tcb_clksrc_register(struct device_node *node,
+				      struct regmap *regmap, void __iomem *base,
+				      int channel, int channel1, int irq,
+				      int bits)
+{
+	u32 rate, divided_rate = 0;
+	int best_divisor_idx = -1;
+	int i, err = -1;
+	u64 (*tc_sched_clock)(void);
+
+	tc.regmap = regmap;
+	tc.base = base;
+	tc.channels[0] = channel;
+	tc.channels[1] = channel1;
+	tc.irq = irq;
+	tc.bits = bits;
+
+	tc.clk[0] = tcb_clk_get(node, tc.channels[0]);
+	if (IS_ERR(tc.clk[0]))
+		return PTR_ERR(tc.clk[0]);
+	err = clk_prepare_enable(tc.clk[0]);
+	if (err) {
+		pr_debug("can't enable T0 clk\n");
+		goto err_clk;
+	}
+
+	/* How fast will we be counting?  Pick something over 5 MHz.  */
+	rate = (u32)clk_get_rate(tc.clk[0]);
+	for (i = 0; i < 5; i++) {
+		unsigned int divisor = atmel_tc_divisors[i];
+		unsigned int tmp;
+
+		if (!divisor)
+			continue;
+
+		tmp = rate / divisor;
+		pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
+		if (best_divisor_idx > 0) {
+			if (tmp < 5 * 1000 * 1000)
+				continue;
+		}
+		divided_rate = tmp;
+		best_divisor_idx = i;
+	}
+
+	if (tc.bits == 32) {
+		tc.clksrc.read = tc_get_cycles32;
+		tcb_setup_single_chan(&tc, best_divisor_idx);
+		tc_sched_clock = tc_sched_clock_read32;
+		snprintf(tc.name, sizeof(tc.name), "%s:%d",
+			 kbasename(node->parent->full_name), tc.channels[0]);
+	} else {
+		tc.clk[1] = tcb_clk_get(node, tc.channels[1]);
+		if (IS_ERR(tc.clk[1]))
+			goto err_disable_t0;
+
+		err = clk_prepare_enable(tc.clk[1]);
+		if (err) {
+			pr_debug("can't enable T1 clk\n");
+			goto err_clk1;
+		}
+		tc.clksrc.read = tc_get_cycles,
+		tcb_setup_dual_chan(&tc, best_divisor_idx);
+		tc_sched_clock = tc_sched_clock_read;
+		snprintf(tc.name, sizeof(tc.name), "%s:%d,%d",
+			 kbasename(node->parent->full_name), tc.channels[0],
+			 tc.channels[1]);
+	}
+
+	pr_debug("%s at %d.%03d MHz\n", tc.name,
+		 divided_rate / 1000000,
+		 ((divided_rate + 500000) % 1000000) / 1000);
+
+	tc.clksrc.name = tc.name;
+	tc.clksrc.suspend = tc_clksrc_suspend;
+	tc.clksrc.resume = tc_clksrc_resume;
+
+	err = clocksource_register_hz(&tc.clksrc, divided_rate);
+	if (err)
+		goto err_disable_t1;
+
+	sched_clock_register(tc_sched_clock, 32, divided_rate);
+
+	tc.registered = true;
+
+	/* Set up and register clockevents */
+	tc.clkevt.name = tc.name;
+	tc.clkevt.cpumask = cpumask_of(0);
+	tc.clkevt.set_next_event = tcb_clkevt_next_event;
+	tc.clkevt.set_state_oneshot = tcb_clkevt_oneshot;
+	tc.clkevt.set_state_shutdown = tcb_clkevt_shutdown;
+	clockevents_config_and_register(&tc.clkevt, divided_rate, 1,
+					BIT(tc.bits) - 1);
+
+	return 0;
+
+err_disable_t1:
+	if (tc.bits == 16)
+		clk_disable_unprepare(tc.clk[1]);
+
+err_clk1:
+	if (tc.bits == 16)
+		clk_put(tc.clk[1]);
+
+err_disable_t0:
+	clk_disable_unprepare(tc.clk[0]);
+
+err_clk:
+	clk_put(tc.clk[0]);
+
+	pr_err("%s: unable to register clocksource/clockevent\n",
+	       tc.clksrc.name);
+
+	return err;
+}
+
+static int __init tcb_clksrc_init(struct device_node *node)
+{
+	const struct of_device_id *match;
+	const struct atmel_tcb_info *tcb_info;
+	struct regmap *regmap;
+	void __iomem *tcb_base;
+	u32 channel;
+	int bits, irq, err, chan1 = -1;
+
+	if (tc.registered && tce.registered)
+		return -ENODEV;
+
+	/*
+	 * The regmap has to be used to access registers that are shared
+	 * between channels on the same TCB but we keep direct IO access for
+	 * the counters to avoid the impact on performance
+	 */
+	regmap = syscon_node_to_regmap(node->parent);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	tcb_base = of_iomap(node->parent, 0);
+	if (!tcb_base) {
+		pr_err("%s +%d %s\n", __FILE__, __LINE__, __func__);
+		return -ENXIO;
+	}
+
+	match = of_match_node(atmel_tcb_dt_ids, node->parent);
+	tcb_info = match->data;
+	bits = tcb_info->bits;
+
+	err = of_property_read_u32_index(node, "reg", 0, &channel);
+	if (err)
+		return err;
+
+	irq = tcb_irq_get(node, channel);
+	if (irq < 0)
+		return irq;
+
+	if (tc.registered)
+		return tc_clkevt_register(node, regmap, tcb_base, channel, irq,
+					  bits);
+
+	if (bits == 16) {
+		of_property_read_u32_index(node, "reg", 1, &chan1);
+		if (chan1 == -1) {
+			if (tce.registered) {
+				pr_err("%s: clocksource needs two channels\n",
+				       node->parent->full_name);
+				return -EINVAL;
+			} else {
+				return tc_clkevt_register(node, regmap,
+							  tcb_base, channel,
+							  irq, bits);
+			}
+		}
+	}
+
+	return tcb_clksrc_register(node, regmap, tcb_base, channel, chan1, irq,
+				   bits);
+}
+CLOCKSOURCE_OF_DECLARE(atmel_tcb_clksrc, "atmel,tcb-timer",
+		       tcb_clksrc_init);
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

Add a driver for the Atmel Timer Counter Blocks. This driver provides a
clocksource and two clockevent devices.

One of the clockevent device is linked to the clocksource counter and so it
will run at the same frequency. This will be used when there is only on TCB
channel available for timers.

The other clockevent device runs on a separate TCB channel when available.

This driver uses regmap and syscon to be able to probe early in the boot
and avoid having to switch on the TCB clocksource later. Using regmap also
means that unused TCB channels may be used by other drivers (PWM for
example). read/writel are still used to access channel specific registers
to avoid the performance impact of regmap (mainly locking).

Tested-by: Alexander Dahl <ada@thorsis.com>
Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 drivers/clocksource/Kconfig           |   8 +
 drivers/clocksource/Makefile          |   3 +-
 drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
 3 files changed, 640 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clocksource/timer-atmel-tcb.c

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index dec0dd88ec15..0b1b0de2abcc 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -403,6 +403,14 @@ config ATMEL_ST
 	help
 	  Support for the Atmel ST timer.
 
+config ATMEL_ARM_TCB_CLKSRC
+	bool "Microchip ARM TC Block" if COMPILE_TEST
+	select REGMAP_MMIO
+	depends on GENERIC_CLOCKEVENTS
+	help
+	  This enables build of clocksource and clockevent driver for
+	  the integrated Timer Counter Blocks in Microchip ARM SoCs.
+
 config CLKSRC_EXYNOS_MCT
 	bool "Exynos multi core timer driver" if COMPILE_TEST
 	depends on ARM || ARM64
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 00caf37e52f9..6991348aa24a 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -3,7 +3,8 @@ obj-$(CONFIG_TIMER_OF)		+= timer-of.o
 obj-$(CONFIG_TIMER_PROBE)	+= timer-probe.o
 obj-$(CONFIG_ATMEL_PIT)		+= timer-atmel-pit.o
 obj-$(CONFIG_ATMEL_ST)		+= timer-atmel-st.o
-obj-$(CONFIG_ATMEL_TCB_CLKSRC)	+= tcb_clksrc.o
+obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o
+obj-$(CONFIG_ATMEL_ARM_TCB_CLKSRC)	+= timer-atmel-tcb.o
 obj-$(CONFIG_X86_PM_TIMER)	+= acpi_pm.o
 obj-$(CONFIG_SCx200HR_TIMER)	+= scx200_hrt.o
 obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
new file mode 100644
index 000000000000..73e6b2783dda
--- /dev/null
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/sched_clock.h>
+#include <soc/at91/atmel_tcb.h>
+
+static struct atmel_tcb_clksrc {
+	struct clocksource clksrc;
+	struct clock_event_device clkevt;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct clk *clk[2];
+	char name[20];
+	int channels[2];
+	int bits;
+	int irq;
+	struct {
+		u32 cmr;
+		u32 imr;
+		u32 rc;
+		bool clken;
+	} cache[2];
+	u32 bmr_cache;
+	bool registered;
+} tc = {
+	.clksrc = {
+		.rating		= 200,
+		.mask		= CLOCKSOURCE_MASK(32),
+		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
+	},
+	.clkevt	= {
+		.features	= CLOCK_EVT_FEAT_ONESHOT,
+		/* Should be lower than at91rm9200's system timer */
+		.rating		= 125,
+	},
+};
+
+static struct tc_clkevt_device {
+	struct clock_event_device clkevt;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct clk *slow_clk;
+	struct clk *clk;
+	char name[20];
+	int channel;
+	int irq;
+	struct {
+		u32 cmr;
+		u32 imr;
+		u32 rc;
+		bool clken;
+	} cache;
+	bool registered;
+	bool clk_enabled;
+} tce = {
+	.clkevt	= {
+		.features		= CLOCK_EVT_FEAT_PERIODIC |
+					  CLOCK_EVT_FEAT_ONESHOT,
+		/*
+		 * Should be lower than at91rm9200's system timer
+		 * but higher than tc.clkevt.rating
+		 */
+		.rating			= 140,
+	},
+};
+
+/*
+ * Clockevent device using its own channel
+ */
+
+static void tc_clkevt2_clk_disable(struct clock_event_device *d)
+{
+	clk_disable(tce.clk);
+	tce.clk_enabled = false;
+}
+
+static void tc_clkevt2_clk_enable(struct clock_event_device *d)
+{
+	if (tce.clk_enabled)
+		return;
+	clk_enable(tce.clk);
+	tce.clk_enabled = true;
+}
+
+static int tc_clkevt2_stop(struct clock_event_device *d)
+{
+	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
+	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_shutdown(struct clock_event_device *d)
+{
+	tc_clkevt2_stop(d);
+	if (!clockevent_state_detached(d))
+		tc_clkevt2_clk_disable(d);
+
+	return 0;
+}
+
+/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
+ * because using one of the divided clocks would usually mean the
+ * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
+ *
+ * A divided clock could be good for high resolution timers, since
+ * 30.5 usec resolution can seem "low".
+ */
+static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
+{
+	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
+		tc_clkevt2_stop(d);
+
+	tc_clkevt2_clk_enable(d);
+
+	/* slow clock, count up to RC, then irq and stop */
+	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_CPCSTOP |
+	       ATMEL_TC_CMR_WAVE | ATMEL_TC_CMR_WAVESEL_UPRC,
+	       tce.base + ATMEL_TC_CMR(tce.channel));
+	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_set_periodic(struct clock_event_device *d)
+{
+	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
+		tc_clkevt2_stop(d);
+
+	/* By not making the gentime core emulate periodic mode on top
+	 * of oneshot, we get lower overhead and improved accuracy.
+	 */
+	tc_clkevt2_clk_enable(d);
+
+	/* slow clock, count up to RC, then irq and restart */
+	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
+	       ATMEL_TC_CMR_WAVESEL_UPRC,
+	       tce.base + ATMEL_TC_CMR(tce.channel));
+	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));
+
+	/* Enable clock and interrupts on RC compare */
+	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
+	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+	       tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static int tc_clkevt2_next_event(unsigned long delta,
+				 struct clock_event_device *d)
+{
+	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
+	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+	       tce.base + ATMEL_TC_CCR(tce.channel));
+
+	return 0;
+}
+
+static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
+{
+	unsigned int sr;
+
+	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
+	if (sr & ATMEL_TC_CPCS) {
+		tce.clkevt.event_handler(&tce.clkevt);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static void tc_clkevt2_suspend(struct clock_event_device *d)
+{
+	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
+	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
+	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
+	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
+				ATMEL_TC_CLKSTA);
+}
+
+static void tc_clkevt2_resume(struct clock_event_device *d)
+{
+	/* Restore registers for the channel, RA and RB are not used  */
+	writel(tce.cache.cmr, tc.base + ATMEL_TC_CMR(tce.channel));
+	writel(tce.cache.rc, tc.base + ATMEL_TC_RC(tce.channel));
+	writel(0, tc.base + ATMEL_TC_RA(tce.channel));
+	writel(0, tc.base + ATMEL_TC_RB(tce.channel));
+	/* Disable all the interrupts */
+	writel(0xff, tc.base + ATMEL_TC_IDR(tce.channel));
+	/* Reenable interrupts that were enabled before suspending */
+	writel(tce.cache.imr, tc.base + ATMEL_TC_IER(tce.channel));
+
+	/* Start the clock if it was used */
+	if (tce.cache.clken)
+		writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
+		       tc.base + ATMEL_TC_CCR(tce.channel));
+}
+
+static int __init tc_clkevt_register(struct device_node *node,
+				     struct regmap *regmap, void __iomem *base,
+				     int channel, int irq, int bits)
+{
+	int ret;
+
+	tce.regmap = regmap;
+	tce.base = base;
+	tce.channel = channel;
+	tce.irq = irq;
+
+	tce.slow_clk = of_clk_get_by_name(node->parent, "slow_clk");
+	if (IS_ERR(tce.slow_clk))
+		return PTR_ERR(tce.slow_clk);
+
+	ret = clk_prepare_enable(tce.slow_clk);
+	if (ret)
+		return ret;
+
+	tce.clk = tcb_clk_get(node, tce.channel);
+	if (IS_ERR(tce.clk)) {
+		ret = PTR_ERR(tce.clk);
+		goto err_slow;
+	}
+
+	snprintf(tce.name, sizeof(tce.name), "%s:%d",
+		 kbasename(node->parent->full_name), channel);
+	tce.clkevt.cpumask = cpumask_of(0);
+	tce.clkevt.name = tce.name;
+	tce.clkevt.set_next_event = tc_clkevt2_next_event,
+	tce.clkevt.set_state_shutdown = tc_clkevt2_shutdown,
+	tce.clkevt.set_state_periodic = tc_clkevt2_set_periodic,
+	tce.clkevt.set_state_oneshot = tc_clkevt2_set_oneshot,
+	tce.clkevt.suspend = tc_clkevt2_suspend,
+	tce.clkevt.resume = tc_clkevt2_resume,
+
+	/* try to enable clk to avoid future errors in mode change */
+	ret = clk_prepare_enable(tce.clk);
+	if (ret)
+		goto err_slow;
+	clk_disable(tce.clk);
+
+	clockevents_config_and_register(&tce.clkevt, 32768, 1, BIT(bits) - 1);
+
+	ret = request_irq(tce.irq, tc_clkevt2_irq, IRQF_TIMER | IRQF_SHARED,
+			  tce.clkevt.name, &tce);
+	if (ret)
+		goto err_clk;
+
+	tce.registered = true;
+
+	return 0;
+
+err_clk:
+	clk_unprepare(tce.clk);
+err_slow:
+	clk_disable_unprepare(tce.slow_clk);
+
+	return ret;
+}
+
+/*
+ * Clocksource and clockevent using the same channel(s)
+ */
+static u64 tc_get_cycles(struct clocksource *cs)
+{
+	u32 lower, upper;
+
+	do {
+		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
+		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
+	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
+
+	return (upper << 16) | lower;
+}
+
+static u64 tc_get_cycles32(struct clocksource *cs)
+{
+	return readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
+}
+
+static u64 notrace tc_sched_clock_read(void)
+{
+	return tc_get_cycles(&tc.clksrc);
+}
+
+static u64 notrace tc_sched_clock_read32(void)
+{
+	return tc_get_cycles32(&tc.clksrc);
+}
+
+static int tcb_clkevt_next_event(unsigned long delta,
+				 struct clock_event_device *d)
+{
+	u32 old, next, cur;
+
+	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
+	next = old + delta;
+	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
+	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
+
+	/* check whether the delta elapsed while setting the register */
+	if ((next < old && cur < old && cur > next) ||
+	    (next > old && (cur < old || cur > next))) {
+		/*
+		 * Clear the CPCS bit in the status register to avoid
+		 * generating a spurious interrupt next time a valid
+		 * timer event is configured.
+		 */
+		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
+		return -ETIME;
+	}
+
+	writel(ATMEL_TC_CPCS, tc.base + ATMEL_TC_IER(tc.channels[0]));
+
+	return 0;
+}
+
+static irqreturn_t tc_clkevt_irq(int irq, void *handle)
+{
+	unsigned int sr;
+
+	sr = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
+	if (sr & ATMEL_TC_CPCS) {
+		tc.clkevt.event_handler(&tc.clkevt);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int tcb_clkevt_oneshot(struct clock_event_device *dev)
+{
+	if (clockevent_state_oneshot(dev))
+		return 0;
+
+	/*
+	 * Because both clockevent devices may share the same IRQ, we don't want
+	 * the less likely one to stay requested
+	 */
+	return request_irq(tc.irq, tc_clkevt_irq, IRQF_TIMER | IRQF_SHARED,
+			   tc.name, &tc);
+}
+
+static int tcb_clkevt_shutdown(struct clock_event_device *dev)
+{
+	writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[0]));
+	if (tc.bits == 16)
+		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[1]));
+
+	if (!clockevent_state_detached(dev))
+		free_irq(tc.irq, &tc);
+
+	return 0;
+}
+
+static void __init tcb_setup_dual_chan(struct atmel_tcb_clksrc *tc,
+				       int mck_divisor_idx)
+{
+	/* first channel: waveform mode, input mclk/8, clock TIOA on overflow */
+	writel(mck_divisor_idx			/* likely divide-by-8 */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP	/* free-run */
+	       | ATMEL_TC_CMR_ACPA(SET)		/* TIOA rises at 0 */
+	       | ATMEL_TC_CMR_ACPC(CLEAR),	/* (duty cycle 50%) */
+	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
+	writel(0x0000, tc->base + ATMEL_TC_RA(tc->channels[0]));
+	writel(0x8000, tc->base + ATMEL_TC_RC(tc->channels[0]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
+
+	/* second channel: waveform mode, input TIOA */
+	writel(ATMEL_TC_CMR_XC(tc->channels[1])		/* input: TIOA */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP,		/* free-run */
+	       tc->base + ATMEL_TC_CMR(tc->channels[1]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[1]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[1]));
+
+	/* chain both channel, we assume the previous channel */
+	regmap_write(tc->regmap, ATMEL_TC_BMR,
+		     ATMEL_TC_BMR_TCXC(1 + tc->channels[1], tc->channels[1]));
+	/* then reset all the timers */
+	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static void __init tcb_setup_single_chan(struct atmel_tcb_clksrc *tc,
+					 int mck_divisor_idx)
+{
+	/* channel 0:  waveform mode, input mclk/8 */
+	writel(mck_divisor_idx			/* likely divide-by-8 */
+	       | ATMEL_TC_CMR_WAVE
+	       | ATMEL_TC_CMR_WAVESEL_UP,	/* free-run */
+	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
+	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
+	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
+
+	/* then reset all the timers */
+	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static void tc_clksrc_suspend(struct clocksource *cs)
+{
+	int i;
+
+	for (i = 0; i < 1 + (tc.bits == 16); i++) {
+		tc.cache[i].cmr = readl(tc.base + ATMEL_TC_CMR(tc.channels[i]));
+		tc.cache[i].imr = readl(tc.base + ATMEL_TC_IMR(tc.channels[i]));
+		tc.cache[i].rc = readl(tc.base + ATMEL_TC_RC(tc.channels[i]));
+		tc.cache[i].clken = !!(readl(tc.base +
+					     ATMEL_TC_SR(tc.channels[i])) &
+				       ATMEL_TC_CLKSTA);
+	}
+
+	if (tc.bits == 16)
+		regmap_read(tc.regmap, ATMEL_TC_BMR, &tc.bmr_cache);
+}
+
+static void tc_clksrc_resume(struct clocksource *cs)
+{
+	int i;
+
+	for (i = 0; i < 1 + (tc.bits == 16); i++) {
+		/* Restore registers for the channel, RA and RB are not used  */
+		writel(tc.cache[i].cmr, tc.base + ATMEL_TC_CMR(tc.channels[i]));
+		writel(tc.cache[i].rc, tc.base + ATMEL_TC_RC(tc.channels[i]));
+		writel(0, tc.base + ATMEL_TC_RA(tc.channels[i]));
+		writel(0, tc.base + ATMEL_TC_RB(tc.channels[i]));
+		/* Disable all the interrupts */
+		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[i]));
+		/* Reenable interrupts that were enabled before suspending */
+		writel(tc.cache[i].imr, tc.base + ATMEL_TC_IER(tc.channels[i]));
+
+		/* Start the clock if it was used */
+		if (tc.cache[i].clken)
+			writel(ATMEL_TC_CCR_CLKEN, tc.base +
+			       ATMEL_TC_CCR(tc.channels[i]));
+	}
+
+	/* in case of dual channel, chain channels */
+	if (tc.bits == 16)
+		regmap_write(tc.regmap, ATMEL_TC_BMR, tc.bmr_cache);
+	/* Finally, trigger all the channels*/
+	regmap_write(tc.regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
+}
+
+static int __init tcb_clksrc_register(struct device_node *node,
+				      struct regmap *regmap, void __iomem *base,
+				      int channel, int channel1, int irq,
+				      int bits)
+{
+	u32 rate, divided_rate = 0;
+	int best_divisor_idx = -1;
+	int i, err = -1;
+	u64 (*tc_sched_clock)(void);
+
+	tc.regmap = regmap;
+	tc.base = base;
+	tc.channels[0] = channel;
+	tc.channels[1] = channel1;
+	tc.irq = irq;
+	tc.bits = bits;
+
+	tc.clk[0] = tcb_clk_get(node, tc.channels[0]);
+	if (IS_ERR(tc.clk[0]))
+		return PTR_ERR(tc.clk[0]);
+	err = clk_prepare_enable(tc.clk[0]);
+	if (err) {
+		pr_debug("can't enable T0 clk\n");
+		goto err_clk;
+	}
+
+	/* How fast will we be counting?  Pick something over 5 MHz.  */
+	rate = (u32)clk_get_rate(tc.clk[0]);
+	for (i = 0; i < 5; i++) {
+		unsigned int divisor = atmel_tc_divisors[i];
+		unsigned int tmp;
+
+		if (!divisor)
+			continue;
+
+		tmp = rate / divisor;
+		pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
+		if (best_divisor_idx > 0) {
+			if (tmp < 5 * 1000 * 1000)
+				continue;
+		}
+		divided_rate = tmp;
+		best_divisor_idx = i;
+	}
+
+	if (tc.bits == 32) {
+		tc.clksrc.read = tc_get_cycles32;
+		tcb_setup_single_chan(&tc, best_divisor_idx);
+		tc_sched_clock = tc_sched_clock_read32;
+		snprintf(tc.name, sizeof(tc.name), "%s:%d",
+			 kbasename(node->parent->full_name), tc.channels[0]);
+	} else {
+		tc.clk[1] = tcb_clk_get(node, tc.channels[1]);
+		if (IS_ERR(tc.clk[1]))
+			goto err_disable_t0;
+
+		err = clk_prepare_enable(tc.clk[1]);
+		if (err) {
+			pr_debug("can't enable T1 clk\n");
+			goto err_clk1;
+		}
+		tc.clksrc.read = tc_get_cycles,
+		tcb_setup_dual_chan(&tc, best_divisor_idx);
+		tc_sched_clock = tc_sched_clock_read;
+		snprintf(tc.name, sizeof(tc.name), "%s:%d,%d",
+			 kbasename(node->parent->full_name), tc.channels[0],
+			 tc.channels[1]);
+	}
+
+	pr_debug("%s at %d.%03d MHz\n", tc.name,
+		 divided_rate / 1000000,
+		 ((divided_rate + 500000) % 1000000) / 1000);
+
+	tc.clksrc.name = tc.name;
+	tc.clksrc.suspend = tc_clksrc_suspend;
+	tc.clksrc.resume = tc_clksrc_resume;
+
+	err = clocksource_register_hz(&tc.clksrc, divided_rate);
+	if (err)
+		goto err_disable_t1;
+
+	sched_clock_register(tc_sched_clock, 32, divided_rate);
+
+	tc.registered = true;
+
+	/* Set up and register clockevents */
+	tc.clkevt.name = tc.name;
+	tc.clkevt.cpumask = cpumask_of(0);
+	tc.clkevt.set_next_event = tcb_clkevt_next_event;
+	tc.clkevt.set_state_oneshot = tcb_clkevt_oneshot;
+	tc.clkevt.set_state_shutdown = tcb_clkevt_shutdown;
+	clockevents_config_and_register(&tc.clkevt, divided_rate, 1,
+					BIT(tc.bits) - 1);
+
+	return 0;
+
+err_disable_t1:
+	if (tc.bits == 16)
+		clk_disable_unprepare(tc.clk[1]);
+
+err_clk1:
+	if (tc.bits == 16)
+		clk_put(tc.clk[1]);
+
+err_disable_t0:
+	clk_disable_unprepare(tc.clk[0]);
+
+err_clk:
+	clk_put(tc.clk[0]);
+
+	pr_err("%s: unable to register clocksource/clockevent\n",
+	       tc.clksrc.name);
+
+	return err;
+}
+
+static int __init tcb_clksrc_init(struct device_node *node)
+{
+	const struct of_device_id *match;
+	const struct atmel_tcb_info *tcb_info;
+	struct regmap *regmap;
+	void __iomem *tcb_base;
+	u32 channel;
+	int bits, irq, err, chan1 = -1;
+
+	if (tc.registered && tce.registered)
+		return -ENODEV;
+
+	/*
+	 * The regmap has to be used to access registers that are shared
+	 * between channels on the same TCB but we keep direct IO access for
+	 * the counters to avoid the impact on performance
+	 */
+	regmap = syscon_node_to_regmap(node->parent);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	tcb_base = of_iomap(node->parent, 0);
+	if (!tcb_base) {
+		pr_err("%s +%d %s\n", __FILE__, __LINE__, __func__);
+		return -ENXIO;
+	}
+
+	match = of_match_node(atmel_tcb_dt_ids, node->parent);
+	tcb_info = match->data;
+	bits = tcb_info->bits;
+
+	err = of_property_read_u32_index(node, "reg", 0, &channel);
+	if (err)
+		return err;
+
+	irq = tcb_irq_get(node, channel);
+	if (irq < 0)
+		return irq;
+
+	if (tc.registered)
+		return tc_clkevt_register(node, regmap, tcb_base, channel, irq,
+					  bits);
+
+	if (bits == 16) {
+		of_property_read_u32_index(node, "reg", 1, &chan1);
+		if (chan1 == -1) {
+			if (tce.registered) {
+				pr_err("%s: clocksource needs two channels\n",
+				       node->parent->full_name);
+				return -EINVAL;
+			} else {
+				return tc_clkevt_register(node, regmap,
+							  tcb_base, channel,
+							  irq, bits);
+			}
+		}
+	}
+
+	return tcb_clksrc_register(node, regmap, tcb_base, channel, chan1, irq,
+				   bits);
+}
+CLOCKSOURCE_OF_DECLARE(atmel_tcb_clksrc, "atmel,tcb-timer",
+		       tcb_clksrc_init);
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 3/6] clocksource/drivers: atmel-pit: make option silent
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

To conform with the other option, make the ATMEL_PIT option silent so it
can be selected from the platform

Tested-by: Alexander Dahl <ada@thorsis.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 drivers/clocksource/Kconfig | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 0b1b0de2abcc..334591ff608e 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -392,8 +392,11 @@ config ARMV7M_SYSTICK
 	  This options enables support for the ARMv7M system timer unit
 
 config ATMEL_PIT
+	bool "Microchip ARM Periodic Interval Timer (PIT)" if COMPILE_TEST
 	select TIMER_OF if OF
-	def_bool SOC_AT91SAM9 || SOC_SAMA5
+	help
+	  This enables build of clocksource and clockevent driver for
+	  the integrated PIT in Microchip ARM SoCs.
 
 config ATMEL_ST
 	bool "Atmel ST timer support" if COMPILE_TEST
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 3/6] clocksource/drivers: atmel-pit: make option silent
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

To conform with the other option, make the ATMEL_PIT option silent so it
can be selected from the platform

Tested-by: Alexander Dahl <ada@thorsis.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 drivers/clocksource/Kconfig | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 0b1b0de2abcc..334591ff608e 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -392,8 +392,11 @@ config ARMV7M_SYSTICK
 	  This options enables support for the ARMv7M system timer unit
 
 config ATMEL_PIT
+	bool "Microchip ARM Periodic Interval Timer (PIT)" if COMPILE_TEST
 	select TIMER_OF if OF
-	def_bool SOC_AT91SAM9 || SOC_SAMA5
+	help
+	  This enables build of clocksource and clockevent driver for
+	  the integrated PIT in Microchip ARM SoCs.
 
 config ATMEL_ST
 	bool "Atmel ST timer support" if COMPILE_TEST
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 4/6] ARM: at91: Implement clocksource selection
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

Allow selecting and unselecting the PIT clocksource driver so it doesn't
have to be compile when unused.

Tested-by: Alexander Dahl <ada@thorsis.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/mach-at91/Kconfig | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 1254bf9d91b4..64f18cd220b9 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -106,6 +106,31 @@ config SOC_AT91SAM9
 	    AT91SAM9X35
 	    AT91SAM9XE
 
+comment "Clocksource driver selection"
+
+config ATMEL_CLOCKSOURCE_PIT
+	bool "Periodic Interval Timer (PIT) support"
+	depends on SOC_AT91SAM9 || SOC_SAMA5
+	default SOC_AT91SAM9 || SOC_SAMA5
+	select ATMEL_PIT
+	help
+	  Select this to get a clocksource based on the Atmel Periodic Interval
+	  Timer. It has a relatively low resolution and the TC Block clocksource
+	  should be preferred.
+
+config ATMEL_CLOCKSOURCE_TCB
+	bool "Timer Counter Blocks (TCB) support"
+	depends on SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5 || COMPILE_TEST
+	default SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5
+	depends on !ATMEL_TCLIB
+	select ATMEL_ARM_TCB_CLKSRC
+	help
+	  Select this to get a high precision clocksource based on a
+	  TC block with a 5+ MHz base clock rate.
+	  On platforms with 16-bit counters, two timer channels are combined
+	  to make a single 32-bit timer.
+	  It can also be used as a clock event device supporting oneshot mode.
+
 config HAVE_AT91_UTMI
 	bool
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 4/6] ARM: at91: Implement clocksource selection
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

Allow selecting and unselecting the PIT clocksource driver so it doesn't
have to be compile when unused.

Tested-by: Alexander Dahl <ada@thorsis.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/mach-at91/Kconfig | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index 1254bf9d91b4..64f18cd220b9 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -106,6 +106,31 @@ config SOC_AT91SAM9
 	    AT91SAM9X35
 	    AT91SAM9XE
 
+comment "Clocksource driver selection"
+
+config ATMEL_CLOCKSOURCE_PIT
+	bool "Periodic Interval Timer (PIT) support"
+	depends on SOC_AT91SAM9 || SOC_SAMA5
+	default SOC_AT91SAM9 || SOC_SAMA5
+	select ATMEL_PIT
+	help
+	  Select this to get a clocksource based on the Atmel Periodic Interval
+	  Timer. It has a relatively low resolution and the TC Block clocksource
+	  should be preferred.
+
+config ATMEL_CLOCKSOURCE_TCB
+	bool "Timer Counter Blocks (TCB) support"
+	depends on SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5 || COMPILE_TEST
+	default SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5
+	depends on !ATMEL_TCLIB
+	select ATMEL_ARM_TCB_CLKSRC
+	help
+	  Select this to get a high precision clocksource based on a
+	  TC block with a 5+ MHz base clock rate.
+	  On platforms with 16-bit counters, two timer channels are combined
+	  to make a single 32-bit timer.
+	  It can also be used as a clock event device supporting oneshot mode.
+
 config HAVE_AT91_UTMI
 	bool
 
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 5/6] ARM: configs: at91: use new TCB timer driver
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

Unselecting ATMEL_TCLIB switches the TCB timer driver from tcb_clksrc to
timer-atmel-tcb.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/configs/at91_dt_defconfig | 1 -
 arch/arm/configs/sama5_defconfig   | 1 -
 2 files changed, 2 deletions(-)

diff --git a/arch/arm/configs/at91_dt_defconfig b/arch/arm/configs/at91_dt_defconfig
index e4b1be66b3f5..09f262e59fef 100644
--- a/arch/arm/configs/at91_dt_defconfig
+++ b/arch/arm/configs/at91_dt_defconfig
@@ -64,7 +64,6 @@ CONFIG_BLK_DEV_LOOP=y
 CONFIG_BLK_DEV_RAM=y
 CONFIG_BLK_DEV_RAM_COUNT=4
 CONFIG_BLK_DEV_RAM_SIZE=8192
-CONFIG_ATMEL_TCLIB=y
 CONFIG_ATMEL_SSC=y
 CONFIG_SCSI=y
 CONFIG_BLK_DEV_SD=y
diff --git a/arch/arm/configs/sama5_defconfig b/arch/arm/configs/sama5_defconfig
index 2080025556b5..f2bbc6339ca6 100644
--- a/arch/arm/configs/sama5_defconfig
+++ b/arch/arm/configs/sama5_defconfig
@@ -75,7 +75,6 @@ CONFIG_BLK_DEV_LOOP=y
 CONFIG_BLK_DEV_RAM=y
 CONFIG_BLK_DEV_RAM_COUNT=4
 CONFIG_BLK_DEV_RAM_SIZE=8192
-CONFIG_ATMEL_TCLIB=y
 CONFIG_ATMEL_SSC=y
 CONFIG_EEPROM_AT24=y
 CONFIG_SCSI=y
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 5/6] ARM: configs: at91: use new TCB timer driver
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

Unselecting ATMEL_TCLIB switches the TCB timer driver from tcb_clksrc to
timer-atmel-tcb.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/configs/at91_dt_defconfig | 1 -
 arch/arm/configs/sama5_defconfig   | 1 -
 2 files changed, 2 deletions(-)

diff --git a/arch/arm/configs/at91_dt_defconfig b/arch/arm/configs/at91_dt_defconfig
index e4b1be66b3f5..09f262e59fef 100644
--- a/arch/arm/configs/at91_dt_defconfig
+++ b/arch/arm/configs/at91_dt_defconfig
@@ -64,7 +64,6 @@ CONFIG_BLK_DEV_LOOP=y
 CONFIG_BLK_DEV_RAM=y
 CONFIG_BLK_DEV_RAM_COUNT=4
 CONFIG_BLK_DEV_RAM_SIZE=8192
-CONFIG_ATMEL_TCLIB=y
 CONFIG_ATMEL_SSC=y
 CONFIG_SCSI=y
 CONFIG_BLK_DEV_SD=y
diff --git a/arch/arm/configs/sama5_defconfig b/arch/arm/configs/sama5_defconfig
index 2080025556b5..f2bbc6339ca6 100644
--- a/arch/arm/configs/sama5_defconfig
+++ b/arch/arm/configs/sama5_defconfig
@@ -75,7 +75,6 @@ CONFIG_BLK_DEV_LOOP=y
 CONFIG_BLK_DEV_RAM=y
 CONFIG_BLK_DEV_RAM_COUNT=4
 CONFIG_BLK_DEV_RAM_SIZE=8192
-CONFIG_ATMEL_TCLIB=y
 CONFIG_ATMEL_SSC=y
 CONFIG_EEPROM_AT24=y
 CONFIG_SCSI=y
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 6/6] ARM: configs: at91: unselect PIT
  2018-06-19 21:19 ` Alexandre Belloni
@ 2018-06-19 21:19   ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel,
	Alexandre Belloni

The PIT is not required anymore to successfully boot and may actually harm
in case preempt-rt is used because the PIT interrupt is shared.
Disable it so the TCB clocksource is used.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/configs/at91_dt_defconfig | 1 +
 arch/arm/configs/sama5_defconfig   | 1 +
 2 files changed, 2 insertions(+)

diff --git a/arch/arm/configs/at91_dt_defconfig b/arch/arm/configs/at91_dt_defconfig
index 09f262e59fef..f4b253bd05ed 100644
--- a/arch/arm/configs/at91_dt_defconfig
+++ b/arch/arm/configs/at91_dt_defconfig
@@ -19,6 +19,7 @@ CONFIG_ARCH_MULTI_V5=y
 CONFIG_ARCH_AT91=y
 CONFIG_SOC_AT91RM9200=y
 CONFIG_SOC_AT91SAM9=y
+# CONFIG_ATMEL_CLOCKSOURCE_PIT is not set
 CONFIG_AEABI=y
 CONFIG_UACCESS_WITH_MEMCPY=y
 CONFIG_ZBOOT_ROM_TEXT=0x0
diff --git a/arch/arm/configs/sama5_defconfig b/arch/arm/configs/sama5_defconfig
index f2bbc6339ca6..be92871ab155 100644
--- a/arch/arm/configs/sama5_defconfig
+++ b/arch/arm/configs/sama5_defconfig
@@ -20,6 +20,7 @@ CONFIG_ARCH_AT91=y
 CONFIG_SOC_SAMA5D2=y
 CONFIG_SOC_SAMA5D3=y
 CONFIG_SOC_SAMA5D4=y
+# CONFIG_ATMEL_CLOCKSOURCE_PIT is not set
 CONFIG_AEABI=y
 CONFIG_UACCESS_WITH_MEMCPY=y
 CONFIG_ZBOOT_ROM_TEXT=0x0
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH v5 6/6] ARM: configs: at91: unselect PIT
@ 2018-06-19 21:19   ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-19 21:19 UTC (permalink / raw)
  To: linux-arm-kernel

The PIT is not required anymore to successfully boot and may actually harm
in case preempt-rt is used because the PIT interrupt is shared.
Disable it so the TCB clocksource is used.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
 arch/arm/configs/at91_dt_defconfig | 1 +
 arch/arm/configs/sama5_defconfig   | 1 +
 2 files changed, 2 insertions(+)

diff --git a/arch/arm/configs/at91_dt_defconfig b/arch/arm/configs/at91_dt_defconfig
index 09f262e59fef..f4b253bd05ed 100644
--- a/arch/arm/configs/at91_dt_defconfig
+++ b/arch/arm/configs/at91_dt_defconfig
@@ -19,6 +19,7 @@ CONFIG_ARCH_MULTI_V5=y
 CONFIG_ARCH_AT91=y
 CONFIG_SOC_AT91RM9200=y
 CONFIG_SOC_AT91SAM9=y
+# CONFIG_ATMEL_CLOCKSOURCE_PIT is not set
 CONFIG_AEABI=y
 CONFIG_UACCESS_WITH_MEMCPY=y
 CONFIG_ZBOOT_ROM_TEXT=0x0
diff --git a/arch/arm/configs/sama5_defconfig b/arch/arm/configs/sama5_defconfig
index f2bbc6339ca6..be92871ab155 100644
--- a/arch/arm/configs/sama5_defconfig
+++ b/arch/arm/configs/sama5_defconfig
@@ -20,6 +20,7 @@ CONFIG_ARCH_AT91=y
 CONFIG_SOC_SAMA5D2=y
 CONFIG_SOC_SAMA5D3=y
 CONFIG_SOC_SAMA5D4=y
+# CONFIG_ATMEL_CLOCKSOURCE_PIT is not set
 CONFIG_AEABI=y
 CONFIG_UACCESS_WITH_MEMCPY=y
 CONFIG_ZBOOT_ROM_TEXT=0x0
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-19 21:19   ` Alexandre Belloni
@ 2018-06-20  9:03     ` Thomas Gleixner
  -1 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20  9:03 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On Tue, 19 Jun 2018, Alexandre Belloni wrote:
> +
> +static struct atmel_tcb_clksrc {
> +	struct clocksource clksrc;
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *clk[2];
> +	char name[20];
> +	int channels[2];
> +	int bits;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache[2];
> +	u32 bmr_cache;
> +	bool registered;
> +} tc = {
> +	.clksrc = {
> +		.rating		= 200,
> +		.mask		= CLOCKSOURCE_MASK(32),
> +		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
> +	},
> +	.clkevt	= {
> +		.features	= CLOCK_EVT_FEAT_ONESHOT,
> +		/* Should be lower than at91rm9200's system timer */
> +		.rating		= 125,
> +	},
> +};
> +
> +static struct tc_clkevt_device {
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *slow_clk;
> +	struct clk *clk;
> +	char name[20];
> +	int channel;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache;
> +	bool registered;
> +	bool clk_enabled;
> +} tce = {
> +	.clkevt	= {
> +		.features		= CLOCK_EVT_FEAT_PERIODIC |
> +					  CLOCK_EVT_FEAT_ONESHOT,
> +		/*
> +		 * Should be lower than at91rm9200's system timer
> +		 * but higher than tc.clkevt.rating
> +		 */
> +		.rating			= 140,
> +	},
> +};

To be honest: This stuff is horrible to read and the two structs are almost
identical. There is really no good reason to have all this duplicated
mess. I'm pretty sure you can consolidate stuff further when using _one_.

And please define the structs separate from the define and arrange the
struct members in tabluar fashion.

> +/*
> + * Clocksource and clockevent using the same channel(s)
> + */
> +static u64 tc_get_cycles(struct clocksource *cs)
> +{
> +	u32 lower, upper;
> +
> +	do {
> +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> +
> +	return (upper << 16) | lower;
> +}

For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
part wraps around in ~859 seconds, which is plenty even for NOHZ.

So I really would avoid the double read/compare/eventually repeat magic and
just use the lower 32bits for performance sake. I assume the same is true
for sched_clock(), but I might be wrong.

> +static int tcb_clkevt_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	u32 old, next, cur;
> +
> +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	next = old + delta;
> +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +
> +	/* check whether the delta elapsed while setting the register */
> +	if ((next < old && cur < old && cur > next) ||
> +	    (next > old && (cur < old || cur > next))) {
> +		/*
> +		 * Clear the CPCS bit in the status register to avoid
> +		 * generating a spurious interrupt next time a valid
> +		 * timer event is configured.
> +		 */
> +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +		return -ETIME;
> +	}

Aarg. Doesn;t that timer block have a simple count down and fire mode?
These compare equal timers suck.

Thanks,

	tglx



^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20  9:03     ` Thomas Gleixner
  0 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20  9:03 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, 19 Jun 2018, Alexandre Belloni wrote:
> +
> +static struct atmel_tcb_clksrc {
> +	struct clocksource clksrc;
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *clk[2];
> +	char name[20];
> +	int channels[2];
> +	int bits;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache[2];
> +	u32 bmr_cache;
> +	bool registered;
> +} tc = {
> +	.clksrc = {
> +		.rating		= 200,
> +		.mask		= CLOCKSOURCE_MASK(32),
> +		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
> +	},
> +	.clkevt	= {
> +		.features	= CLOCK_EVT_FEAT_ONESHOT,
> +		/* Should be lower than at91rm9200's system timer */
> +		.rating		= 125,
> +	},
> +};
> +
> +static struct tc_clkevt_device {
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *slow_clk;
> +	struct clk *clk;
> +	char name[20];
> +	int channel;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache;
> +	bool registered;
> +	bool clk_enabled;
> +} tce = {
> +	.clkevt	= {
> +		.features		= CLOCK_EVT_FEAT_PERIODIC |
> +					  CLOCK_EVT_FEAT_ONESHOT,
> +		/*
> +		 * Should be lower than at91rm9200's system timer
> +		 * but higher than tc.clkevt.rating
> +		 */
> +		.rating			= 140,
> +	},
> +};

To be honest: This stuff is horrible to read and the two structs are almost
identical. There is really no good reason to have all this duplicated
mess. I'm pretty sure you can consolidate stuff further when using _one_.

And please define the structs separate from the define and arrange the
struct members in tabluar fashion.

> +/*
> + * Clocksource and clockevent using the same channel(s)
> + */
> +static u64 tc_get_cycles(struct clocksource *cs)
> +{
> +	u32 lower, upper;
> +
> +	do {
> +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> +
> +	return (upper << 16) | lower;
> +}

For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
part wraps around in ~859 seconds, which is plenty even for NOHZ.

So I really would avoid the double read/compare/eventually repeat magic and
just use the lower 32bits for performance sake. I assume the same is true
for sched_clock(), but I might be wrong.

> +static int tcb_clkevt_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	u32 old, next, cur;
> +
> +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	next = old + delta;
> +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +
> +	/* check whether the delta elapsed while setting the register */
> +	if ((next < old && cur < old && cur > next) ||
> +	    (next > old && (cur < old || cur > next))) {
> +		/*
> +		 * Clear the CPCS bit in the status register to avoid
> +		 * generating a spurious interrupt next time a valid
> +		 * timer event is configured.
> +		 */
> +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +		return -ETIME;
> +	}

Aarg. Doesn;t that timer block have a simple count down and fire mode?
These compare equal timers suck.

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20  9:03     ` Thomas Gleixner
@ 2018-06-20  9:46       ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20  9:46 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 20/06/2018 11:03:40+0200, Thomas Gleixner wrote:
> > +/*
> > + * Clocksource and clockevent using the same channel(s)
> > + */
> > +static u64 tc_get_cycles(struct clocksource *cs)
> > +{
> > +	u32 lower, upper;
> > +
> > +	do {
> > +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> > +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> > +
> > +	return (upper << 16) | lower;
> > +}
> 
> For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
> part wraps around in ~859 seconds, which is plenty even for NOHZ.
> 
> So I really would avoid the double read/compare/eventually repeat magic and
> just use the lower 32bits for performance sake. I assume the same is true
> for sched_clock(), but I might be wrong.
> 

Agreed, this is why this is only used with the 16 bit counters (the
register is 32 bit wide but the counter only have 16 bits. For the 32
bit counters, tc_get_cycles32 is used and only use one counter.

> > +static int tcb_clkevt_next_event(unsigned long delta,
> > +				 struct clock_event_device *d)
> > +{
> > +	u32 old, next, cur;
> > +
> > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +	next = old + delta;
> > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +
> > +	/* check whether the delta elapsed while setting the register */
> > +	if ((next < old && cur < old && cur > next) ||
> > +	    (next > old && (cur < old || cur > next))) {
> > +		/*
> > +		 * Clear the CPCS bit in the status register to avoid
> > +		 * generating a spurious interrupt next time a valid
> > +		 * timer event is configured.
> > +		 */
> > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > +		return -ETIME;
> > +	}
> 
> Aarg. Doesn;t that timer block have a simple count down and fire mode?
> These compare equal timers suck.

It only counts up...


-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20  9:46       ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20  9:46 UTC (permalink / raw)
  To: linux-arm-kernel

On 20/06/2018 11:03:40+0200, Thomas Gleixner wrote:
> > +/*
> > + * Clocksource and clockevent using the same channel(s)
> > + */
> > +static u64 tc_get_cycles(struct clocksource *cs)
> > +{
> > +	u32 lower, upper;
> > +
> > +	do {
> > +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> > +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> > +
> > +	return (upper << 16) | lower;
> > +}
> 
> For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
> part wraps around in ~859 seconds, which is plenty even for NOHZ.
> 
> So I really would avoid the double read/compare/eventually repeat magic and
> just use the lower 32bits for performance sake. I assume the same is true
> for sched_clock(), but I might be wrong.
> 

Agreed, this is why this is only used with the 16 bit counters (the
register is 32 bit wide but the counter only have 16 bits. For the 32
bit counters, tc_get_cycles32 is used and only use one counter.

> > +static int tcb_clkevt_next_event(unsigned long delta,
> > +				 struct clock_event_device *d)
> > +{
> > +	u32 old, next, cur;
> > +
> > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +	next = old + delta;
> > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > +
> > +	/* check whether the delta elapsed while setting the register */
> > +	if ((next < old && cur < old && cur > next) ||
> > +	    (next > old && (cur < old || cur > next))) {
> > +		/*
> > +		 * Clear the CPCS bit in the status register to avoid
> > +		 * generating a spurious interrupt next time a valid
> > +		 * timer event is configured.
> > +		 */
> > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > +		return -ETIME;
> > +	}
> 
> Aarg. Doesn;t that timer block have a simple count down and fire mode?
> These compare equal timers suck.

It only counts up...


-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20  9:46       ` Alexandre Belloni
@ 2018-06-20 10:07         ` Thomas Gleixner
  -1 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 10:07 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:
> On 20/06/2018 11:03:40+0200, Thomas Gleixner wrote:
> > > +/*
> > > + * Clocksource and clockevent using the same channel(s)
> > > + */
> > > +static u64 tc_get_cycles(struct clocksource *cs)
> > > +{
> > > +	u32 lower, upper;
> > > +
> > > +	do {
> > > +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> > > +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> > > +
> > > +	return (upper << 16) | lower;
> > > +}
> > 
> > For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
> > part wraps around in ~859 seconds, which is plenty even for NOHZ.
> > 
> > So I really would avoid the double read/compare/eventually repeat magic and
> > just use the lower 32bits for performance sake. I assume the same is true
> > for sched_clock(), but I might be wrong.
> > 
> 
> Agreed, this is why this is only used with the 16 bit counters (the
> register is 32 bit wide but the counter only have 16 bits. For the 32
> bit counters, tc_get_cycles32 is used and only use one counter.

Ah, sorry. I misread the code. Missed that it's the 16bit case. 

> > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > +				 struct clock_event_device *d)
> > > +{
> > > +	u32 old, next, cur;
> > > +
> > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +	next = old + delta;
> > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +
> > > +	/* check whether the delta elapsed while setting the register */
> > > +	if ((next < old && cur < old && cur > next) ||
> > > +	    (next > old && (cur < old || cur > next))) {
> > > +		/*
> > > +		 * Clear the CPCS bit in the status register to avoid
> > > +		 * generating a spurious interrupt next time a valid
> > > +		 * timer event is configured.
> > > +		 */
> > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > +		return -ETIME;
> > > +	}
> > 
> > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > These compare equal timers suck.
> 
> It only counts up...

Have you tried to play with that waveform stuff?

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20 10:07         ` Thomas Gleixner
  0 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 10:07 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:
> On 20/06/2018 11:03:40+0200, Thomas Gleixner wrote:
> > > +/*
> > > + * Clocksource and clockevent using the same channel(s)
> > > + */
> > > +static u64 tc_get_cycles(struct clocksource *cs)
> > > +{
> > > +	u32 lower, upper;
> > > +
> > > +	do {
> > > +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> > > +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> > > +
> > > +	return (upper << 16) | lower;
> > > +}
> > 
> > For timekeeping the win of this is dubious. With a 5Mhz clock the 32bit
> > part wraps around in ~859 seconds, which is plenty even for NOHZ.
> > 
> > So I really would avoid the double read/compare/eventually repeat magic and
> > just use the lower 32bits for performance sake. I assume the same is true
> > for sched_clock(), but I might be wrong.
> > 
> 
> Agreed, this is why this is only used with the 16 bit counters (the
> register is 32 bit wide but the counter only have 16 bits. For the 32
> bit counters, tc_get_cycles32 is used and only use one counter.

Ah, sorry. I misread the code. Missed that it's the 16bit case. 

> > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > +				 struct clock_event_device *d)
> > > +{
> > > +	u32 old, next, cur;
> > > +
> > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +	next = old + delta;
> > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > +
> > > +	/* check whether the delta elapsed while setting the register */
> > > +	if ((next < old && cur < old && cur > next) ||
> > > +	    (next > old && (cur < old || cur > next))) {
> > > +		/*
> > > +		 * Clear the CPCS bit in the status register to avoid
> > > +		 * generating a spurious interrupt next time a valid
> > > +		 * timer event is configured.
> > > +		 */
> > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > +		return -ETIME;
> > > +	}
> > 
> > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > These compare equal timers suck.
> 
> It only counts up...

Have you tried to play with that waveform stuff?

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20 10:07         ` Thomas Gleixner
@ 2018-06-20 10:32           ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20 10:32 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 20/06/2018 12:07:00+0200, Thomas Gleixner wrote:
> > > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > > +				 struct clock_event_device *d)
> > > > +{
> > > > +	u32 old, next, cur;
> > > > +
> > > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > +	next = old + delta;
> > > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > +
> > > > +	/* check whether the delta elapsed while setting the register */
> > > > +	if ((next < old && cur < old && cur > next) ||
> > > > +	    (next > old && (cur < old || cur > next))) {
> > > > +		/*
> > > > +		 * Clear the CPCS bit in the status register to avoid
> > > > +		 * generating a spurious interrupt next time a valid
> > > > +		 * timer event is configured.
> > > > +		 */
> > > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > > +		return -ETIME;
> > > > +	}
> > > 
> > > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > > These compare equal timers suck.
> > 
> > It only counts up...
> 
> Have you tried to play with that waveform stuff?
> 

There are only a count up and count up then down modes. As the counter
value is in a read only register, the only configurable starting value
is 0 so it will always start by counting up. I'm pretty sure the up/down
mode will not help us.

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20 10:32           ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

On 20/06/2018 12:07:00+0200, Thomas Gleixner wrote:
> > > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > > +				 struct clock_event_device *d)
> > > > +{
> > > > +	u32 old, next, cur;
> > > > +
> > > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > +	next = old + delta;
> > > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > +
> > > > +	/* check whether the delta elapsed while setting the register */
> > > > +	if ((next < old && cur < old && cur > next) ||
> > > > +	    (next > old && (cur < old || cur > next))) {
> > > > +		/*
> > > > +		 * Clear the CPCS bit in the status register to avoid
> > > > +		 * generating a spurious interrupt next time a valid
> > > > +		 * timer event is configured.
> > > > +		 */
> > > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > > +		return -ETIME;
> > > > +	}
> > > 
> > > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > > These compare equal timers suck.
> > 
> > It only counts up...
> 
> Have you tried to play with that waveform stuff?
> 

There are only a count up and count up then down modes. As the counter
value is in a read only register, the only configurable starting value
is 0 so it will always start by counting up. I'm pretty sure the up/down
mode will not help us.

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20 10:32           ` Alexandre Belloni
@ 2018-06-20 10:58             ` Thomas Gleixner
  -1 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 10:58 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:

> On 20/06/2018 12:07:00+0200, Thomas Gleixner wrote:
> > > > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > > > +				 struct clock_event_device *d)
> > > > > +{
> > > > > +	u32 old, next, cur;
> > > > > +
> > > > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > > +	next = old + delta;
> > > > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > > +
> > > > > +	/* check whether the delta elapsed while setting the register */
> > > > > +	if ((next < old && cur < old && cur > next) ||
> > > > > +	    (next > old && (cur < old || cur > next))) {
> > > > > +		/*
> > > > > +		 * Clear the CPCS bit in the status register to avoid
> > > > > +		 * generating a spurious interrupt next time a valid
> > > > > +		 * timer event is configured.
> > > > > +		 */
> > > > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > > > +		return -ETIME;
> > > > > +	}
> > > > 
> > > > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > > > These compare equal timers suck.
> > > 
> > > It only counts up...
> > 
> > Have you tried to play with that waveform stuff?
> > 
> 
> There are only a count up and count up then down modes. As the counter
> value is in a read only register, the only configurable starting value
> is 0 so it will always start by counting up. I'm pretty sure the up/down
> mode will not help us.

Hmm, fair enough.

Though the manual says:

     A trigger resets the counter and starts the counter clock. Three types
     of triggers are common to both modes, and a fourth external trigger is
     available to each mode.

     ...

     Software Trigger: Each channel has a software trigger, available by
     setting SWTRG in TC_CCR.

So the question is whether you can't do the following:

     stop_counter()
     issue_sw_trigger()  ---> resets the counter to zero
     write_compare()
     start_counter()

So that should avoid all te mess with comparing to the free running counter
as long as you have two blocks of counters, but then one of them will be
16bit only assumed that there are always 3 counter channels in the TC.

Just a thought, but the code you have should work as well.

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20 10:58             ` Thomas Gleixner
  0 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 10:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:

> On 20/06/2018 12:07:00+0200, Thomas Gleixner wrote:
> > > > > +static int tcb_clkevt_next_event(unsigned long delta,
> > > > > +				 struct clock_event_device *d)
> > > > > +{
> > > > > +	u32 old, next, cur;
> > > > > +
> > > > > +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > > +	next = old + delta;
> > > > > +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> > > > > +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> > > > > +
> > > > > +	/* check whether the delta elapsed while setting the register */
> > > > > +	if ((next < old && cur < old && cur > next) ||
> > > > > +	    (next > old && (cur < old || cur > next))) {
> > > > > +		/*
> > > > > +		 * Clear the CPCS bit in the status register to avoid
> > > > > +		 * generating a spurious interrupt next time a valid
> > > > > +		 * timer event is configured.
> > > > > +		 */
> > > > > +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> > > > > +		return -ETIME;
> > > > > +	}
> > > > 
> > > > Aarg. Doesn;t that timer block have a simple count down and fire mode?
> > > > These compare equal timers suck.
> > > 
> > > It only counts up...
> > 
> > Have you tried to play with that waveform stuff?
> > 
> 
> There are only a count up and count up then down modes. As the counter
> value is in a read only register, the only configurable starting value
> is 0 so it will always start by counting up. I'm pretty sure the up/down
> mode will not help us.

Hmm, fair enough.

Though the manual says:

     A trigger resets the counter and starts the counter clock. Three types
     of triggers are common to both modes, and a fourth external trigger is
     available to each mode.

     ...

     Software Trigger: Each channel has a software trigger, available by
     setting SWTRG in TC_CCR.

So the question is whether you can't do the following:

     stop_counter()
     issue_sw_trigger()  ---> resets the counter to zero
     write_compare()
     start_counter()

So that should avoid all te mess with comparing to the free running counter
as long as you have two blocks of counters, but then one of them will be
16bit only assumed that there are always 3 counter channels in the TC.

Just a thought, but the code you have should work as well.

Thanks,

	tglx

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20 10:58             ` Thomas Gleixner
@ 2018-06-20 11:18               ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20 11:18 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 20/06/2018 12:58:01+0200, Thomas Gleixner wrote:
> > There are only a count up and count up then down modes. As the counter
> > value is in a read only register, the only configurable starting value
> > is 0 so it will always start by counting up. I'm pretty sure the up/down
> > mode will not help us.
> 
> Hmm, fair enough.
> 
> Though the manual says:
> 
>      A trigger resets the counter and starts the counter clock. Three types
>      of triggers are common to both modes, and a fourth external trigger is
>      available to each mode.
> 
>      ...
> 
>      Software Trigger: Each channel has a software trigger, available by
>      setting SWTRG in TC_CCR.
> 
> So the question is whether you can't do the following:
> 
>      stop_counter()
>      issue_sw_trigger()  ---> resets the counter to zero
>      write_compare()
>      start_counter()
> 
> So that should avoid all te mess with comparing to the free running counter
> as long as you have two blocks of counters, but then one of them will be
> 16bit only assumed that there are always 3 counter channels in the TC.
> 
> Just a thought, but the code you have should work as well.
> 

Ah yes, sure, I misunderstood your first comment then. the driver will
register one or two clockevent devices, depending on the number of
available channels.

The first one is based on the clocksource counter and does the counter
comparison you don't like. the second one uses its own channel in the
way you describe (set RC, reset counter, start counter).

This was necessary because some people are running out of TCB channels
as they use the remaining ones as PWMs. But it is still better to use
one channel as clocksource and clockevent that use the PIT.

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20 11:18               ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-20 11:18 UTC (permalink / raw)
  To: linux-arm-kernel

On 20/06/2018 12:58:01+0200, Thomas Gleixner wrote:
> > There are only a count up and count up then down modes. As the counter
> > value is in a read only register, the only configurable starting value
> > is 0 so it will always start by counting up. I'm pretty sure the up/down
> > mode will not help us.
> 
> Hmm, fair enough.
> 
> Though the manual says:
> 
>      A trigger resets the counter and starts the counter clock. Three types
>      of triggers are common to both modes, and a fourth external trigger is
>      available to each mode.
> 
>      ...
> 
>      Software Trigger: Each channel has a software trigger, available by
>      setting SWTRG in TC_CCR.
> 
> So the question is whether you can't do the following:
> 
>      stop_counter()
>      issue_sw_trigger()  ---> resets the counter to zero
>      write_compare()
>      start_counter()
> 
> So that should avoid all te mess with comparing to the free running counter
> as long as you have two blocks of counters, but then one of them will be
> 16bit only assumed that there are always 3 counter channels in the TC.
> 
> Just a thought, but the code you have should work as well.
> 

Ah yes, sure, I misunderstood your first comment then. the driver will
register one or two clockevent devices, depending on the number of
available channels.

The first one is based on the clocksource counter and does the counter
comparison you don't like. the second one uses its own channel in the
way you describe (set RC, reset counter, start counter).

This was necessary because some people are running out of TCB channels
as they use the remaining ones as PWMs. But it is still better to use
one channel as clocksource and clockevent that use the PIT.

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-20 11:18               ` Alexandre Belloni
@ 2018-06-20 11:55                 ` Thomas Gleixner
  -1 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 11:55 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Daniel Lezcano, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:
> On 20/06/2018 12:58:01+0200, Thomas Gleixner wrote:
> > > There are only a count up and count up then down modes. As the counter
> > > value is in a read only register, the only configurable starting value
> > > is 0 so it will always start by counting up. I'm pretty sure the up/down
> > > mode will not help us.
> > 
> > Hmm, fair enough.
> > 
> > Though the manual says:
> > 
> >      A trigger resets the counter and starts the counter clock. Three types
> >      of triggers are common to both modes, and a fourth external trigger is
> >      available to each mode.
> > 
> >      ...
> > 
> >      Software Trigger: Each channel has a software trigger, available by
> >      setting SWTRG in TC_CCR.
> > 
> > So the question is whether you can't do the following:
> > 
> >      stop_counter()
> >      issue_sw_trigger()  ---> resets the counter to zero
> >      write_compare()
> >      start_counter()
> > 
> > So that should avoid all te mess with comparing to the free running counter
> > as long as you have two blocks of counters, but then one of them will be
> > 16bit only assumed that there are always 3 counter channels in the TC.
> > 
> > Just a thought, but the code you have should work as well.
> > 
> 
> Ah yes, sure, I misunderstood your first comment then. the driver will
> register one or two clockevent devices, depending on the number of
> available channels.
> 
> The first one is based on the clocksource counter and does the counter
> comparison you don't like. the second one uses its own channel in the
> way you describe (set RC, reset counter, start counter).
> 
> This was necessary because some people are running out of TCB channels
> as they use the remaining ones as PWMs. But it is still better to use
> one channel as clocksource and clockevent that use the PIT.

Fair enough.


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-20 11:55                 ` Thomas Gleixner
  0 siblings, 0 replies; 38+ messages in thread
From: Thomas Gleixner @ 2018-06-20 11:55 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, 20 Jun 2018, Alexandre Belloni wrote:
> On 20/06/2018 12:58:01+0200, Thomas Gleixner wrote:
> > > There are only a count up and count up then down modes. As the counter
> > > value is in a read only register, the only configurable starting value
> > > is 0 so it will always start by counting up. I'm pretty sure the up/down
> > > mode will not help us.
> > 
> > Hmm, fair enough.
> > 
> > Though the manual says:
> > 
> >      A trigger resets the counter and starts the counter clock. Three types
> >      of triggers are common to both modes, and a fourth external trigger is
> >      available to each mode.
> > 
> >      ...
> > 
> >      Software Trigger: Each channel has a software trigger, available by
> >      setting SWTRG in TC_CCR.
> > 
> > So the question is whether you can't do the following:
> > 
> >      stop_counter()
> >      issue_sw_trigger()  ---> resets the counter to zero
> >      write_compare()
> >      start_counter()
> > 
> > So that should avoid all te mess with comparing to the free running counter
> > as long as you have two blocks of counters, but then one of them will be
> > 16bit only assumed that there are always 3 counter channels in the TC.
> > 
> > Just a thought, but the code you have should work as well.
> > 
> 
> Ah yes, sure, I misunderstood your first comment then. the driver will
> register one or two clockevent devices, depending on the number of
> available channels.
> 
> The first one is based on the clocksource counter and does the counter
> comparison you don't like. the second one uses its own channel in the
> way you describe (set RC, reset counter, start counter).
> 
> This was necessary because some people are running out of TCB channels
> as they use the remaining ones as PWMs. But it is still better to use
> one channel as clocksource and clockevent that use the PIT.

Fair enough.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 1/6] ARM: at91: add TCB registers definitions
  2018-06-19 21:19   ` Alexandre Belloni
@ 2018-06-28 15:15     ` Daniel Lezcano
  -1 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 15:15 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 19/06/2018 23:19, Alexandre Belloni wrote:
> Add registers and bits definitions for the timer counter blocks found on
> Atmel ARM SoCs.
> 
> Tested-by: Alexander Dahl <ada@thorsis.com>
> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> ---
>  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++

Is the header necessary ? Can it be moved in the .c ?

>  1 file changed, 216 insertions(+)
>  create mode 100644 include/soc/at91/atmel_tcb.h
> 
> diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
> new file mode 100644
> index 000000000000..3ed66031fc76
> --- /dev/null

[ ... ]

> +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
> +{
> +	struct clk *clk;
> +	char clk_name[] = "t0_clk";
> +
> +	clk_name[1] += channel;

clever :)

> +	clk = of_clk_get_by_name(node->parent, clk_name);
> +	if (!IS_ERR(clk))
> +		return clk;
> +
> +	return of_clk_get_by_name(node->parent, "t0_clk");

Why do you want to return clk from t0_clk if another channel is
requested ? This is prone to error.

I would clarify that at the caller level, if tcb_clk_get fails then try
with channel zero.

> +}
> +
> +static inline int tcb_irq_get(struct device_node *node, int channel)

no inline

> +{
> +	int irq;
> +
> +	irq = of_irq_get(node->parent, channel);
> +	if (irq > 0)
> +		return irq;
> +
> +	return of_irq_get(node->parent, 0);

Same comment than above.

> +}
> +
> +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
> +
> +struct atmel_tcb_info {
> +	int bits;
> +};
> +
> +static const struct atmel_tcb_info atmel_tcb_infos[] = {
> +	{ .bits = 16 },
> +	{ .bits = 32 },
> +};

Structuring the code with structure is a good practice. However, this is
too much :)

> +static const struct of_device_id atmel_tcb_dt_ids[] = {
> +	{
> +		.compatible = "atmel,at91rm9200-tcb",
> +		.data = &atmel_tcb_infos[0],

		.data = (void *)16;

> +	}, {
> +		.compatible = "atmel,at91sam9x5-tcb",
> +		.data = &atmel_tcb_infos[1],
> +	}, {
> +		/* sentinel */
> +	}
> +};
> +



> +#endif /* __SOC_ATMEL_TCB_H */
> 


-- 
 <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 1/6] ARM: at91: add TCB registers definitions
@ 2018-06-28 15:15     ` Daniel Lezcano
  0 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 15:15 UTC (permalink / raw)
  To: linux-arm-kernel

On 19/06/2018 23:19, Alexandre Belloni wrote:
> Add registers and bits definitions for the timer counter blocks found on
> Atmel ARM SoCs.
> 
> Tested-by: Alexander Dahl <ada@thorsis.com>
> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> ---
>  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++

Is the header necessary ? Can it be moved in the .c ?

>  1 file changed, 216 insertions(+)
>  create mode 100644 include/soc/at91/atmel_tcb.h
> 
> diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
> new file mode 100644
> index 000000000000..3ed66031fc76
> --- /dev/null

[ ... ]

> +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
> +{
> +	struct clk *clk;
> +	char clk_name[] = "t0_clk";
> +
> +	clk_name[1] += channel;

clever :)

> +	clk = of_clk_get_by_name(node->parent, clk_name);
> +	if (!IS_ERR(clk))
> +		return clk;
> +
> +	return of_clk_get_by_name(node->parent, "t0_clk");

Why do you want to return clk from t0_clk if another channel is
requested ? This is prone to error.

I would clarify that at the caller level, if tcb_clk_get fails then try
with channel zero.

> +}
> +
> +static inline int tcb_irq_get(struct device_node *node, int channel)

no inline

> +{
> +	int irq;
> +
> +	irq = of_irq_get(node->parent, channel);
> +	if (irq > 0)
> +		return irq;
> +
> +	return of_irq_get(node->parent, 0);

Same comment than above.

> +}
> +
> +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
> +
> +struct atmel_tcb_info {
> +	int bits;
> +};
> +
> +static const struct atmel_tcb_info atmel_tcb_infos[] = {
> +	{ .bits = 16 },
> +	{ .bits = 32 },
> +};

Structuring the code with structure is a good practice. However, this is
too much :)

> +static const struct of_device_id atmel_tcb_dt_ids[] = {
> +	{
> +		.compatible = "atmel,at91rm9200-tcb",
> +		.data = &atmel_tcb_infos[0],

		.data = (void *)16;

> +	}, {
> +		.compatible = "atmel,at91sam9x5-tcb",
> +		.data = &atmel_tcb_infos[1],
> +	}, {
> +		/* sentinel */
> +	}
> +};
> +



> +#endif /* __SOC_ATMEL_TCB_H */
> 


-- 
 <http://www.linaro.org/> Linaro.org ? Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-19 21:19   ` Alexandre Belloni
@ 2018-06-28 16:24     ` Daniel Lezcano
  -1 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 16:24 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 19/06/2018 23:19, Alexandre Belloni wrote:
> Add a driver for the Atmel Timer Counter Blocks. This driver provides a
> clocksource and two clockevent devices.
> 
> One of the clockevent device is linked to the clocksource counter and so it
> will run at the same frequency. This will be used when there is only on TCB
> channel available for timers.
> 
> The other clockevent device runs on a separate TCB channel when available.

Can you split this patch in two. clockevent2 and clocksource/clockevent ?

> This driver uses regmap and syscon to be able to probe early in the boot
> and avoid having to switch on the TCB clocksource later. Using regmap also
> means that unused TCB channels may be used by other drivers (PWM for
> example). read/writel are still used to access channel specific registers
> to avoid the performance impact of regmap (mainly locking).
> 
> Tested-by: Alexander Dahl <ada@thorsis.com>
> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> ---
>  drivers/clocksource/Kconfig           |   8 +
>  drivers/clocksource/Makefile          |   3 +-
>  drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
>  3 files changed, 640 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/clocksource/timer-atmel-tcb.c
> 
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index dec0dd88ec15..0b1b0de2abcc 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -403,6 +403,14 @@ config ATMEL_ST
>  	help
>  	  Support for the Atmel ST timer.
>  
> +config ATMEL_ARM_TCB_CLKSRC
> +	bool "Microchip ARM TC Block" if COMPILE_TEST
> +	select REGMAP_MMIO
> +	depends on GENERIC_CLOCKEVENTS
> +	help
> +	  This enables build of clocksource and clockevent driver for
> +	  the integrated Timer Counter Blocks in Microchip ARM SoCs.
> +
>  config CLKSRC_EXYNOS_MCT
>  	bool "Exynos multi core timer driver" if COMPILE_TEST
>  	depends on ARM || ARM64
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index 00caf37e52f9..6991348aa24a 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -3,7 +3,8 @@ obj-$(CONFIG_TIMER_OF)		+= timer-of.o
>  obj-$(CONFIG_TIMER_PROBE)	+= timer-probe.o
>  obj-$(CONFIG_ATMEL_PIT)		+= timer-atmel-pit.o
>  obj-$(CONFIG_ATMEL_ST)		+= timer-atmel-st.o
> -obj-$(CONFIG_ATMEL_TCB_CLKSRC)	+= tcb_clksrc.o
> +obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o
> +obj-$(CONFIG_ATMEL_ARM_TCB_CLKSRC)	+= timer-atmel-tcb.o
>  obj-$(CONFIG_X86_PM_TIMER)	+= acpi_pm.o
>  obj-$(CONFIG_SCx200HR_TIMER)	+= scx200_hrt.o
>  obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
> diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
> new file mode 100644
> index 000000000000..73e6b2783dda
> --- /dev/null
> +++ b/drivers/clocksource/timer-atmel-tcb.c
> @@ -0,0 +1,630 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +#include <linux/sched_clock.h>
> +#include <soc/at91/atmel_tcb.h>
> +
> +static struct atmel_tcb_clksrc {
> +	struct clocksource clksrc;
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *clk[2];
> +	char name[20];
> +	int channels[2];
> +	int bits;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache[2];
> +	u32 bmr_cache;
> +	bool registered;
> +} tc = {
> +	.clksrc = {
> +		.rating		= 200,
> +		.mask		= CLOCKSOURCE_MASK(32),
> +		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
> +	},
> +	.clkevt	= {
> +		.features	= CLOCK_EVT_FEAT_ONESHOT,
> +		/* Should be lower than at91rm9200's system timer */
> +		.rating		= 125,
> +	},
> +};
> +
> +static struct tc_clkevt_device {
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *slow_clk;
> +	struct clk *clk;
> +	char name[20];
> +	int channel;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache;
> +	bool registered;
> +	bool clk_enabled;
> +} tce = {
> +	.clkevt	= {
> +		.features		= CLOCK_EVT_FEAT_PERIODIC |
> +					  CLOCK_EVT_FEAT_ONESHOT,
> +		/*
> +		 * Should be lower than at91rm9200's system timer
> +		 * but higher than tc.clkevt.rating
> +		 */
> +		.rating			= 140,
> +	},
> +};
> +
> +/*
> + * Clockevent device using its own channel
> + */
> +
> +static void tc_clkevt2_clk_disable(struct clock_event_device *d)
> +{
> +	clk_disable(tce.clk);
> +	tce.clk_enabled = false;
> +}
> +
> +static void tc_clkevt2_clk_enable(struct clock_event_device *d)
> +{
> +	if (tce.clk_enabled)
> +		return;

Why this test ?

> +	clk_enable(tce.clk);
> +	tce.clk_enabled = true;
> +}
>
> +static int tc_clkevt2_stop(struct clock_event_device *d)
> +{
> +	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_shutdown(struct clock_event_device *d)
> +{
> +	tc_clkevt2_stop(d);
> +	if (!clockevent_state_detached(d))

Why this test ?

> +		tc_clkevt2_clk_disable(d);
> +
> +	return 0;
> +}
> +
> +/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
> + * because using one of the divided clocks would usually mean the
> + * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
> + *
> + * A divided clock could be good for high resolution timers, since
> + * 30.5 usec resolution can seem "low".
> + */
> +static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
> +{
> +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> +		tc_clkevt2_stop(d);

Why these tests ? :)

> +	tc_clkevt2_clk_enable(d);
> +
> +	/* slow clock, count up to RC, then irq and stop */
> +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_CPCSTOP |
> +	       ATMEL_TC_CMR_WAVE | ATMEL_TC_CMR_WAVESEL_UPRC,
> +	       tce.base + ATMEL_TC_CMR(tce.channel));
> +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_set_periodic(struct clock_event_device *d)
> +{
> +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> +		tc_clkevt2_stop(d);

?

> +	/* By not making the gentime core emulate periodic mode on top
> +	 * of oneshot, we get lower overhead and improved accuracy.
> +	 */
> +	tc_clkevt2_clk_enable(d);
> +
> +	/* slow clock, count up to RC, then irq and restart */
> +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
> +	       ATMEL_TC_CMR_WAVESEL_UPRC,
> +	       tce.base + ATMEL_TC_CMR(tce.channel));
> +	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));

Do this computation at init time.

> +	/* Enable clock and interrupts on RC compare */
> +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +	       tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +	       tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
> +{
> +	unsigned int sr;
> +
> +	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
> +	if (sr & ATMEL_TC_CPCS) {
> +		tce.clkevt.event_handler(&tce.clkevt);
> +		return IRQ_HANDLED;

Isn't an clear irq missing ?

> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static void tc_clkevt2_suspend(struct clock_event_device *d)
> +{
> +	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
> +	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
> +	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
> +	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
> +				ATMEL_TC_CLKSTA);

Who is in charge of powering down the timer ?

> +}
> +
> +static void tc_clkevt2_resume(struct clock_event_device *d)
> +{
> +	/* Restore registers for the channel, RA and RB are not used  */
> +	writel(tce.cache.cmr, tc.base + ATMEL_TC_CMR(tce.channel));
> +	writel(tce.cache.rc, tc.base + ATMEL_TC_RC(tce.channel));
> +	writel(0, tc.base + ATMEL_TC_RA(tce.channel));
> +	writel(0, tc.base + ATMEL_TC_RB(tce.channel));
> +	/* Disable all the interrupts */
> +	writel(0xff, tc.base + ATMEL_TC_IDR(tce.channel));
> +	/* Reenable interrupts that were enabled before suspending */
> +	writel(tce.cache.imr, tc.base + ATMEL_TC_IER(tce.channel));
> +
> +	/* Start the clock if it was used */
> +	if (tce.cache.clken)
> +		writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +		       tc.base + ATMEL_TC_CCR(tce.channel));
> +}
> +
> +static int __init tc_clkevt_register(struct device_node *node,
> +				     struct regmap *regmap, void __iomem *base,
> +				     int channel, int irq, int bits)
> +{
> +	int ret;
> +
> +	tce.regmap = regmap;
> +	tce.base = base;
> +	tce.channel = channel;
> +	tce.irq = irq;
> +
> +	tce.slow_clk = of_clk_get_by_name(node->parent, "slow_clk");
> +	if (IS_ERR(tce.slow_clk))
> +		return PTR_ERR(tce.slow_clk);
> +
> +	ret = clk_prepare_enable(tce.slow_clk);
> +	if (ret)
> +		return ret;

It is not mandatory to have the slow_clk variable in the structure, you
can declare it locally and for the rest use the timer-of API. That will
save you a lot of line of code.

> +
> +	tce.clk = tcb_clk_get(node, tce.channel);
> +	if (IS_ERR(tce.clk)) {
> +		ret = PTR_ERR(tce.clk);
> +		goto err_slow;
> +	}
> +
> +	snprintf(tce.name, sizeof(tce.name), "%s:%d",
> +		 kbasename(node->parent->full_name), channel);
> +	tce.clkevt.cpumask = cpumask_of(0);
> +	tce.clkevt.name = tce.name;
> +	tce.clkevt.set_next_event = tc_clkevt2_next_event,
> +	tce.clkevt.set_state_shutdown = tc_clkevt2_shutdown,
> +	tce.clkevt.set_state_periodic = tc_clkevt2_set_periodic,
> +	tce.clkevt.set_state_oneshot = tc_clkevt2_set_oneshot,
> +	tce.clkevt.suspend = tc_clkevt2_suspend,
> +	tce.clkevt.resume = tc_clkevt2_resume,
> +
> +	/* try to enable clk to avoid future errors in mode change */
> +	ret = clk_prepare_enable(tce.clk);
> +	if (ret)
> +		goto err_slow;
> +	clk_disable(tce.clk);
> +
> +	clockevents_config_and_register(&tce.clkevt, 32768, 1, BIT(bits) - 1);

s/BIT(bits) - 1/CLOCKSOURCE_MASK/

> +	ret = request_irq(tce.irq, tc_clkevt2_irq, IRQF_TIMER | IRQF_SHARED,
> +			  tce.clkevt.name, &tce);
> +	if (ret)
> +		goto err_clk;
> +
> +	tce.registered = true;
> +
> +	return 0;
> +
> +err_clk:
> +	clk_unprepare(tce.clk);
> +err_slow:
> +	clk_disable_unprepare(tce.slow_clk);
> +
> +	return ret;
> +}

I let you send the split version before continuing the review.

  -- Daniel

> +/*
> + * Clocksource and clockevent using the same channel(s)
> + */
> +static u64 tc_get_cycles(struct clocksource *cs)
> +{
> +	u32 lower, upper;
> +
> +	do {
> +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> +
> +	return (upper << 16) | lower;
> +}
> +
> +static u64 tc_get_cycles32(struct clocksource *cs)
> +{
> +	return readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +}
> +
> +static u64 notrace tc_sched_clock_read(void)
> +{
> +	return tc_get_cycles(&tc.clksrc);
> +}
> +
> +static u64 notrace tc_sched_clock_read32(void)
> +{
> +	return tc_get_cycles32(&tc.clksrc);
> +}
> +
> +static int tcb_clkevt_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	u32 old, next, cur;
> +
> +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	next = old + delta;
> +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +
> +	/* check whether the delta elapsed while setting the register */
> +	if ((next < old && cur < old && cur > next) ||
> +	    (next > old && (cur < old || cur > next))) {
> +		/*
> +		 * Clear the CPCS bit in the status register to avoid
> +		 * generating a spurious interrupt next time a valid
> +		 * timer event is configured.
> +		 */
> +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +		return -ETIME;
> +	}
> +
> +	writel(ATMEL_TC_CPCS, tc.base + ATMEL_TC_IER(tc.channels[0]));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t tc_clkevt_irq(int irq, void *handle)
> +{
> +	unsigned int sr;
> +
> +	sr = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +	if (sr & ATMEL_TC_CPCS) {
> +		tc.clkevt.event_handler(&tc.clkevt);
> +		return IRQ_HANDLED;
> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static int tcb_clkevt_oneshot(struct clock_event_device *dev)
> +{
> +	if (clockevent_state_oneshot(dev))
> +		return 0;
> +
> +	/*
> +	 * Because both clockevent devices may share the same IRQ, we don't want
> +	 * the less likely one to stay requested
> +	 */
> +	return request_irq(tc.irq, tc_clkevt_irq, IRQF_TIMER | IRQF_SHARED,
> +			   tc.name, &tc);
> +}
> +
> +static int tcb_clkevt_shutdown(struct clock_event_device *dev)
> +{
> +	writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[0]));
> +	if (tc.bits == 16)
> +		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[1]));
> +
> +	if (!clockevent_state_detached(dev))
> +		free_irq(tc.irq, &tc);
> +
> +	return 0;
> +}
> +
> +static void __init tcb_setup_dual_chan(struct atmel_tcb_clksrc *tc,
> +				       int mck_divisor_idx)
> +{
> +	/* first channel: waveform mode, input mclk/8, clock TIOA on overflow */
> +	writel(mck_divisor_idx			/* likely divide-by-8 */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP	/* free-run */
> +	       | ATMEL_TC_CMR_ACPA(SET)		/* TIOA rises at 0 */
> +	       | ATMEL_TC_CMR_ACPC(CLEAR),	/* (duty cycle 50%) */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
> +	writel(0x0000, tc->base + ATMEL_TC_RA(tc->channels[0]));
> +	writel(0x8000, tc->base + ATMEL_TC_RC(tc->channels[0]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
> +
> +	/* second channel: waveform mode, input TIOA */
> +	writel(ATMEL_TC_CMR_XC(tc->channels[1])		/* input: TIOA */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP,		/* free-run */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[1]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[1]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[1]));
> +
> +	/* chain both channel, we assume the previous channel */
> +	regmap_write(tc->regmap, ATMEL_TC_BMR,
> +		     ATMEL_TC_BMR_TCXC(1 + tc->channels[1], tc->channels[1]));
> +	/* then reset all the timers */
> +	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static void __init tcb_setup_single_chan(struct atmel_tcb_clksrc *tc,
> +					 int mck_divisor_idx)
> +{
> +	/* channel 0:  waveform mode, input mclk/8 */
> +	writel(mck_divisor_idx			/* likely divide-by-8 */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP,	/* free-run */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
> +
> +	/* then reset all the timers */
> +	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static void tc_clksrc_suspend(struct clocksource *cs)
> +{
> +	int i;
> +
> +	for (i = 0; i < 1 + (tc.bits == 16); i++) {
> +		tc.cache[i].cmr = readl(tc.base + ATMEL_TC_CMR(tc.channels[i]));
> +		tc.cache[i].imr = readl(tc.base + ATMEL_TC_IMR(tc.channels[i]));
> +		tc.cache[i].rc = readl(tc.base + ATMEL_TC_RC(tc.channels[i]));
> +		tc.cache[i].clken = !!(readl(tc.base +
> +					     ATMEL_TC_SR(tc.channels[i])) &
> +				       ATMEL_TC_CLKSTA);
> +	}
> +
> +	if (tc.bits == 16)
> +		regmap_read(tc.regmap, ATMEL_TC_BMR, &tc.bmr_cache);
> +}
> +
> +static void tc_clksrc_resume(struct clocksource *cs)
> +{
> +	int i;
> +
> +	for (i = 0; i < 1 + (tc.bits == 16); i++) {
> +		/* Restore registers for the channel, RA and RB are not used  */
> +		writel(tc.cache[i].cmr, tc.base + ATMEL_TC_CMR(tc.channels[i]));
> +		writel(tc.cache[i].rc, tc.base + ATMEL_TC_RC(tc.channels[i]));
> +		writel(0, tc.base + ATMEL_TC_RA(tc.channels[i]));
> +		writel(0, tc.base + ATMEL_TC_RB(tc.channels[i]));
> +		/* Disable all the interrupts */
> +		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[i]));
> +		/* Reenable interrupts that were enabled before suspending */
> +		writel(tc.cache[i].imr, tc.base + ATMEL_TC_IER(tc.channels[i]));
> +
> +		/* Start the clock if it was used */
> +		if (tc.cache[i].clken)
> +			writel(ATMEL_TC_CCR_CLKEN, tc.base +
> +			       ATMEL_TC_CCR(tc.channels[i]));
> +	}
> +
> +	/* in case of dual channel, chain channels */
> +	if (tc.bits == 16)
> +		regmap_write(tc.regmap, ATMEL_TC_BMR, tc.bmr_cache);
> +	/* Finally, trigger all the channels*/
> +	regmap_write(tc.regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static int __init tcb_clksrc_register(struct device_node *node,
> +				      struct regmap *regmap, void __iomem *base,
> +				      int channel, int channel1, int irq,
> +				      int bits)
> +{
> +	u32 rate, divided_rate = 0;
> +	int best_divisor_idx = -1;
> +	int i, err = -1;
> +	u64 (*tc_sched_clock)(void);
> +
> +	tc.regmap = regmap;
> +	tc.base = base;
> +	tc.channels[0] = channel;
> +	tc.channels[1] = channel1;
> +	tc.irq = irq;
> +	tc.bits = bits;
> +
> +	tc.clk[0] = tcb_clk_get(node, tc.channels[0]);
> +	if (IS_ERR(tc.clk[0]))
> +		return PTR_ERR(tc.clk[0]);
> +	err = clk_prepare_enable(tc.clk[0]);
> +	if (err) {
> +		pr_debug("can't enable T0 clk\n");
> +		goto err_clk;
> +	}
> +
> +	/* How fast will we be counting?  Pick something over 5 MHz.  */
> +	rate = (u32)clk_get_rate(tc.clk[0]);
> +	for (i = 0; i < 5; i++) {
> +		unsigned int divisor = atmel_tc_divisors[i];
> +		unsigned int tmp;
> +
> +		if (!divisor)
> +			continue;
> +
> +		tmp = rate / divisor;
> +		pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
> +		if (best_divisor_idx > 0) {
> +			if (tmp < 5 * 1000 * 1000)
> +				continue;
> +		}
> +		divided_rate = tmp;
> +		best_divisor_idx = i;
> +	}
> +
> +	if (tc.bits == 32) {
> +		tc.clksrc.read = tc_get_cycles32;
> +		tcb_setup_single_chan(&tc, best_divisor_idx);
> +		tc_sched_clock = tc_sched_clock_read32;
> +		snprintf(tc.name, sizeof(tc.name), "%s:%d",
> +			 kbasename(node->parent->full_name), tc.channels[0]);
> +	} else {
> +		tc.clk[1] = tcb_clk_get(node, tc.channels[1]);
> +		if (IS_ERR(tc.clk[1]))
> +			goto err_disable_t0;
> +
> +		err = clk_prepare_enable(tc.clk[1]);
> +		if (err) {
> +			pr_debug("can't enable T1 clk\n");
> +			goto err_clk1;
> +		}
> +		tc.clksrc.read = tc_get_cycles,
> +		tcb_setup_dual_chan(&tc, best_divisor_idx);
> +		tc_sched_clock = tc_sched_clock_read;
> +		snprintf(tc.name, sizeof(tc.name), "%s:%d,%d",
> +			 kbasename(node->parent->full_name), tc.channels[0],
> +			 tc.channels[1]);
> +	}
> +
> +	pr_debug("%s at %d.%03d MHz\n", tc.name,
> +		 divided_rate / 1000000,
> +		 ((divided_rate + 500000) % 1000000) / 1000);
> +
> +	tc.clksrc.name = tc.name;
> +	tc.clksrc.suspend = tc_clksrc_suspend;
> +	tc.clksrc.resume = tc_clksrc_resume;
> +
> +	err = clocksource_register_hz(&tc.clksrc, divided_rate);
> +	if (err)
> +		goto err_disable_t1;
> +
> +	sched_clock_register(tc_sched_clock, 32, divided_rate);
> +
> +	tc.registered = true;
> +
> +	/* Set up and register clockevents */
> +	tc.clkevt.name = tc.name;
> +	tc.clkevt.cpumask = cpumask_of(0);
> +	tc.clkevt.set_next_event = tcb_clkevt_next_event;
> +	tc.clkevt.set_state_oneshot = tcb_clkevt_oneshot;
> +	tc.clkevt.set_state_shutdown = tcb_clkevt_shutdown;
> +	clockevents_config_and_register(&tc.clkevt, divided_rate, 1,
> +					BIT(tc.bits) - 1);
> +
> +	return 0;
> +
> +err_disable_t1:
> +	if (tc.bits == 16)
> +		clk_disable_unprepare(tc.clk[1]);
> +
> +err_clk1:
> +	if (tc.bits == 16)
> +		clk_put(tc.clk[1]);
> +
> +err_disable_t0:
> +	clk_disable_unprepare(tc.clk[0]);
> +
> +err_clk:
> +	clk_put(tc.clk[0]);
> +
> +	pr_err("%s: unable to register clocksource/clockevent\n",
> +	       tc.clksrc.name);
> +
> +	return err;
> +}
> +
> +static int __init tcb_clksrc_init(struct device_node *node)
> +{
> +	const struct of_device_id *match;
> +	const struct atmel_tcb_info *tcb_info;
> +	struct regmap *regmap;
> +	void __iomem *tcb_base;
> +	u32 channel;
> +	int bits, irq, err, chan1 = -1;
> +
> +	if (tc.registered && tce.registered)
> +		return -ENODEV;
> +
> +	/*
> +	 * The regmap has to be used to access registers that are shared
> +	 * between channels on the same TCB but we keep direct IO access for
> +	 * the counters to avoid the impact on performance
> +	 */
> +	regmap = syscon_node_to_regmap(node->parent);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	tcb_base = of_iomap(node->parent, 0);
> +	if (!tcb_base) {
> +		pr_err("%s +%d %s\n", __FILE__, __LINE__, __func__);
> +		return -ENXIO;
> +	}
> +
> +	match = of_match_node(atmel_tcb_dt_ids, node->parent);
> +	tcb_info = match->data;
> +	bits = tcb_info->bits;
> +
> +	err = of_property_read_u32_index(node, "reg", 0, &channel);
> +	if (err)
> +		return err;
> +
> +	irq = tcb_irq_get(node, channel);
> +	if (irq < 0)
> +		return irq;
> +
> +	if (tc.registered)
> +		return tc_clkevt_register(node, regmap, tcb_base, channel, irq,
> +					  bits);
> +
> +	if (bits == 16) {
> +		of_property_read_u32_index(node, "reg", 1, &chan1);
> +		if (chan1 == -1) {
> +			if (tce.registered) {
> +				pr_err("%s: clocksource needs two channels\n",
> +				       node->parent->full_name);
> +				return -EINVAL;
> +			} else {
> +				return tc_clkevt_register(node, regmap,
> +							  tcb_base, channel,
> +							  irq, bits);
> +			}
> +		}
> +	}
> +
> +	return tcb_clksrc_register(node, regmap, tcb_base, channel, chan1, irq,
> +				   bits);
> +}
> +CLOCKSOURCE_OF_DECLARE(atmel_tcb_clksrc, "atmel,tcb-timer",
> +		       tcb_clksrc_init);
> 


-- 
 <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-06-28 16:24     ` Daniel Lezcano
  0 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 16:24 UTC (permalink / raw)
  To: linux-arm-kernel

On 19/06/2018 23:19, Alexandre Belloni wrote:
> Add a driver for the Atmel Timer Counter Blocks. This driver provides a
> clocksource and two clockevent devices.
> 
> One of the clockevent device is linked to the clocksource counter and so it
> will run at the same frequency. This will be used when there is only on TCB
> channel available for timers.
> 
> The other clockevent device runs on a separate TCB channel when available.

Can you split this patch in two. clockevent2 and clocksource/clockevent ?

> This driver uses regmap and syscon to be able to probe early in the boot
> and avoid having to switch on the TCB clocksource later. Using regmap also
> means that unused TCB channels may be used by other drivers (PWM for
> example). read/writel are still used to access channel specific registers
> to avoid the performance impact of regmap (mainly locking).
> 
> Tested-by: Alexander Dahl <ada@thorsis.com>
> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> ---
>  drivers/clocksource/Kconfig           |   8 +
>  drivers/clocksource/Makefile          |   3 +-
>  drivers/clocksource/timer-atmel-tcb.c | 630 ++++++++++++++++++++++++++
>  3 files changed, 640 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/clocksource/timer-atmel-tcb.c
> 
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index dec0dd88ec15..0b1b0de2abcc 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -403,6 +403,14 @@ config ATMEL_ST
>  	help
>  	  Support for the Atmel ST timer.
>  
> +config ATMEL_ARM_TCB_CLKSRC
> +	bool "Microchip ARM TC Block" if COMPILE_TEST
> +	select REGMAP_MMIO
> +	depends on GENERIC_CLOCKEVENTS
> +	help
> +	  This enables build of clocksource and clockevent driver for
> +	  the integrated Timer Counter Blocks in Microchip ARM SoCs.
> +
>  config CLKSRC_EXYNOS_MCT
>  	bool "Exynos multi core timer driver" if COMPILE_TEST
>  	depends on ARM || ARM64
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index 00caf37e52f9..6991348aa24a 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -3,7 +3,8 @@ obj-$(CONFIG_TIMER_OF)		+= timer-of.o
>  obj-$(CONFIG_TIMER_PROBE)	+= timer-probe.o
>  obj-$(CONFIG_ATMEL_PIT)		+= timer-atmel-pit.o
>  obj-$(CONFIG_ATMEL_ST)		+= timer-atmel-st.o
> -obj-$(CONFIG_ATMEL_TCB_CLKSRC)	+= tcb_clksrc.o
> +obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o
> +obj-$(CONFIG_ATMEL_ARM_TCB_CLKSRC)	+= timer-atmel-tcb.o
>  obj-$(CONFIG_X86_PM_TIMER)	+= acpi_pm.o
>  obj-$(CONFIG_SCx200HR_TIMER)	+= scx200_hrt.o
>  obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC)	+= cs5535-clockevt.o
> diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
> new file mode 100644
> index 000000000000..73e6b2783dda
> --- /dev/null
> +++ b/drivers/clocksource/timer-atmel-tcb.c
> @@ -0,0 +1,630 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +#include <linux/sched_clock.h>
> +#include <soc/at91/atmel_tcb.h>
> +
> +static struct atmel_tcb_clksrc {
> +	struct clocksource clksrc;
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *clk[2];
> +	char name[20];
> +	int channels[2];
> +	int bits;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache[2];
> +	u32 bmr_cache;
> +	bool registered;
> +} tc = {
> +	.clksrc = {
> +		.rating		= 200,
> +		.mask		= CLOCKSOURCE_MASK(32),
> +		.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
> +	},
> +	.clkevt	= {
> +		.features	= CLOCK_EVT_FEAT_ONESHOT,
> +		/* Should be lower than at91rm9200's system timer */
> +		.rating		= 125,
> +	},
> +};
> +
> +static struct tc_clkevt_device {
> +	struct clock_event_device clkevt;
> +	struct regmap *regmap;
> +	void __iomem *base;
> +	struct clk *slow_clk;
> +	struct clk *clk;
> +	char name[20];
> +	int channel;
> +	int irq;
> +	struct {
> +		u32 cmr;
> +		u32 imr;
> +		u32 rc;
> +		bool clken;
> +	} cache;
> +	bool registered;
> +	bool clk_enabled;
> +} tce = {
> +	.clkevt	= {
> +		.features		= CLOCK_EVT_FEAT_PERIODIC |
> +					  CLOCK_EVT_FEAT_ONESHOT,
> +		/*
> +		 * Should be lower than at91rm9200's system timer
> +		 * but higher than tc.clkevt.rating
> +		 */
> +		.rating			= 140,
> +	},
> +};
> +
> +/*
> + * Clockevent device using its own channel
> + */
> +
> +static void tc_clkevt2_clk_disable(struct clock_event_device *d)
> +{
> +	clk_disable(tce.clk);
> +	tce.clk_enabled = false;
> +}
> +
> +static void tc_clkevt2_clk_enable(struct clock_event_device *d)
> +{
> +	if (tce.clk_enabled)
> +		return;

Why this test ?

> +	clk_enable(tce.clk);
> +	tce.clk_enabled = true;
> +}
>
> +static int tc_clkevt2_stop(struct clock_event_device *d)
> +{
> +	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_shutdown(struct clock_event_device *d)
> +{
> +	tc_clkevt2_stop(d);
> +	if (!clockevent_state_detached(d))

Why this test ?

> +		tc_clkevt2_clk_disable(d);
> +
> +	return 0;
> +}
> +
> +/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
> + * because using one of the divided clocks would usually mean the
> + * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
> + *
> + * A divided clock could be good for high resolution timers, since
> + * 30.5 usec resolution can seem "low".
> + */
> +static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
> +{
> +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> +		tc_clkevt2_stop(d);

Why these tests ? :)

> +	tc_clkevt2_clk_enable(d);
> +
> +	/* slow clock, count up to RC, then irq and stop */
> +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_CPCSTOP |
> +	       ATMEL_TC_CMR_WAVE | ATMEL_TC_CMR_WAVESEL_UPRC,
> +	       tce.base + ATMEL_TC_CMR(tce.channel));
> +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_set_periodic(struct clock_event_device *d)
> +{
> +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> +		tc_clkevt2_stop(d);

?

> +	/* By not making the gentime core emulate periodic mode on top
> +	 * of oneshot, we get lower overhead and improved accuracy.
> +	 */
> +	tc_clkevt2_clk_enable(d);
> +
> +	/* slow clock, count up to RC, then irq and restart */
> +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
> +	       ATMEL_TC_CMR_WAVESEL_UPRC,
> +	       tce.base + ATMEL_TC_CMR(tce.channel));
> +	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));

Do this computation at init time.

> +	/* Enable clock and interrupts on RC compare */
> +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +	       tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static int tc_clkevt2_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
> +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +	       tce.base + ATMEL_TC_CCR(tce.channel));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
> +{
> +	unsigned int sr;
> +
> +	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
> +	if (sr & ATMEL_TC_CPCS) {
> +		tce.clkevt.event_handler(&tce.clkevt);
> +		return IRQ_HANDLED;

Isn't an clear irq missing ?

> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static void tc_clkevt2_suspend(struct clock_event_device *d)
> +{
> +	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
> +	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
> +	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
> +	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
> +				ATMEL_TC_CLKSTA);

Who is in charge of powering down the timer ?

> +}
> +
> +static void tc_clkevt2_resume(struct clock_event_device *d)
> +{
> +	/* Restore registers for the channel, RA and RB are not used  */
> +	writel(tce.cache.cmr, tc.base + ATMEL_TC_CMR(tce.channel));
> +	writel(tce.cache.rc, tc.base + ATMEL_TC_RC(tce.channel));
> +	writel(0, tc.base + ATMEL_TC_RA(tce.channel));
> +	writel(0, tc.base + ATMEL_TC_RB(tce.channel));
> +	/* Disable all the interrupts */
> +	writel(0xff, tc.base + ATMEL_TC_IDR(tce.channel));
> +	/* Reenable interrupts that were enabled before suspending */
> +	writel(tce.cache.imr, tc.base + ATMEL_TC_IER(tce.channel));
> +
> +	/* Start the clock if it was used */
> +	if (tce.cache.clken)
> +		writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> +		       tc.base + ATMEL_TC_CCR(tce.channel));
> +}
> +
> +static int __init tc_clkevt_register(struct device_node *node,
> +				     struct regmap *regmap, void __iomem *base,
> +				     int channel, int irq, int bits)
> +{
> +	int ret;
> +
> +	tce.regmap = regmap;
> +	tce.base = base;
> +	tce.channel = channel;
> +	tce.irq = irq;
> +
> +	tce.slow_clk = of_clk_get_by_name(node->parent, "slow_clk");
> +	if (IS_ERR(tce.slow_clk))
> +		return PTR_ERR(tce.slow_clk);
> +
> +	ret = clk_prepare_enable(tce.slow_clk);
> +	if (ret)
> +		return ret;

It is not mandatory to have the slow_clk variable in the structure, you
can declare it locally and for the rest use the timer-of API. That will
save you a lot of line of code.

> +
> +	tce.clk = tcb_clk_get(node, tce.channel);
> +	if (IS_ERR(tce.clk)) {
> +		ret = PTR_ERR(tce.clk);
> +		goto err_slow;
> +	}
> +
> +	snprintf(tce.name, sizeof(tce.name), "%s:%d",
> +		 kbasename(node->parent->full_name), channel);
> +	tce.clkevt.cpumask = cpumask_of(0);
> +	tce.clkevt.name = tce.name;
> +	tce.clkevt.set_next_event = tc_clkevt2_next_event,
> +	tce.clkevt.set_state_shutdown = tc_clkevt2_shutdown,
> +	tce.clkevt.set_state_periodic = tc_clkevt2_set_periodic,
> +	tce.clkevt.set_state_oneshot = tc_clkevt2_set_oneshot,
> +	tce.clkevt.suspend = tc_clkevt2_suspend,
> +	tce.clkevt.resume = tc_clkevt2_resume,
> +
> +	/* try to enable clk to avoid future errors in mode change */
> +	ret = clk_prepare_enable(tce.clk);
> +	if (ret)
> +		goto err_slow;
> +	clk_disable(tce.clk);
> +
> +	clockevents_config_and_register(&tce.clkevt, 32768, 1, BIT(bits) - 1);

s/BIT(bits) - 1/CLOCKSOURCE_MASK/

> +	ret = request_irq(tce.irq, tc_clkevt2_irq, IRQF_TIMER | IRQF_SHARED,
> +			  tce.clkevt.name, &tce);
> +	if (ret)
> +		goto err_clk;
> +
> +	tce.registered = true;
> +
> +	return 0;
> +
> +err_clk:
> +	clk_unprepare(tce.clk);
> +err_slow:
> +	clk_disable_unprepare(tce.slow_clk);
> +
> +	return ret;
> +}

I let you send the split version before continuing the review.

  -- Daniel

> +/*
> + * Clocksource and clockevent using the same channel(s)
> + */
> +static u64 tc_get_cycles(struct clocksource *cs)
> +{
> +	u32 lower, upper;
> +
> +	do {
> +		upper = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1]));
> +		lower = readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	} while (upper != readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[1])));
> +
> +	return (upper << 16) | lower;
> +}
> +
> +static u64 tc_get_cycles32(struct clocksource *cs)
> +{
> +	return readl_relaxed(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +}
> +
> +static u64 notrace tc_sched_clock_read(void)
> +{
> +	return tc_get_cycles(&tc.clksrc);
> +}
> +
> +static u64 notrace tc_sched_clock_read32(void)
> +{
> +	return tc_get_cycles32(&tc.clksrc);
> +}
> +
> +static int tcb_clkevt_next_event(unsigned long delta,
> +				 struct clock_event_device *d)
> +{
> +	u32 old, next, cur;
> +
> +	old = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +	next = old + delta;
> +	writel(next, tc.base + ATMEL_TC_RC(tc.channels[0]));
> +	cur = readl(tc.base + ATMEL_TC_CV(tc.channels[0]));
> +
> +	/* check whether the delta elapsed while setting the register */
> +	if ((next < old && cur < old && cur > next) ||
> +	    (next > old && (cur < old || cur > next))) {
> +		/*
> +		 * Clear the CPCS bit in the status register to avoid
> +		 * generating a spurious interrupt next time a valid
> +		 * timer event is configured.
> +		 */
> +		old = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +		return -ETIME;
> +	}
> +
> +	writel(ATMEL_TC_CPCS, tc.base + ATMEL_TC_IER(tc.channels[0]));
> +
> +	return 0;
> +}
> +
> +static irqreturn_t tc_clkevt_irq(int irq, void *handle)
> +{
> +	unsigned int sr;
> +
> +	sr = readl(tc.base + ATMEL_TC_SR(tc.channels[0]));
> +	if (sr & ATMEL_TC_CPCS) {
> +		tc.clkevt.event_handler(&tc.clkevt);
> +		return IRQ_HANDLED;
> +	}
> +
> +	return IRQ_NONE;
> +}
> +
> +static int tcb_clkevt_oneshot(struct clock_event_device *dev)
> +{
> +	if (clockevent_state_oneshot(dev))
> +		return 0;
> +
> +	/*
> +	 * Because both clockevent devices may share the same IRQ, we don't want
> +	 * the less likely one to stay requested
> +	 */
> +	return request_irq(tc.irq, tc_clkevt_irq, IRQF_TIMER | IRQF_SHARED,
> +			   tc.name, &tc);
> +}
> +
> +static int tcb_clkevt_shutdown(struct clock_event_device *dev)
> +{
> +	writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[0]));
> +	if (tc.bits == 16)
> +		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[1]));
> +
> +	if (!clockevent_state_detached(dev))
> +		free_irq(tc.irq, &tc);
> +
> +	return 0;
> +}
> +
> +static void __init tcb_setup_dual_chan(struct atmel_tcb_clksrc *tc,
> +				       int mck_divisor_idx)
> +{
> +	/* first channel: waveform mode, input mclk/8, clock TIOA on overflow */
> +	writel(mck_divisor_idx			/* likely divide-by-8 */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP	/* free-run */
> +	       | ATMEL_TC_CMR_ACPA(SET)		/* TIOA rises at 0 */
> +	       | ATMEL_TC_CMR_ACPC(CLEAR),	/* (duty cycle 50%) */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
> +	writel(0x0000, tc->base + ATMEL_TC_RA(tc->channels[0]));
> +	writel(0x8000, tc->base + ATMEL_TC_RC(tc->channels[0]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
> +
> +	/* second channel: waveform mode, input TIOA */
> +	writel(ATMEL_TC_CMR_XC(tc->channels[1])		/* input: TIOA */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP,		/* free-run */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[1]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[1]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[1]));
> +
> +	/* chain both channel, we assume the previous channel */
> +	regmap_write(tc->regmap, ATMEL_TC_BMR,
> +		     ATMEL_TC_BMR_TCXC(1 + tc->channels[1], tc->channels[1]));
> +	/* then reset all the timers */
> +	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static void __init tcb_setup_single_chan(struct atmel_tcb_clksrc *tc,
> +					 int mck_divisor_idx)
> +{
> +	/* channel 0:  waveform mode, input mclk/8 */
> +	writel(mck_divisor_idx			/* likely divide-by-8 */
> +	       | ATMEL_TC_CMR_WAVE
> +	       | ATMEL_TC_CMR_WAVESEL_UP,	/* free-run */
> +	       tc->base + ATMEL_TC_CMR(tc->channels[0]));
> +	writel(0xff, tc->base + ATMEL_TC_IDR(tc->channels[0]));	/* no irqs */
> +	writel(ATMEL_TC_CCR_CLKEN, tc->base + ATMEL_TC_CCR(tc->channels[0]));
> +
> +	/* then reset all the timers */
> +	regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static void tc_clksrc_suspend(struct clocksource *cs)
> +{
> +	int i;
> +
> +	for (i = 0; i < 1 + (tc.bits == 16); i++) {
> +		tc.cache[i].cmr = readl(tc.base + ATMEL_TC_CMR(tc.channels[i]));
> +		tc.cache[i].imr = readl(tc.base + ATMEL_TC_IMR(tc.channels[i]));
> +		tc.cache[i].rc = readl(tc.base + ATMEL_TC_RC(tc.channels[i]));
> +		tc.cache[i].clken = !!(readl(tc.base +
> +					     ATMEL_TC_SR(tc.channels[i])) &
> +				       ATMEL_TC_CLKSTA);
> +	}
> +
> +	if (tc.bits == 16)
> +		regmap_read(tc.regmap, ATMEL_TC_BMR, &tc.bmr_cache);
> +}
> +
> +static void tc_clksrc_resume(struct clocksource *cs)
> +{
> +	int i;
> +
> +	for (i = 0; i < 1 + (tc.bits == 16); i++) {
> +		/* Restore registers for the channel, RA and RB are not used  */
> +		writel(tc.cache[i].cmr, tc.base + ATMEL_TC_CMR(tc.channels[i]));
> +		writel(tc.cache[i].rc, tc.base + ATMEL_TC_RC(tc.channels[i]));
> +		writel(0, tc.base + ATMEL_TC_RA(tc.channels[i]));
> +		writel(0, tc.base + ATMEL_TC_RB(tc.channels[i]));
> +		/* Disable all the interrupts */
> +		writel(0xff, tc.base + ATMEL_TC_IDR(tc.channels[i]));
> +		/* Reenable interrupts that were enabled before suspending */
> +		writel(tc.cache[i].imr, tc.base + ATMEL_TC_IER(tc.channels[i]));
> +
> +		/* Start the clock if it was used */
> +		if (tc.cache[i].clken)
> +			writel(ATMEL_TC_CCR_CLKEN, tc.base +
> +			       ATMEL_TC_CCR(tc.channels[i]));
> +	}
> +
> +	/* in case of dual channel, chain channels */
> +	if (tc.bits == 16)
> +		regmap_write(tc.regmap, ATMEL_TC_BMR, tc.bmr_cache);
> +	/* Finally, trigger all the channels*/
> +	regmap_write(tc.regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC);
> +}
> +
> +static int __init tcb_clksrc_register(struct device_node *node,
> +				      struct regmap *regmap, void __iomem *base,
> +				      int channel, int channel1, int irq,
> +				      int bits)
> +{
> +	u32 rate, divided_rate = 0;
> +	int best_divisor_idx = -1;
> +	int i, err = -1;
> +	u64 (*tc_sched_clock)(void);
> +
> +	tc.regmap = regmap;
> +	tc.base = base;
> +	tc.channels[0] = channel;
> +	tc.channels[1] = channel1;
> +	tc.irq = irq;
> +	tc.bits = bits;
> +
> +	tc.clk[0] = tcb_clk_get(node, tc.channels[0]);
> +	if (IS_ERR(tc.clk[0]))
> +		return PTR_ERR(tc.clk[0]);
> +	err = clk_prepare_enable(tc.clk[0]);
> +	if (err) {
> +		pr_debug("can't enable T0 clk\n");
> +		goto err_clk;
> +	}
> +
> +	/* How fast will we be counting?  Pick something over 5 MHz.  */
> +	rate = (u32)clk_get_rate(tc.clk[0]);
> +	for (i = 0; i < 5; i++) {
> +		unsigned int divisor = atmel_tc_divisors[i];
> +		unsigned int tmp;
> +
> +		if (!divisor)
> +			continue;
> +
> +		tmp = rate / divisor;
> +		pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
> +		if (best_divisor_idx > 0) {
> +			if (tmp < 5 * 1000 * 1000)
> +				continue;
> +		}
> +		divided_rate = tmp;
> +		best_divisor_idx = i;
> +	}
> +
> +	if (tc.bits == 32) {
> +		tc.clksrc.read = tc_get_cycles32;
> +		tcb_setup_single_chan(&tc, best_divisor_idx);
> +		tc_sched_clock = tc_sched_clock_read32;
> +		snprintf(tc.name, sizeof(tc.name), "%s:%d",
> +			 kbasename(node->parent->full_name), tc.channels[0]);
> +	} else {
> +		tc.clk[1] = tcb_clk_get(node, tc.channels[1]);
> +		if (IS_ERR(tc.clk[1]))
> +			goto err_disable_t0;
> +
> +		err = clk_prepare_enable(tc.clk[1]);
> +		if (err) {
> +			pr_debug("can't enable T1 clk\n");
> +			goto err_clk1;
> +		}
> +		tc.clksrc.read = tc_get_cycles,
> +		tcb_setup_dual_chan(&tc, best_divisor_idx);
> +		tc_sched_clock = tc_sched_clock_read;
> +		snprintf(tc.name, sizeof(tc.name), "%s:%d,%d",
> +			 kbasename(node->parent->full_name), tc.channels[0],
> +			 tc.channels[1]);
> +	}
> +
> +	pr_debug("%s at %d.%03d MHz\n", tc.name,
> +		 divided_rate / 1000000,
> +		 ((divided_rate + 500000) % 1000000) / 1000);
> +
> +	tc.clksrc.name = tc.name;
> +	tc.clksrc.suspend = tc_clksrc_suspend;
> +	tc.clksrc.resume = tc_clksrc_resume;
> +
> +	err = clocksource_register_hz(&tc.clksrc, divided_rate);
> +	if (err)
> +		goto err_disable_t1;
> +
> +	sched_clock_register(tc_sched_clock, 32, divided_rate);
> +
> +	tc.registered = true;
> +
> +	/* Set up and register clockevents */
> +	tc.clkevt.name = tc.name;
> +	tc.clkevt.cpumask = cpumask_of(0);
> +	tc.clkevt.set_next_event = tcb_clkevt_next_event;
> +	tc.clkevt.set_state_oneshot = tcb_clkevt_oneshot;
> +	tc.clkevt.set_state_shutdown = tcb_clkevt_shutdown;
> +	clockevents_config_and_register(&tc.clkevt, divided_rate, 1,
> +					BIT(tc.bits) - 1);
> +
> +	return 0;
> +
> +err_disable_t1:
> +	if (tc.bits == 16)
> +		clk_disable_unprepare(tc.clk[1]);
> +
> +err_clk1:
> +	if (tc.bits == 16)
> +		clk_put(tc.clk[1]);
> +
> +err_disable_t0:
> +	clk_disable_unprepare(tc.clk[0]);
> +
> +err_clk:
> +	clk_put(tc.clk[0]);
> +
> +	pr_err("%s: unable to register clocksource/clockevent\n",
> +	       tc.clksrc.name);
> +
> +	return err;
> +}
> +
> +static int __init tcb_clksrc_init(struct device_node *node)
> +{
> +	const struct of_device_id *match;
> +	const struct atmel_tcb_info *tcb_info;
> +	struct regmap *regmap;
> +	void __iomem *tcb_base;
> +	u32 channel;
> +	int bits, irq, err, chan1 = -1;
> +
> +	if (tc.registered && tce.registered)
> +		return -ENODEV;
> +
> +	/*
> +	 * The regmap has to be used to access registers that are shared
> +	 * between channels on the same TCB but we keep direct IO access for
> +	 * the counters to avoid the impact on performance
> +	 */
> +	regmap = syscon_node_to_regmap(node->parent);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	tcb_base = of_iomap(node->parent, 0);
> +	if (!tcb_base) {
> +		pr_err("%s +%d %s\n", __FILE__, __LINE__, __func__);
> +		return -ENXIO;
> +	}
> +
> +	match = of_match_node(atmel_tcb_dt_ids, node->parent);
> +	tcb_info = match->data;
> +	bits = tcb_info->bits;
> +
> +	err = of_property_read_u32_index(node, "reg", 0, &channel);
> +	if (err)
> +		return err;
> +
> +	irq = tcb_irq_get(node, channel);
> +	if (irq < 0)
> +		return irq;
> +
> +	if (tc.registered)
> +		return tc_clkevt_register(node, regmap, tcb_base, channel, irq,
> +					  bits);
> +
> +	if (bits == 16) {
> +		of_property_read_u32_index(node, "reg", 1, &chan1);
> +		if (chan1 == -1) {
> +			if (tce.registered) {
> +				pr_err("%s: clocksource needs two channels\n",
> +				       node->parent->full_name);
> +				return -EINVAL;
> +			} else {
> +				return tc_clkevt_register(node, regmap,
> +							  tcb_base, channel,
> +							  irq, bits);
> +			}
> +		}
> +	}
> +
> +	return tcb_clksrc_register(node, regmap, tcb_base, channel, chan1, irq,
> +				   bits);
> +}
> +CLOCKSOURCE_OF_DECLARE(atmel_tcb_clksrc, "atmel,tcb-timer",
> +		       tcb_clksrc_init);
> 


-- 
 <http://www.linaro.org/> Linaro.org ? Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 1/6] ARM: at91: add TCB registers definitions
  2018-06-28 15:15     ` Daniel Lezcano
@ 2018-06-28 18:34       ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-28 18:34 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 28/06/2018 17:15:39+0200, Daniel Lezcano wrote:
> On 19/06/2018 23:19, Alexandre Belloni wrote:
> > Add registers and bits definitions for the timer counter blocks found on
> > Atmel ARM SoCs.
> > 
> > Tested-by: Alexander Dahl <ada@thorsis.com>
> > Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> > Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> > ---
> >  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
> 
> Is the header necessary ? Can it be moved in the .c ?
> 

Ultimately, the clocksource driver will not be the only one to use it.
There is the pwm driver that will be converted (it was converted in the
first version of the series). and then there is a counter driver that
will be submitted once the subsystem is upstreamed.

> >  1 file changed, 216 insertions(+)
> >  create mode 100644 include/soc/at91/atmel_tcb.h
> > 
> > diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
> > new file mode 100644
> > index 000000000000..3ed66031fc76
> > --- /dev/null
> 
> [ ... ]
> 
> > +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
> > +{
> > +	struct clk *clk;
> > +	char clk_name[] = "t0_clk";
> > +
> > +	clk_name[1] += channel;
> 
> clever :)
> 
> > +	clk = of_clk_get_by_name(node->parent, clk_name);
> > +	if (!IS_ERR(clk))
> > +		return clk;
> > +
> > +	return of_clk_get_by_name(node->parent, "t0_clk");
> 
> Why do you want to return clk from t0_clk if another channel is
> requested ? This is prone to error.

The newer TCBs only have one peripheral clocks. The current DT binding only
have t0_clk in that case so whatever the channel, t0_clk is the correct
one.

> 
> I would clarify that at the caller level, if tcb_clk_get fails then try
> with channel zero.

This was hidden from the individual drivers by tclib but this can be
open coded in the drivers.

> 
> > +}
> > +
> > +static inline int tcb_irq_get(struct device_node *node, int channel)
> 
> no inline
> 

IIRC, removing the inline will make linking the kernel fail when there
is more than 2 drivers using the TCBs but I'll try again. Or I can
remove both those functions and open code as you suggest.

> > +{
> > +	int irq;
> > +
> > +	irq = of_irq_get(node->parent, channel);
> > +	if (irq > 0)
> > +		return irq;
> > +
> > +	return of_irq_get(node->parent, 0);
> 
> Same comment than above.
> 
> > +}
> > +
> > +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
> > +
> > +struct atmel_tcb_info {
> > +	int bits;
> > +};
> > +
> > +static const struct atmel_tcb_info atmel_tcb_infos[] = {
> > +	{ .bits = 16 },
> > +	{ .bits = 32 },
> > +};
> 
> Structuring the code with structure is a good practice. However, this is
> too much :)
> 

I was going to add the divisor there but as AVR32 is gone, this is
indeed unnecessary.

> > +static const struct of_device_id atmel_tcb_dt_ids[] = {
> > +	{
> > +		.compatible = "atmel,at91rm9200-tcb",
> > +		.data = &atmel_tcb_infos[0],
> 
> 		.data = (void *)16;
> 
> > +	}, {
> > +		.compatible = "atmel,at91sam9x5-tcb",
> > +		.data = &atmel_tcb_infos[1],
> > +	}, {
> > +		/* sentinel */
> > +	}
> > +};
> > +
> 
> 
> 
> > +#endif /* __SOC_ATMEL_TCB_H */
> > 
> 
> 
> -- 
>  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
> 
> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
> <http://twitter.com/#!/linaroorg> Twitter |
> <http://www.linaro.org/linaro-blog/> Blog
> 

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 1/6] ARM: at91: add TCB registers definitions
@ 2018-06-28 18:34       ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-06-28 18:34 UTC (permalink / raw)
  To: linux-arm-kernel

On 28/06/2018 17:15:39+0200, Daniel Lezcano wrote:
> On 19/06/2018 23:19, Alexandre Belloni wrote:
> > Add registers and bits definitions for the timer counter blocks found on
> > Atmel ARM SoCs.
> > 
> > Tested-by: Alexander Dahl <ada@thorsis.com>
> > Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
> > Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
> > ---
> >  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
> 
> Is the header necessary ? Can it be moved in the .c ?
> 

Ultimately, the clocksource driver will not be the only one to use it.
There is the pwm driver that will be converted (it was converted in the
first version of the series). and then there is a counter driver that
will be submitted once the subsystem is upstreamed.

> >  1 file changed, 216 insertions(+)
> >  create mode 100644 include/soc/at91/atmel_tcb.h
> > 
> > diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
> > new file mode 100644
> > index 000000000000..3ed66031fc76
> > --- /dev/null
> 
> [ ... ]
> 
> > +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
> > +{
> > +	struct clk *clk;
> > +	char clk_name[] = "t0_clk";
> > +
> > +	clk_name[1] += channel;
> 
> clever :)
> 
> > +	clk = of_clk_get_by_name(node->parent, clk_name);
> > +	if (!IS_ERR(clk))
> > +		return clk;
> > +
> > +	return of_clk_get_by_name(node->parent, "t0_clk");
> 
> Why do you want to return clk from t0_clk if another channel is
> requested ? This is prone to error.

The newer TCBs only have one peripheral clocks. The current DT binding only
have t0_clk in that case so whatever the channel, t0_clk is the correct
one.

> 
> I would clarify that at the caller level, if tcb_clk_get fails then try
> with channel zero.

This was hidden from the individual drivers by tclib but this can be
open coded in the drivers.

> 
> > +}
> > +
> > +static inline int tcb_irq_get(struct device_node *node, int channel)
> 
> no inline
> 

IIRC, removing the inline will make linking the kernel fail when there
is more than 2 drivers using the TCBs but I'll try again. Or I can
remove both those functions and open code as you suggest.

> > +{
> > +	int irq;
> > +
> > +	irq = of_irq_get(node->parent, channel);
> > +	if (irq > 0)
> > +		return irq;
> > +
> > +	return of_irq_get(node->parent, 0);
> 
> Same comment than above.
> 
> > +}
> > +
> > +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
> > +
> > +struct atmel_tcb_info {
> > +	int bits;
> > +};
> > +
> > +static const struct atmel_tcb_info atmel_tcb_infos[] = {
> > +	{ .bits = 16 },
> > +	{ .bits = 32 },
> > +};
> 
> Structuring the code with structure is a good practice. However, this is
> too much :)
> 

I was going to add the divisor there but as AVR32 is gone, this is
indeed unnecessary.

> > +static const struct of_device_id atmel_tcb_dt_ids[] = {
> > +	{
> > +		.compatible = "atmel,at91rm9200-tcb",
> > +		.data = &atmel_tcb_infos[0],
> 
> 		.data = (void *)16;
> 
> > +	}, {
> > +		.compatible = "atmel,at91sam9x5-tcb",
> > +		.data = &atmel_tcb_infos[1],
> > +	}, {
> > +		/* sentinel */
> > +	}
> > +};
> > +
> 
> 
> 
> > +#endif /* __SOC_ATMEL_TCB_H */
> > 
> 
> 
> -- 
>  <http://www.linaro.org/> Linaro.org ? Open source software for ARM SoCs
> 
> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
> <http://twitter.com/#!/linaroorg> Twitter |
> <http://www.linaro.org/linaro-blog/> Blog
> 

-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 1/6] ARM: at91: add TCB registers definitions
  2018-06-28 18:34       ` Alexandre Belloni
@ 2018-06-28 19:55         ` Daniel Lezcano
  -1 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 19:55 UTC (permalink / raw)
  To: Alexandre Belloni
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 28/06/2018 20:34, Alexandre Belloni wrote:
> On 28/06/2018 17:15:39+0200, Daniel Lezcano wrote:
>> On 19/06/2018 23:19, Alexandre Belloni wrote:
>>> Add registers and bits definitions for the timer counter blocks found on
>>> Atmel ARM SoCs.
>>>
>>> Tested-by: Alexander Dahl <ada@thorsis.com>
>>> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
>>> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
>>> ---
>>>  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
>>
>> Is the header necessary ? Can it be moved in the .c ?
>>
> 
> Ultimately, the clocksource driver will not be the only one to use it.
> There is the pwm driver that will be converted (it was converted in the
> first version of the series). and then there is a counter driver that
> will be submitted once the subsystem is upstreamed.

Ok.

>>>  1 file changed, 216 insertions(+)
>>>  create mode 100644 include/soc/at91/atmel_tcb.h
>>>
>>> diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
>>> new file mode 100644
>>> index 000000000000..3ed66031fc76
>>> --- /dev/null
>>
>> [ ... ]
>>
>>> +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
>>> +{
>>> +	struct clk *clk;
>>> +	char clk_name[] = "t0_clk";
>>> +
>>> +	clk_name[1] += channel;
>>
>> clever :)
>>
>>> +	clk = of_clk_get_by_name(node->parent, clk_name);
>>> +	if (!IS_ERR(clk))
>>> +		return clk;
>>> +
>>> +	return of_clk_get_by_name(node->parent, "t0_clk");
>>
>> Why do you want to return clk from t0_clk if another channel is
>> requested ? This is prone to error.
> 
> The newer TCBs only have one peripheral clocks. The current DT binding only
> have t0_clk in that case so whatever the channel, t0_clk is the correct
> one.
> 
>>
>> I would clarify that at the caller level, if tcb_clk_get fails then try
>> with channel zero.
> 
> This was hidden from the individual drivers by tclib but this can be
> open coded in the drivers.
> 
>>
>>> +}
>>> +
>>> +static inline int tcb_irq_get(struct device_node *node, int channel)
>>
>> no inline
>>
> 
> IIRC, removing the inline will make linking the kernel fail when there
> is more than 2 drivers using the TCBs but I'll try again. Or I can
> remove both those functions and open code as you suggest.

Yes, preferable to remove these functions.

>>> +{
>>> +	int irq;
>>> +
>>> +	irq = of_irq_get(node->parent, channel);
>>> +	if (irq > 0)
>>> +		return irq;
>>> +
>>> +	return of_irq_get(node->parent, 0);
>>
>> Same comment than above.
>>
>>> +}
>>> +
>>> +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
>>> +
>>> +struct atmel_tcb_info {
>>> +	int bits;
>>> +};
>>> +
>>> +static const struct atmel_tcb_info atmel_tcb_infos[] = {
>>> +	{ .bits = 16 },
>>> +	{ .bits = 32 },
>>> +};
>>
>> Structuring the code with structure is a good practice. However, this is
>> too much :)
>>
> 
> I was going to add the divisor there but as AVR32 is gone, this is
> indeed unnecessary.
> 
>>> +static const struct of_device_id atmel_tcb_dt_ids[] = {
>>> +	{
>>> +		.compatible = "atmel,at91rm9200-tcb",
>>> +		.data = &atmel_tcb_infos[0],
>>
>> 		.data = (void *)16;
>>
>>> +	}, {
>>> +		.compatible = "atmel,at91sam9x5-tcb",
>>> +		.data = &atmel_tcb_infos[1],
>>> +	}, {
>>> +		/* sentinel */
>>> +	}
>>> +};
>>> +
>>
>>
>>
>>> +#endif /* __SOC_ATMEL_TCB_H */
>>>
>>
>>
>> -- 
>>  <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
>>
>> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
>> <http://twitter.com/#!/linaroorg> Twitter |
>> <http://www.linaro.org/linaro-blog/> Blog
>>
> 


-- 
 <http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog


^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 1/6] ARM: at91: add TCB registers definitions
@ 2018-06-28 19:55         ` Daniel Lezcano
  0 siblings, 0 replies; 38+ messages in thread
From: Daniel Lezcano @ 2018-06-28 19:55 UTC (permalink / raw)
  To: linux-arm-kernel

On 28/06/2018 20:34, Alexandre Belloni wrote:
> On 28/06/2018 17:15:39+0200, Daniel Lezcano wrote:
>> On 19/06/2018 23:19, Alexandre Belloni wrote:
>>> Add registers and bits definitions for the timer counter blocks found on
>>> Atmel ARM SoCs.
>>>
>>> Tested-by: Alexander Dahl <ada@thorsis.com>
>>> Tested-by: Andras Szemzo <szemzo.andras@gmail.com>
>>> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
>>> ---
>>>  include/soc/at91/atmel_tcb.h | 216 +++++++++++++++++++++++++++++++++++
>>
>> Is the header necessary ? Can it be moved in the .c ?
>>
> 
> Ultimately, the clocksource driver will not be the only one to use it.
> There is the pwm driver that will be converted (it was converted in the
> first version of the series). and then there is a counter driver that
> will be submitted once the subsystem is upstreamed.

Ok.

>>>  1 file changed, 216 insertions(+)
>>>  create mode 100644 include/soc/at91/atmel_tcb.h
>>>
>>> diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
>>> new file mode 100644
>>> index 000000000000..3ed66031fc76
>>> --- /dev/null
>>
>> [ ... ]
>>
>>> +static inline struct clk *tcb_clk_get(struct device_node *node, int channel)
>>> +{
>>> +	struct clk *clk;
>>> +	char clk_name[] = "t0_clk";
>>> +
>>> +	clk_name[1] += channel;
>>
>> clever :)
>>
>>> +	clk = of_clk_get_by_name(node->parent, clk_name);
>>> +	if (!IS_ERR(clk))
>>> +		return clk;
>>> +
>>> +	return of_clk_get_by_name(node->parent, "t0_clk");
>>
>> Why do you want to return clk from t0_clk if another channel is
>> requested ? This is prone to error.
> 
> The newer TCBs only have one peripheral clocks. The current DT binding only
> have t0_clk in that case so whatever the channel, t0_clk is the correct
> one.
> 
>>
>> I would clarify that at the caller level, if tcb_clk_get fails then try
>> with channel zero.
> 
> This was hidden from the individual drivers by tclib but this can be
> open coded in the drivers.
> 
>>
>>> +}
>>> +
>>> +static inline int tcb_irq_get(struct device_node *node, int channel)
>>
>> no inline
>>
> 
> IIRC, removing the inline will make linking the kernel fail when there
> is more than 2 drivers using the TCBs but I'll try again. Or I can
> remove both those functions and open code as you suggest.

Yes, preferable to remove these functions.

>>> +{
>>> +	int irq;
>>> +
>>> +	irq = of_irq_get(node->parent, channel);
>>> +	if (irq > 0)
>>> +		return irq;
>>> +
>>> +	return of_irq_get(node->parent, 0);
>>
>> Same comment than above.
>>
>>> +}
>>> +
>>> +static const u8 atmel_tc_divisors[5] = { 2, 8, 32, 128, 0, };
>>> +
>>> +struct atmel_tcb_info {
>>> +	int bits;
>>> +};
>>> +
>>> +static const struct atmel_tcb_info atmel_tcb_infos[] = {
>>> +	{ .bits = 16 },
>>> +	{ .bits = 32 },
>>> +};
>>
>> Structuring the code with structure is a good practice. However, this is
>> too much :)
>>
> 
> I was going to add the divisor there but as AVR32 is gone, this is
> indeed unnecessary.
> 
>>> +static const struct of_device_id atmel_tcb_dt_ids[] = {
>>> +	{
>>> +		.compatible = "atmel,at91rm9200-tcb",
>>> +		.data = &atmel_tcb_infos[0],
>>
>> 		.data = (void *)16;
>>
>>> +	}, {
>>> +		.compatible = "atmel,at91sam9x5-tcb",
>>> +		.data = &atmel_tcb_infos[1],
>>> +	}, {
>>> +		/* sentinel */
>>> +	}
>>> +};
>>> +
>>
>>
>>
>>> +#endif /* __SOC_ATMEL_TCB_H */
>>>
>>
>>
>> -- 
>>  <http://www.linaro.org/> Linaro.org ? Open source software for ARM SoCs
>>
>> Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
>> <http://twitter.com/#!/linaroorg> Twitter |
>> <http://www.linaro.org/linaro-blog/> Blog
>>
> 


-- 
 <http://www.linaro.org/> Linaro.org ? Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
  2018-06-28 16:24     ` Daniel Lezcano
@ 2018-07-06 15:18       ` Alexandre Belloni
  -1 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-07-06 15:18 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Thomas Gleixner, Nicolas Ferre, Alexander Dahl,
	Sebastian Andrzej Siewior, linux-arm-kernel, linux-kernel

On 28/06/2018 18:24:12+0200, Daniel Lezcano wrote:
> > +static void tc_clkevt2_clk_enable(struct clock_event_device *d)
> > +{
> > +	if (tce.clk_enabled)
> > +		return;
> 
> Why this test ?
> 
> > +	clk_enable(tce.clk);
> > +	tce.clk_enabled = true;
> > +}
> >
> > +static int tc_clkevt2_stop(struct clock_event_device *d)
> > +{
> > +	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static int tc_clkevt2_shutdown(struct clock_event_device *d)
> > +{
> > +	tc_clkevt2_stop(d);
> > +	if (!clockevent_state_detached(d))
> 
> Why this test ?
> 
> > +		tc_clkevt2_clk_disable(d);
> > +
> > +	return 0;
> > +}
> > +
> > +/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
> > + * because using one of the divided clocks would usually mean the
> > + * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
> > + *
> > + * A divided clock could be good for high resolution timers, since
> > + * 30.5 usec resolution can seem "low".
> > + */
> > +static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
> > +{
> > +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> > +		tc_clkevt2_stop(d);
> 
> Why these tests ? :)
> 

All these test are to be nice with preempt-rt else we would be disabling
then reenabling the clock in an atomic context which fails with the
preempt-rt patch.

> > +	/* By not making the gentime core emulate periodic mode on top
> > +	 * of oneshot, we get lower overhead and improved accuracy.
> > +	 */
> > +	tc_clkevt2_clk_enable(d);
> > +
> > +	/* slow clock, count up to RC, then irq and restart */
> > +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
> > +	       ATMEL_TC_CMR_WAVESEL_UPRC,
> > +	       tce.base + ATMEL_TC_CMR(tce.channel));
> > +	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));
> 
> Do this computation at init time.
> 

Does it really matter? All the members are constant so no code will be
generated for the computation. This ends up being an immediate value
computing it at init time would mean storing it in the structure which
would incur an extra load.

> > +	/* Enable clock and interrupts on RC compare */
> > +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> > +	       tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static int tc_clkevt2_next_event(unsigned long delta,
> > +				 struct clock_event_device *d)
> > +{
> > +	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> > +	       tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
> > +{
> > +	unsigned int sr;
> > +
> > +	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
> > +	if (sr & ATMEL_TC_CPCS) {
> > +		tce.clkevt.event_handler(&tce.clkevt);
> > +		return IRQ_HANDLED;
> 
> Isn't an clear irq missing ?
> 

It is cleared on read.

> > +	}
> > +
> > +	return IRQ_NONE;
> > +}
> > +
> > +static void tc_clkevt2_suspend(struct clock_event_device *d)
> > +{
> > +	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
> > +	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
> > +	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
> > +	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
> > +				ATMEL_TC_CLKSTA);
> 
> Who is in charge of powering down the timer ?
> 

The platform code stops all the clocks when going to suspend.


-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks
@ 2018-07-06 15:18       ` Alexandre Belloni
  0 siblings, 0 replies; 38+ messages in thread
From: Alexandre Belloni @ 2018-07-06 15:18 UTC (permalink / raw)
  To: linux-arm-kernel

On 28/06/2018 18:24:12+0200, Daniel Lezcano wrote:
> > +static void tc_clkevt2_clk_enable(struct clock_event_device *d)
> > +{
> > +	if (tce.clk_enabled)
> > +		return;
> 
> Why this test ?
> 
> > +	clk_enable(tce.clk);
> > +	tce.clk_enabled = true;
> > +}
> >
> > +static int tc_clkevt2_stop(struct clock_event_device *d)
> > +{
> > +	writel(0xff, tce.base + ATMEL_TC_IDR(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKDIS, tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static int tc_clkevt2_shutdown(struct clock_event_device *d)
> > +{
> > +	tc_clkevt2_stop(d);
> > +	if (!clockevent_state_detached(d))
> 
> Why this test ?
> 
> > +		tc_clkevt2_clk_disable(d);
> > +
> > +	return 0;
> > +}
> > +
> > +/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
> > + * because using one of the divided clocks would usually mean the
> > + * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
> > + *
> > + * A divided clock could be good for high resolution timers, since
> > + * 30.5 usec resolution can seem "low".
> > + */
> > +static int tc_clkevt2_set_oneshot(struct clock_event_device *d)
> > +{
> > +	if (clockevent_state_oneshot(d) || clockevent_state_periodic(d))
> > +		tc_clkevt2_stop(d);
> 
> Why these tests ? :)
> 

All these test are to be nice with preempt-rt else we would be disabling
then reenabling the clock in an atomic context which fails with the
preempt-rt patch.

> > +	/* By not making the gentime core emulate periodic mode on top
> > +	 * of oneshot, we get lower overhead and improved accuracy.
> > +	 */
> > +	tc_clkevt2_clk_enable(d);
> > +
> > +	/* slow clock, count up to RC, then irq and restart */
> > +	writel(ATMEL_TC_CMR_TCLK(4) | ATMEL_TC_CMR_WAVE |
> > +	       ATMEL_TC_CMR_WAVESEL_UPRC,
> > +	       tce.base + ATMEL_TC_CMR(tce.channel));
> > +	writel((32768 + HZ / 2) / HZ, tce.base + ATMEL_TC_RC(tce.channel));
> 
> Do this computation at init time.
> 

Does it really matter? All the members are constant so no code will be
generated for the computation. This ends up being an immediate value
computing it at init time would mean storing it in the structure which
would incur an extra load.

> > +	/* Enable clock and interrupts on RC compare */
> > +	writel(ATMEL_TC_CPCS, tce.base + ATMEL_TC_IER(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> > +	       tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static int tc_clkevt2_next_event(unsigned long delta,
> > +				 struct clock_event_device *d)
> > +{
> > +	writel(delta, tce.base + ATMEL_TC_RC(tce.channel));
> > +	writel(ATMEL_TC_CCR_CLKEN | ATMEL_TC_CCR_SWTRG,
> > +	       tce.base + ATMEL_TC_CCR(tce.channel));
> > +
> > +	return 0;
> > +}
> > +
> > +static irqreturn_t tc_clkevt2_irq(int irq, void *handle)
> > +{
> > +	unsigned int sr;
> > +
> > +	sr = readl(tce.base + ATMEL_TC_SR(tce.channel));
> > +	if (sr & ATMEL_TC_CPCS) {
> > +		tce.clkevt.event_handler(&tce.clkevt);
> > +		return IRQ_HANDLED;
> 
> Isn't an clear irq missing ?
> 

It is cleared on read.

> > +	}
> > +
> > +	return IRQ_NONE;
> > +}
> > +
> > +static void tc_clkevt2_suspend(struct clock_event_device *d)
> > +{
> > +	tce.cache.cmr = readl(tce.base + ATMEL_TC_CMR(tce.channel));
> > +	tce.cache.imr = readl(tce.base + ATMEL_TC_IMR(tce.channel));
> > +	tce.cache.rc = readl(tce.base + ATMEL_TC_RC(tce.channel));
> > +	tce.cache.clken = !!(readl(tce.base + ATMEL_TC_SR(tce.channel)) &
> > +				ATMEL_TC_CLKSTA);
> 
> Who is in charge of powering down the timer ?
> 

The platform code stops all the clocks when going to suspend.


-- 
Alexandre Belloni, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2018-07-06 15:18 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-19 21:19 [PATCH v5 0/6] clocksource: rework Atmel TCB timer driver Alexandre Belloni
2018-06-19 21:19 ` Alexandre Belloni
2018-06-19 21:19 ` [PATCH v5 1/6] ARM: at91: add TCB registers definitions Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni
2018-06-28 15:15   ` Daniel Lezcano
2018-06-28 15:15     ` Daniel Lezcano
2018-06-28 18:34     ` Alexandre Belloni
2018-06-28 18:34       ` Alexandre Belloni
2018-06-28 19:55       ` Daniel Lezcano
2018-06-28 19:55         ` Daniel Lezcano
2018-06-19 21:19 ` [PATCH v5 2/6] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni
2018-06-20  9:03   ` Thomas Gleixner
2018-06-20  9:03     ` Thomas Gleixner
2018-06-20  9:46     ` Alexandre Belloni
2018-06-20  9:46       ` Alexandre Belloni
2018-06-20 10:07       ` Thomas Gleixner
2018-06-20 10:07         ` Thomas Gleixner
2018-06-20 10:32         ` Alexandre Belloni
2018-06-20 10:32           ` Alexandre Belloni
2018-06-20 10:58           ` Thomas Gleixner
2018-06-20 10:58             ` Thomas Gleixner
2018-06-20 11:18             ` Alexandre Belloni
2018-06-20 11:18               ` Alexandre Belloni
2018-06-20 11:55               ` Thomas Gleixner
2018-06-20 11:55                 ` Thomas Gleixner
2018-06-28 16:24   ` Daniel Lezcano
2018-06-28 16:24     ` Daniel Lezcano
2018-07-06 15:18     ` Alexandre Belloni
2018-07-06 15:18       ` Alexandre Belloni
2018-06-19 21:19 ` [PATCH v5 3/6] clocksource/drivers: atmel-pit: make option silent Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni
2018-06-19 21:19 ` [PATCH v5 4/6] ARM: at91: Implement clocksource selection Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni
2018-06-19 21:19 ` [PATCH v5 5/6] ARM: configs: at91: use new TCB timer driver Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni
2018-06-19 21:19 ` [PATCH v5 6/6] ARM: configs: at91: unselect PIT Alexandre Belloni
2018-06-19 21:19   ` Alexandre Belloni

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.