[PATCH v5 1/4] timer: atmel_tcb_timer: add atmel_tcb driver
Clément Léger
clement.leger at bootlin.com
Mon Mar 7 12:08:41 CET 2022
Le Mon, 7 Mar 2022 10:57:02 +0000,
<Claudiu.Beznea at microchip.com> a écrit :
> On 04.03.2022 13:05, Clément Léger wrote:
> > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> >
> > Add a driver for the timer counter block that can be found on sama5d2.
> > This driver will be used when booting under OP-TEE since the pit timer
> > which is part of the SYSC is secured. Channel 1 & 2 are configured to
> > be chained together which allows to have a 64bits counter.
> >
> > Signed-off-by: Clément Léger <clement.leger at bootlin.com>
> > ---
> > MAINTAINERS | 1 +
> > drivers/timer/Kconfig | 8 ++
> > drivers/timer/Makefile | 1 +
> > drivers/timer/atmel_tcb_timer.c | 155 ++++++++++++++++++++++++++++++++
> > 4 files changed, 165 insertions(+)
> > create mode 100644 drivers/timer/atmel_tcb_timer.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 0f39bc6bc9..e30389dc3e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -352,6 +352,7 @@ F: arch/arm/mach-at91/
> > F: board/atmel/
> > F: drivers/cpu/at91_cpu.c
> > F: drivers/misc/microchip_flexcom.c
> > +F: drivers/timer/atmel_tcb_timer.c
> > F: include/dt-bindings/mfd/atmel-flexcom.h
> > F: drivers/timer/mchp-pit64b-timer.c
> >
> > diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
> > index 8913142654..8fad59b81a 100644
> > --- a/drivers/timer/Kconfig
> > +++ b/drivers/timer/Kconfig
> > @@ -96,6 +96,14 @@ config ATMEL_PIT_TIMER
> > it is designed to offer maximum accuracy and efficient management,
> > even for systems with long response time.
> >
> > +config ATMEL_TCB_TIMER
> > + bool "Atmel timer counter support"
> > + depends on TIMER
> > + depends on ARCH_AT91
> > + help
> > + Select this to enable the use of the timer counter as a monotonic
> > + counter.
> > +
> > config CADENCE_TTC_TIMER
> > bool "Cadence TTC (Triple Timer Counter)"
> > depends on TIMER
> > diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
> > index e2bd530eb0..58da6c1e84 100644
> > --- a/drivers/timer/Makefile
> > +++ b/drivers/timer/Makefile
> > @@ -10,6 +10,7 @@ obj-$(CONFIG_ARC_TIMER) += arc_timer.o
> > obj-$(CONFIG_AST_TIMER) += ast_timer.o
> > obj-$(CONFIG_ATCPIT100_TIMER) += atcpit100_timer.o
> > obj-$(CONFIG_ATMEL_PIT_TIMER) += atmel_pit_timer.o
> > +obj-$(CONFIG_ATMEL_TCB_TIMER) += atmel_tcb_timer.o
> > obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence-ttc.o
> > obj-$(CONFIG_DESIGNWARE_APB_TIMER) += dw-apb-timer.o
> > obj-$(CONFIG_MPC83XX_TIMER) += mpc83xx_timer.o
> > diff --git a/drivers/timer/atmel_tcb_timer.c b/drivers/timer/atmel_tcb_timer.c
> > new file mode 100644
> > index 0000000000..b73b367227
> > --- /dev/null
> > +++ b/drivers/timer/atmel_tcb_timer.c
> > @@ -0,0 +1,155 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Copyright (C) 2022 Microchip Corporation
> > + *
> > + * Author: Clément Léger <clement.leger at bootlin.com>
> > + */
> > +
> > +#include <common.h>
> > +#include <clk.h>
> > +#include <dm.h>
> > +#include <timer.h>
> > +#include <asm/io.h>
> > +#include <linux/bitops.h>
> > +
> > +#define TCB_CHAN(chan) ((chan) * 0x40)
> > +
> > +#define TCB_CCR(chan) (0x0 + TCB_CHAN(chan))
> > +#define TCB_CCR_SWTRG 0x4
>
> This is not used
>
> > +#define TCB_CCR_CLKEN 0x1
>
> Here
>
> > +
> > +#define TCB_CMR(chan) (0x4 + TCB_CHAN(chan))
> > +#define TCB_CMR_WAVE BIT(15)
>
> here
>
> > +#define TCB_CMR_TIMER_CLOCK0 0
>
> Datasheet names it CLOCK1.
Indeed, I'll rename that.
>
> > +#define TCB_CMR_XC1 6
> > +#define TCB_CMR_ACPA_SET BIT(16)
>
> here
>
> > +#define TCB_CMR_ACPC_CLEAR (2 << 18)
> > +
> > +#define TCB_CV(chan) (0x10 + TCB_CHAN(chan))
> > +
> > +#define TCB_RA(chan) (0x14 + TCB_CHAN(chan))
> > +#define TCB_RC(chan) (0x1c + TCB_CHAN(chan))
> > +
> > +#define TCB_IDR(chan) (0x28 + TCB_CHAN(chan))
> > +
> > +#define TCB_BCR 0xc0
> > +#define TCB_BCR_SYNC 0x1
>
> and here: there a mixture of using BIT() and using plain bit defines: would
> be good to stick to one or the other.
Acked, I guess I'll go with plain bit defines.
>
> > +
> > +#define TCB_BMR 0xc4
> > +#define TCB_BMR_TC1XC1S_TIOA0 (2 << 2)
> > +
> > +#define TCB_WPMR 0xe4
> > +#define TCB_WPMR_WAKEY 0x54494d
> > +
> > +struct atmel_tcb_plat {
> > + void __iomem *base;
> > +};
> > +
> > +static u64 atmel_tcb_get_count(struct udevice *dev)
> > +{
> > + struct atmel_tcb_plat *plat = dev_get_plat(dev);
> > + u64 cv0 = 0;
> > + u64 cv1 = 0;
> > +
> > + do {
> > + cv1 = readl(plat->base + TCB_CV(1));
> > + cv0 = readl(plat->base + TCB_CV(0));
> > + } while (readl(plat->base + TCB_CV(1)) != cv1);
> > +
> > + cv0 |= cv1 << 32;
> > +
> > + return cv0;
> > +}
> > +
> > +static void atmel_tcb_configure(void __iomem *base)
> > +{
> > + /* Disable write protection */
> > + writel(TCB_WPMR_WAKEY, base + TCB_WPMR);
> > +
> > + /* Disable all irqs for both channel 0 & 1 */
> > + writel(0xff, base + TCB_IDR(0));
> > + writel(0xff, base + TCB_IDR(1));
> > +
> > + /*
> > + * In order to avoid wrapping, use a 64 bit counter by chaining
> > + * two channels.
> > + * Channel 0 is configured to generate a clock on TIOA0 which is cleared
> > + * when reaching 0x80000000 and set when reaching 0.
> > + */
> > + writel(TCB_CMR_TIMER_CLOCK0 | TCB_CMR_WAVE | TCB_CMR_ACPA_SET
> > + | TCB_CMR_ACPC_CLEAR, base + TCB_CMR(0));
> > + writel(0x80000000, base + TCB_RC(0));
> > + writel(0x1, base + TCB_RA(0));
>
> I see this set to zero in Linux. Is it set to 1 to avoid "set when reaching
> 0" described above?
Yes, do you want me to add a comment ?
>
> > + writel(TCB_CCR_CLKEN, base + TCB_CCR(0));
> > +
> > + /* Channel 1 is configured to use TIOA0 as input */
> > + writel(TCB_CMR_XC1 | TCB_CMR_WAVE, base + TCB_CMR(1));
> > + writel(TCB_CCR_CLKEN, base + TCB_CCR(1));
> > +
> > + /* Set XC1 input to be TIOA0 (ie output of Channel 0) */
> > + writel(TCB_BMR_TC1XC1S_TIOA0, base + TCB_BMR);
> > +
> > + /* Sync & start all timers */
> > + writel(TCB_BCR_SYNC, base + TCB_BCR);
> > +}
> > +
> > +static int atmel_tcb_probe(struct udevice *dev)
> > +{
> > + struct atmel_tcb_plat *plat = dev_get_plat(dev);
> > + struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
> > + struct clk clk;
> > + ulong clk_rate;
> > + int ret;
> > +
> > + if (!device_is_compatible(dev->parent, "atmel,sama5d2-tcb"))
>
> Is this necessary?
If we want to be pedantic about the bindings, yes... THis ensure that
we respect the node hierarchy that have been described in the Linux
bindings.
Thanks for the review,
--
Clément Léger,
Embedded Linux and Kernel engineer at Bootlin
https://bootlin.com
More information about the U-Boot
mailing list