Skip to content

Commit

Permalink
clocksource/drivers/sh_cmt: Access registers according to spec
Browse files Browse the repository at this point in the history
[ Upstream commit 3f44f71 ]

Documentation for most CMTs say that it takes two input clocks before
changes propagate to the timer. This is especially relevant when the timer
is stopped to change further settings.

Implement the delays according to the spec. To avoid unnecessary delays in
atomic mode, also check if the to-be-written value actually differs.

CMCNT is a bit special because testing showed that it requires 3 cycles to
propagate, which affects all CMTs. Also, the WRFLAG needs to be checked
before writing. This fixes "cannot clear CMCNT" messages which occur often
on R-Car Gen4 SoCs, but only very rarely on older SoCs for some reason.

Fixes: 81b3b27 ("clocksource: sh_cmt: Add support for multiple channels per device")
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20221130210609.7718-1-wsa+renesas@sang-engineering.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Wolfram Sang authored and gregkh committed Dec 31, 2022
1 parent a47de2f commit 0f8e6fe
Showing 1 changed file with 55 additions and 33 deletions.
88 changes: 55 additions & 33 deletions drivers/clocksource/sh_cmt.c
Expand Up @@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/module.h>
Expand Down Expand Up @@ -116,6 +117,7 @@ struct sh_cmt_device {
void __iomem *mapbase;
struct clk *clk;
unsigned long rate;
unsigned int reg_delay;

raw_spinlock_t lock; /* Protect the shared start/stop register */

Expand Down Expand Up @@ -247,10 +249,17 @@ static inline u32 sh_cmt_read_cmstr(struct sh_cmt_channel *ch)

static inline void sh_cmt_write_cmstr(struct sh_cmt_channel *ch, u32 value)
{
if (ch->iostart)
ch->cmt->info->write_control(ch->iostart, 0, value);
else
ch->cmt->info->write_control(ch->cmt->mapbase, 0, value);
u32 old_value = sh_cmt_read_cmstr(ch);

if (value != old_value) {
if (ch->iostart) {
ch->cmt->info->write_control(ch->iostart, 0, value);
udelay(ch->cmt->reg_delay);
} else {
ch->cmt->info->write_control(ch->cmt->mapbase, 0, value);
udelay(ch->cmt->reg_delay);
}
}
}

static inline u32 sh_cmt_read_cmcsr(struct sh_cmt_channel *ch)
Expand All @@ -260,22 +269,46 @@ static inline u32 sh_cmt_read_cmcsr(struct sh_cmt_channel *ch)

static inline void sh_cmt_write_cmcsr(struct sh_cmt_channel *ch, u32 value)
{
ch->cmt->info->write_control(ch->ioctrl, CMCSR, value);
u32 old_value = sh_cmt_read_cmcsr(ch);

if (value != old_value) {
ch->cmt->info->write_control(ch->ioctrl, CMCSR, value);
udelay(ch->cmt->reg_delay);
}
}

static inline u32 sh_cmt_read_cmcnt(struct sh_cmt_channel *ch)
{
return ch->cmt->info->read_count(ch->ioctrl, CMCNT);
}

static inline void sh_cmt_write_cmcnt(struct sh_cmt_channel *ch, u32 value)
static inline int sh_cmt_write_cmcnt(struct sh_cmt_channel *ch, u32 value)
{
/* Tests showed that we need to wait 3 clocks here */
unsigned int cmcnt_delay = DIV_ROUND_UP(3 * ch->cmt->reg_delay, 2);
u32 reg;

if (ch->cmt->info->model > SH_CMT_16BIT) {
int ret = read_poll_timeout_atomic(sh_cmt_read_cmcsr, reg,
!(reg & SH_CMT32_CMCSR_WRFLG),
1, cmcnt_delay, false, ch);
if (ret < 0)
return ret;
}

ch->cmt->info->write_count(ch->ioctrl, CMCNT, value);
udelay(cmcnt_delay);
return 0;
}

static inline void sh_cmt_write_cmcor(struct sh_cmt_channel *ch, u32 value)
{
ch->cmt->info->write_count(ch->ioctrl, CMCOR, value);
u32 old_value = ch->cmt->info->read_count(ch->ioctrl, CMCOR);

if (value != old_value) {
ch->cmt->info->write_count(ch->ioctrl, CMCOR, value);
udelay(ch->cmt->reg_delay);
}
}

static u32 sh_cmt_get_counter(struct sh_cmt_channel *ch, u32 *has_wrapped)
Expand Down Expand Up @@ -319,7 +352,7 @@ static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start)

static int sh_cmt_enable(struct sh_cmt_channel *ch)
{
int k, ret;
int ret;

dev_pm_syscore_device(&ch->cmt->pdev->dev, true);

Expand Down Expand Up @@ -347,26 +380,9 @@ static int sh_cmt_enable(struct sh_cmt_channel *ch)
}

sh_cmt_write_cmcor(ch, 0xffffffff);
sh_cmt_write_cmcnt(ch, 0);

/*
* According to the sh73a0 user's manual, as CMCNT can be operated
* only by the RCLK (Pseudo 32 kHz), there's one restriction on
* modifying CMCNT register; two RCLK cycles are necessary before
* this register is either read or any modification of the value
* it holds is reflected in the LSI's actual operation.
*
* While at it, we're supposed to clear out the CMCNT as of this
* moment, so make sure it's processed properly here. This will
* take RCLKx2 at maximum.
*/
for (k = 0; k < 100; k++) {
if (!sh_cmt_read_cmcnt(ch))
break;
udelay(1);
}
ret = sh_cmt_write_cmcnt(ch, 0);

if (sh_cmt_read_cmcnt(ch)) {
if (ret || sh_cmt_read_cmcnt(ch)) {
dev_err(&ch->cmt->pdev->dev, "ch%u: cannot clear CMCNT\n",
ch->index);
ret = -ETIMEDOUT;
Expand Down Expand Up @@ -987,8 +1003,8 @@ MODULE_DEVICE_TABLE(of, sh_cmt_of_table);

static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
{
unsigned int mask;
unsigned int i;
unsigned int mask, i;
unsigned long rate;
int ret;

cmt->pdev = pdev;
Expand Down Expand Up @@ -1024,10 +1040,16 @@ static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
if (ret < 0)
goto err_clk_unprepare;

if (cmt->info->width == 16)
cmt->rate = clk_get_rate(cmt->clk) / 512;
else
cmt->rate = clk_get_rate(cmt->clk) / 8;
rate = clk_get_rate(cmt->clk);
if (!rate) {
ret = -EINVAL;
goto err_clk_disable;
}

/* We shall wait 2 input clks after register writes */
if (cmt->info->model >= SH_CMT_48BIT)
cmt->reg_delay = DIV_ROUND_UP(2UL * USEC_PER_SEC, rate);
cmt->rate = rate / (cmt->info->width == 16 ? 512 : 8);

/* Map the memory resource(s). */
ret = sh_cmt_map_memory(cmt);
Expand Down

0 comments on commit 0f8e6fe

Please sign in to comment.