Commit 81b3b271 authored by Laurent Pinchart's avatar Laurent Pinchart

clocksource: sh_cmt: Add support for multiple channels per device

CMT hardware devices can support multiple channels, with global
registers and per-channel registers. The sh_cmt driver currently models
the hardware with one Linux device per channel. This model makes it
difficult to handle global registers in a clean way.

Add support for a new model that uses one Linux device per timer with
multiple channels per device. This requires changes to platform data,
add new channel configuration fields.

Support for the legacy model is kept and will be removed after all
platforms switch to the new model.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
parent fb28a659
...@@ -53,7 +53,16 @@ struct sh_cmt_device; ...@@ -53,7 +53,16 @@ struct sh_cmt_device;
* channel registers block. All other versions have a shared start/stop register * channel registers block. All other versions have a shared start/stop register
* located in the global space. * located in the global space.
* *
* Note that CMT0 on r8a73a4, r8a7790 and r8a7791, while implementing 32-bit * 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. * channels only, is a 48-bit gen2 CMT with the 48-bit channels unavailable.
*/ */
...@@ -85,10 +94,14 @@ struct sh_cmt_info { ...@@ -85,10 +94,14 @@ struct sh_cmt_info {
struct sh_cmt_channel { struct sh_cmt_channel {
struct sh_cmt_device *cmt; struct sh_cmt_device *cmt;
unsigned int index;
void __iomem *base; unsigned int index; /* Index in the documentation */
unsigned int hwidx; /* Real hardware index */
void __iomem *iostart;
void __iomem *ioctrl;
unsigned int timer_bit;
unsigned long flags; unsigned long flags;
unsigned long match_value; unsigned long match_value;
unsigned long next_match_value; unsigned long next_match_value;
...@@ -105,6 +118,7 @@ struct sh_cmt_device { ...@@ -105,6 +118,7 @@ struct sh_cmt_device {
struct platform_device *pdev; struct platform_device *pdev;
const struct sh_cmt_info *info; const struct sh_cmt_info *info;
bool legacy;
void __iomem *mapbase_ch; void __iomem *mapbase_ch;
void __iomem *mapbase; void __iomem *mapbase;
...@@ -112,6 +126,9 @@ struct sh_cmt_device { ...@@ -112,6 +126,9 @@ struct sh_cmt_device {
struct sh_cmt_channel *channels; struct sh_cmt_channel *channels;
unsigned int num_channels; unsigned int num_channels;
bool has_clockevent;
bool has_clocksource;
}; };
#define SH_CMT16_CMCSR_CMF (1 << 7) #define SH_CMT16_CMCSR_CMF (1 << 7)
...@@ -223,41 +240,47 @@ static const struct sh_cmt_info sh_cmt_info[] = { ...@@ -223,41 +240,47 @@ static const struct sh_cmt_info sh_cmt_info[] = {
static inline unsigned long sh_cmt_read_cmstr(struct sh_cmt_channel *ch) static inline unsigned long sh_cmt_read_cmstr(struct sh_cmt_channel *ch)
{ {
return ch->cmt->info->read_control(ch->cmt->mapbase, 0); if (ch->iostart)
return ch->cmt->info->read_control(ch->iostart, 0);
else
return ch->cmt->info->read_control(ch->cmt->mapbase, 0);
} }
static inline unsigned long sh_cmt_read_cmcsr(struct sh_cmt_channel *ch) static inline void sh_cmt_write_cmstr(struct sh_cmt_channel *ch,
unsigned long value)
{ {
return ch->cmt->info->read_control(ch->base, CMCSR); if (ch->iostart)
ch->cmt->info->write_control(ch->iostart, 0, value);
else
ch->cmt->info->write_control(ch->cmt->mapbase, 0, value);
} }
static inline unsigned long sh_cmt_read_cmcnt(struct sh_cmt_channel *ch) static inline unsigned long sh_cmt_read_cmcsr(struct sh_cmt_channel *ch)
{ {
return ch->cmt->info->read_count(ch->base, CMCNT); return ch->cmt->info->read_control(ch->ioctrl, CMCSR);
} }
static inline void sh_cmt_write_cmstr(struct sh_cmt_channel *ch, static inline void sh_cmt_write_cmcsr(struct sh_cmt_channel *ch,
unsigned long value) unsigned long value)
{ {
ch->cmt->info->write_control(ch->cmt->mapbase, 0, value); ch->cmt->info->write_control(ch->ioctrl, CMCSR, value);
} }
static inline void sh_cmt_write_cmcsr(struct sh_cmt_channel *ch, static inline unsigned long sh_cmt_read_cmcnt(struct sh_cmt_channel *ch)
unsigned long value)
{ {
ch->cmt->info->write_control(ch->base, CMCSR, value); return ch->cmt->info->read_count(ch->ioctrl, CMCNT);
} }
static inline void sh_cmt_write_cmcnt(struct sh_cmt_channel *ch, static inline void sh_cmt_write_cmcnt(struct sh_cmt_channel *ch,
unsigned long value) unsigned long value)
{ {
ch->cmt->info->write_count(ch->base, CMCNT, value); ch->cmt->info->write_count(ch->ioctrl, CMCNT, value);
} }
static inline void sh_cmt_write_cmcor(struct sh_cmt_channel *ch, static inline void sh_cmt_write_cmcor(struct sh_cmt_channel *ch,
unsigned long value) unsigned long value)
{ {
ch->cmt->info->write_count(ch->base, CMCOR, value); ch->cmt->info->write_count(ch->ioctrl, CMCOR, value);
} }
static unsigned long sh_cmt_get_counter(struct sh_cmt_channel *ch, static unsigned long sh_cmt_get_counter(struct sh_cmt_channel *ch,
...@@ -286,7 +309,6 @@ static DEFINE_RAW_SPINLOCK(sh_cmt_lock); ...@@ -286,7 +309,6 @@ static DEFINE_RAW_SPINLOCK(sh_cmt_lock);
static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start) static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start)
{ {
struct sh_timer_config *cfg = ch->cmt->pdev->dev.platform_data;
unsigned long flags, value; unsigned long flags, value;
/* start stop register shared by multiple timer channels */ /* start stop register shared by multiple timer channels */
...@@ -294,9 +316,9 @@ static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start) ...@@ -294,9 +316,9 @@ static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start)
value = sh_cmt_read_cmstr(ch); value = sh_cmt_read_cmstr(ch);
if (start) if (start)
value |= 1 << cfg->timer_bit; value |= 1 << ch->timer_bit;
else else
value &= ~(1 << cfg->timer_bit); value &= ~(1 << ch->timer_bit);
sh_cmt_write_cmstr(ch, value); sh_cmt_write_cmstr(ch, value);
raw_spin_unlock_irqrestore(&sh_cmt_lock, flags); raw_spin_unlock_irqrestore(&sh_cmt_lock, flags);
...@@ -790,27 +812,72 @@ static void sh_cmt_register_clockevent(struct sh_cmt_channel *ch, ...@@ -790,27 +812,72 @@ static void sh_cmt_register_clockevent(struct sh_cmt_channel *ch,
static int sh_cmt_register(struct sh_cmt_channel *ch, const char *name, static int sh_cmt_register(struct sh_cmt_channel *ch, const char *name,
bool clockevent, bool clocksource) bool clockevent, bool clocksource)
{ {
if (clockevent) if (clockevent) {
ch->cmt->has_clockevent = true;
sh_cmt_register_clockevent(ch, name); sh_cmt_register_clockevent(ch, name);
}
if (clocksource) if (clocksource) {
ch->cmt->has_clocksource = true;
sh_cmt_register_clocksource(ch, name); sh_cmt_register_clocksource(ch, name);
}
return 0; return 0;
} }
static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index, static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index,
struct sh_cmt_device *cmt) unsigned int hwidx, bool clockevent,
bool clocksource, struct sh_cmt_device *cmt)
{ {
struct sh_timer_config *cfg = cmt->pdev->dev.platform_data;
int irq; int irq;
int ret; int ret;
/* Skip unused channels. */
if (!clockevent && !clocksource)
return 0;
ch->cmt = cmt; ch->cmt = cmt;
ch->base = cmt->mapbase_ch;
ch->index = index; ch->index = index;
ch->hwidx = hwidx;
/*
* Compute the address of the channel control register block. For the
* timers with a per-channel start/stop register, compute its address
* as well.
*
* For legacy configuration the address has been mapped explicitly.
*/
if (cmt->legacy) {
ch->ioctrl = cmt->mapbase_ch;
} else {
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_CMT_32BIT_FAST:
/*
* The 32-bit "fast" timer has a single channel at hwidx
* 5 but is located at offset 0x40 instead of 0x60 for
* some reason.
*/
ch->ioctrl = cmt->mapbase + 0x40;
break;
case SH_CMT_48BIT_GEN2:
ch->iostart = cmt->mapbase + ch->hwidx * 0x100;
ch->ioctrl = ch->iostart + 0x10;
break;
}
}
if (cmt->legacy)
irq = platform_get_irq(cmt->pdev, 0);
else
irq = platform_get_irq(cmt->pdev, ch->index);
irq = platform_get_irq(cmt->pdev, 0);
if (irq < 0) { if (irq < 0) {
dev_err(&cmt->pdev->dev, "ch%u: failed to get irq\n", dev_err(&cmt->pdev->dev, "ch%u: failed to get irq\n",
ch->index); ch->index);
...@@ -825,9 +892,15 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index, ...@@ -825,9 +892,15 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index,
ch->match_value = ch->max_match_value; ch->match_value = ch->max_match_value;
raw_spin_lock_init(&ch->lock); raw_spin_lock_init(&ch->lock);
if (cmt->legacy) {
ch->timer_bit = ch->hwidx;
} else {
ch->timer_bit = cmt->info->model == SH_CMT_48BIT_GEN2
? 0 : ch->hwidx;
}
ret = sh_cmt_register(ch, dev_name(&cmt->pdev->dev), ret = sh_cmt_register(ch, dev_name(&cmt->pdev->dev),
cfg->clockevent_rating != 0, clockevent, clocksource);
cfg->clocksource_rating != 0);
if (ret) { if (ret) {
dev_err(&cmt->pdev->dev, "ch%u: registration failed\n", dev_err(&cmt->pdev->dev, "ch%u: registration failed\n",
ch->index); ch->index);
...@@ -847,97 +920,180 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index, ...@@ -847,97 +920,180 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index,
return 0; return 0;
} }
static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev) static int sh_cmt_map_memory(struct sh_cmt_device *cmt)
{ {
struct sh_timer_config *cfg = pdev->dev.platform_data; struct resource *mem;
struct resource *res, *res2;
int ret;
ret = -ENXIO;
cmt->pdev = pdev; mem = platform_get_resource(cmt->pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&cmt->pdev->dev, "failed to get I/O memory\n");
return -ENXIO;
}
if (!cfg) { cmt->mapbase = ioremap_nocache(mem->start, resource_size(mem));
dev_err(&cmt->pdev->dev, "missing platform data\n"); if (cmt->mapbase == NULL) {
goto err0; dev_err(&cmt->pdev->dev, "failed to remap I/O memory\n");
return -ENXIO;
} }
return 0;
}
static int sh_cmt_map_memory_legacy(struct sh_cmt_device *cmt)
{
struct sh_timer_config *cfg = cmt->pdev->dev.platform_data;
struct resource *res, *res2;
/* map memory, let mapbase_ch point to our channel */
res = platform_get_resource(cmt->pdev, IORESOURCE_MEM, 0); res = platform_get_resource(cmt->pdev, IORESOURCE_MEM, 0);
if (!res) { if (!res) {
dev_err(&cmt->pdev->dev, "failed to get I/O memory\n"); dev_err(&cmt->pdev->dev, "failed to get I/O memory\n");
goto err0; return -ENXIO;
} }
/* optional resource for the shared timer start/stop register */
res2 = platform_get_resource(cmt->pdev, IORESOURCE_MEM, 1);
/* map memory, let mapbase_ch point to our channel */
cmt->mapbase_ch = ioremap_nocache(res->start, resource_size(res)); cmt->mapbase_ch = ioremap_nocache(res->start, resource_size(res));
if (cmt->mapbase_ch == NULL) { if (cmt->mapbase_ch == NULL) {
dev_err(&cmt->pdev->dev, "failed to remap I/O memory\n"); dev_err(&cmt->pdev->dev, "failed to remap I/O memory\n");
goto err0; return -ENXIO;
} }
/* optional resource for the shared timer start/stop register */
res2 = platform_get_resource(cmt->pdev, IORESOURCE_MEM, 1);
/* map second resource for CMSTR */ /* map second resource for CMSTR */
cmt->mapbase = ioremap_nocache(res2 ? res2->start : cmt->mapbase = ioremap_nocache(res2 ? res2->start :
res->start - cfg->channel_offset, res->start - cfg->channel_offset,
res2 ? resource_size(res2) : 2); res2 ? resource_size(res2) : 2);
if (cmt->mapbase == NULL) { if (cmt->mapbase == NULL) {
dev_err(&cmt->pdev->dev, "failed to remap I/O second memory\n"); dev_err(&cmt->pdev->dev, "failed to remap I/O second memory\n");
goto err1; iounmap(cmt->mapbase_ch);
return -ENXIO;
} }
/* get hold of clock */ /* identify the model based on the resources */
if (resource_size(res) == 6)
cmt->info = &sh_cmt_info[SH_CMT_16BIT];
else if (res2 && (resource_size(res2) == 4))
cmt->info = &sh_cmt_info[SH_CMT_48BIT_GEN2];
else
cmt->info = &sh_cmt_info[SH_CMT_32BIT];
return 0;
}
static void sh_cmt_unmap_memory(struct sh_cmt_device *cmt)
{
iounmap(cmt->mapbase);
if (cmt->mapbase_ch)
iounmap(cmt->mapbase_ch);
}
static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
{
struct sh_timer_config *cfg = pdev->dev.platform_data;
const struct platform_device_id *id = pdev->id_entry;
unsigned int hw_channels;
int ret;
memset(cmt, 0, sizeof(*cmt));
cmt->pdev = pdev;
if (!cfg) {
dev_err(&cmt->pdev->dev, "missing platform data\n");
return -ENXIO;
}
cmt->info = (const struct sh_cmt_info *)id->driver_data;
cmt->legacy = cmt->info ? false : true;
/* Get hold of clock. */
cmt->clk = clk_get(&cmt->pdev->dev, "cmt_fck"); cmt->clk = clk_get(&cmt->pdev->dev, "cmt_fck");
if (IS_ERR(cmt->clk)) { if (IS_ERR(cmt->clk)) {
dev_err(&cmt->pdev->dev, "cannot get clock\n"); dev_err(&cmt->pdev->dev, "cannot get clock\n");
ret = PTR_ERR(cmt->clk); return PTR_ERR(cmt->clk);
goto err2;
} }
ret = clk_prepare(cmt->clk); ret = clk_prepare(cmt->clk);
if (ret < 0) if (ret < 0)
goto err3; goto err_clk_put;
/* identify the model based on the resources */ /*
if (resource_size(res) == 6) * Map the memory resource(s). We need to support both the legacy
cmt->info = &sh_cmt_info[SH_CMT_16BIT]; * platform device configuration (with one device per channel) and the
else if (res2 && (resource_size(res2) == 4)) * new version (with multiple channels per device).
cmt->info = &sh_cmt_info[SH_CMT_48BIT_GEN2]; */
if (cmt->legacy)
ret = sh_cmt_map_memory_legacy(cmt);
else else
cmt->info = &sh_cmt_info[SH_CMT_32BIT]; ret = sh_cmt_map_memory(cmt);
cmt->channels = kzalloc(sizeof(*cmt->channels), GFP_KERNEL); if (ret < 0)
goto err_clk_unprepare;
/* Allocate and setup the channels. */
if (cmt->legacy) {
cmt->num_channels = 1;
hw_channels = 0;
} else {
cmt->num_channels = hweight8(cfg->channels_mask);
hw_channels = cfg->channels_mask;
}
cmt->channels = kzalloc(cmt->num_channels * sizeof(*cmt->channels),
GFP_KERNEL);
if (cmt->channels == NULL) { if (cmt->channels == NULL) {
ret = -ENOMEM; ret = -ENOMEM;
goto err4; goto err_unmap;
} }
cmt->num_channels = 1; if (cmt->legacy) {
ret = sh_cmt_setup_channel(&cmt->channels[0],
cfg->timer_bit, cfg->timer_bit,
cfg->clockevent_rating != 0,
cfg->clocksource_rating != 0, cmt);
if (ret < 0)
goto err_unmap;
} else {
unsigned int mask = hw_channels;
unsigned int i;
ret = sh_cmt_setup_channel(&cmt->channels[0], cfg->timer_bit, cmt); /*
if (ret < 0) * Use the first channel as a clock event device and the second
goto err4; * channel as a clock source. If only one channel is available
* use it for both.
*/
for (i = 0; i < cmt->num_channels; ++i) {
unsigned int 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;
mask &= ~(1 << hwidx);
}
}
platform_set_drvdata(pdev, cmt); platform_set_drvdata(pdev, cmt);
return 0; return 0;
err4:
err_unmap:
kfree(cmt->channels); kfree(cmt->channels);
sh_cmt_unmap_memory(cmt);
err_clk_unprepare:
clk_unprepare(cmt->clk); clk_unprepare(cmt->clk);
err3: err_clk_put:
clk_put(cmt->clk); clk_put(cmt->clk);
err2:
iounmap(cmt->mapbase);
err1:
iounmap(cmt->mapbase_ch);
err0:
return ret; return ret;
} }
static int sh_cmt_probe(struct platform_device *pdev) static int sh_cmt_probe(struct platform_device *pdev)
{ {
struct sh_cmt_device *cmt = platform_get_drvdata(pdev); struct sh_cmt_device *cmt = platform_get_drvdata(pdev);
struct sh_timer_config *cfg = pdev->dev.platform_data;
int ret; int ret;
if (!is_early_platform_device(pdev)) { if (!is_early_platform_device(pdev)) {
...@@ -966,7 +1122,7 @@ static int sh_cmt_probe(struct platform_device *pdev) ...@@ -966,7 +1122,7 @@ static int sh_cmt_probe(struct platform_device *pdev)
return 0; return 0;
out: out:
if (cfg->clockevent_rating || cfg->clocksource_rating) if (cmt->has_clockevent || cmt->has_clocksource)
pm_runtime_irq_safe(&pdev->dev); pm_runtime_irq_safe(&pdev->dev);
else else
pm_runtime_idle(&pdev->dev); pm_runtime_idle(&pdev->dev);
...@@ -979,12 +1135,24 @@ static int sh_cmt_remove(struct platform_device *pdev) ...@@ -979,12 +1135,24 @@ static int sh_cmt_remove(struct platform_device *pdev)
return -EBUSY; /* cannot unregister clockevent and clocksource */ return -EBUSY; /* cannot unregister clockevent and clocksource */
} }
static const struct platform_device_id sh_cmt_id_table[] = {
{ "sh_cmt", 0 },
{ "sh-cmt-16", (kernel_ulong_t)&sh_cmt_info[SH_CMT_16BIT] },
{ "sh-cmt-32", (kernel_ulong_t)&sh_cmt_info[SH_CMT_32BIT] },
{ "sh-cmt-32-fast", (kernel_ulong_t)&sh_cmt_info[SH_CMT_32BIT_FAST] },
{ "sh-cmt-48", (kernel_ulong_t)&sh_cmt_info[SH_CMT_48BIT] },
{ "sh-cmt-48-gen2", (kernel_ulong_t)&sh_cmt_info[SH_CMT_48BIT_GEN2] },
{ }
};
MODULE_DEVICE_TABLE(platform, sh_cmt_id_table);
static struct platform_driver sh_cmt_device_driver = { static struct platform_driver sh_cmt_device_driver = {
.probe = sh_cmt_probe, .probe = sh_cmt_probe,
.remove = sh_cmt_remove, .remove = sh_cmt_remove,
.driver = { .driver = {
.name = "sh_cmt", .name = "sh_cmt",
} },
.id_table = sh_cmt_id_table,
}; };
static int __init sh_cmt_init(void) static int __init sh_cmt_init(void)
......
...@@ -7,6 +7,7 @@ struct sh_timer_config { ...@@ -7,6 +7,7 @@ struct sh_timer_config {
int timer_bit; int timer_bit;
unsigned long clockevent_rating; unsigned long clockevent_rating;
unsigned long clocksource_rating; unsigned long clocksource_rating;
unsigned int channels_mask;
}; };
#endif /* __SH_TIMER_H__ */ #endif /* __SH_TIMER_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment