/* * The CMT comes in 5 different identified flavours, depending not only on the * SoC but also on the particular instance. The following table lists the main * characteristics of those flavours. * * 16B 32B 32B-F 48B R-Car Gen2 * ----------------------------------------------------------------------------- * Channels 2 1/4 1 6 2/8 * Control Width 16 16 16 16 32 * Counter Width 16 32 32 32/48 32/48 * Shared Start/Stop Y Y Y Y N * * The r8a73a4 / R-Car Gen2 version has a per-channel start/stop register * located in the channel registers block. All other versions have a shared * start/stop register located in the global space. * * Channels are indexed from 0 to N-1 in the documentation. The channel index * infers the start/stop bit position in the control register and the channel * registers block address. Some CMT instances have a subset of channels * available, in which case the index in the documentation doesn't match the * "real" index as implemented in hardware. This is for instance the case with * CMT0 on r8a7740, which is a 32-bit variant with a single channel numbered 0 * in the documentation but using start/stop bit 5 and having its registers * block at 0x60. * * Similarly CMT0 on r8a73a4, r8a7790 and r8a7791, while implementing 32-bit * channels only, is a 48-bit gen2 CMT with the 48-bit channels unavailable.
*/
staticvoid sh_cmt_clock_event_program_verify(struct sh_cmt_channel *ch, int absolute)
{
u32 value = ch->next_match_value;
u32 new_match;
u32 delay = 0;
u32 now = 0;
u32 has_wrapped;
now = sh_cmt_get_counter(ch, &has_wrapped);
ch->flags |= FLAG_REPROGRAM; /* force reprogram */
if (has_wrapped) { /* we're competing with the interrupt handler. * -> let the interrupt handler reprogram the timer. * -> interrupt number two handles the event.
*/
ch->flags |= FLAG_SKIPEVENT; return;
}
if (absolute)
now = 0;
do { /* reprogram the timer hardware, * but don't save the new match value yet.
*/
new_match = now + value + delay; if (new_match > ch->max_match_value)
new_match = ch->max_match_value;
sh_cmt_write_cmcor(ch, new_match);
now = sh_cmt_get_counter(ch, &has_wrapped); if (has_wrapped && (new_match > ch->match_value)) { /* we are changing to a greater match value, * so this wrap must be caused by the counter * matching the old value. * -> first interrupt reprograms the timer. * -> interrupt number two handles the event.
*/
ch->flags |= FLAG_SKIPEVENT; break;
}
if (has_wrapped) { /* we are changing to a smaller match value, * so the wrap must be caused by the counter * matching the new value. * -> save programmed match value. * -> let isr handle the event.
*/
ch->match_value = new_match; break;
}
/* be safe: verify hardware settings */ if (now < new_match) { /* timer value is below match value, all good. * this makes sure we won't miss any match events. * -> save programmed match value. * -> let isr handle the event.
*/
ch->match_value = new_match; break;
}
/* the counter has reached a value greater * than our new match value. and since the * has_wrapped flag isn't set we must have * programmed a too close event. * -> increase delay and retry.
*/ if (delay)
delay <<= 1; else
delay = 1;
if (!delay)
dev_warn(&ch->cmt->pdev->dev, "ch%u: too long delay\n",
ch->index);
} while (delay);
}
staticvoid __sh_cmt_set_next(struct sh_cmt_channel *ch, unsignedlong delta)
{ if (delta > ch->max_match_value)
dev_warn(&ch->cmt->pdev->dev, "ch%u: delta out of range\n",
ch->index);
/* update clock source counter to begin with if enabled * the wrap flag should be cleared by the timer specific * isr before we end up here.
*/ if (ch->flags & FLAG_CLOCKSOURCE)
ch->total_cycles += ch->match_value + 1;
if (!(ch->flags & FLAG_REPROGRAM))
ch->next_match_value = ch->max_match_value;
ch->flags |= FLAG_IRQCONTEXT;
if (ch->flags & FLAG_CLOCKEVENT) { if (!(ch->flags & FLAG_SKIPEVENT)) { if (clockevent_state_oneshot(&ch->ced)) {
ch->next_match_value = ch->max_match_value;
ch->flags |= FLAG_REPROGRAM;
}
ch->ced.event_handler(&ch->ced);
}
}
ch->flags &= ~FLAG_SKIPEVENT;
raw_spin_lock_irqsave(&ch->lock, flags);
if (ch->flags & FLAG_REPROGRAM) {
ch->flags &= ~FLAG_REPROGRAM;
sh_cmt_clock_event_program_verify(ch, 1);
if (ch->flags & FLAG_CLOCKEVENT) if ((clockevent_state_shutdown(&ch->ced))
|| (ch->match_value == ch->next_match_value))
ch->flags &= ~FLAG_REPROGRAM;
}
ch->flags &= ~FLAG_IRQCONTEXT;
raw_spin_unlock_irqrestore(&ch->lock, flags);
return IRQ_HANDLED;
}
staticint sh_cmt_start(struct sh_cmt_channel *ch, unsignedlong flag)
{ int ret = 0; unsignedlong flags;
if (flag & FLAG_CLOCKSOURCE)
pm_runtime_get_sync(&ch->cmt->pdev->dev);
raw_spin_lock_irqsave(&ch->lock, flags);
if (!(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) { if (flag & FLAG_CLOCKEVENT)
pm_runtime_get_sync(&ch->cmt->pdev->dev);
ret = sh_cmt_enable(ch);
}
if (ret) goto out;
ch->flags |= flag;
/* setup timeout if no clockevent */ if (ch->cmt->num_channels == 1 &&
flag == FLAG_CLOCKSOURCE && (!(ch->flags & FLAG_CLOCKEVENT)))
__sh_cmt_set_next(ch, ch->max_match_value);
out:
raw_spin_unlock_irqrestore(&ch->lock, flags);
f = ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE);
ch->flags &= ~flag;
if (f && !(ch->flags & (FLAG_CLOCKEVENT | FLAG_CLOCKSOURCE))) {
sh_cmt_disable(ch); if (flag & FLAG_CLOCKEVENT)
pm_runtime_put(&ch->cmt->pdev->dev);
}
/* adjust the timeout to maximum if only clocksource left */ if ((flag == FLAG_CLOCKEVENT) && (ch->flags & FLAG_CLOCKSOURCE))
__sh_cmt_set_next(ch, ch->max_match_value);
raw_spin_unlock_irqrestore(&ch->lock, flags);
if (flag & FLAG_CLOCKSOURCE)
pm_runtime_put(&ch->cmt->pdev->dev);
}
/* * Compute the address of the channel control register block. For the * timers with a per-channel start/stop register, compute its address * as well.
*/ switch (cmt->info->model) { case SH_CMT_16BIT:
ch->ioctrl = cmt->mapbase + 2 + ch->hwidx * 6; break; case SH_CMT_32BIT: case SH_CMT_48BIT:
ch->ioctrl = cmt->mapbase + 0x10 + ch->hwidx * 0x10; break; case SH_CMT0_RCAR_GEN2: case SH_CMT1_RCAR_GEN2:
ch->iostart = cmt->mapbase + ch->hwidx * 0x100;
ch->ioctrl = ch->iostart + 0x10;
ch->timer_bit = 0;
/* Enable the clock supply to the channel */
value = ioread32(cmt->mapbase + CMCLKE);
value |= BIT(hwidx);
iowrite32(value, cmt->mapbase + CMCLKE); break;
}
/* Get hold of clock. */
cmt->clk = clk_get(&cmt->pdev->dev, "fck"); if (IS_ERR(cmt->clk)) {
dev_err(&cmt->pdev->dev, "cannot get clock\n"); return PTR_ERR(cmt->clk);
}
ret = clk_prepare(cmt->clk); if (ret < 0) goto err_clk_put;
/* Determine clock rate. */
ret = clk_enable(cmt->clk); if (ret < 0) goto err_clk_unprepare;
rate = clk_get_rate(cmt->clk); if (!rate) {
ret = -EINVAL; goto err_clk_disable;
}
/* Map the memory resource(s). */
ret = sh_cmt_map_memory(cmt); if (ret < 0) goto err_clk_disable;
/* Allocate and setup the channels. */
cmt->num_channels = hweight8(cmt->hw_channels);
cmt->channels = kcalloc(cmt->num_channels, sizeof(*cmt->channels),
GFP_KERNEL); if (cmt->channels == NULL) {
ret = -ENOMEM; goto err_unmap;
}
/* * Use the first channel as a clock event device and the second channel * as a clock source. If only one channel is available use it for both.
*/ for (i = 0, mask = cmt->hw_channels; i < cmt->num_channels; ++i) { unsignedint hwidx = ffs(mask) - 1; bool clocksource = i == 1 || cmt->num_channels == 1; bool clockevent = i == 0;
ret = sh_cmt_setup_channel(&cmt->channels[i], i, hwidx,
clockevent, clocksource, cmt); if (ret < 0) goto err_unmap;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.