clocksource: sh_mtu2: Add support for multiple channels per device
MTU2 hardware devices can support multiple channels, with global registers and per-channel registers. The sh_mtu2 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: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Tested-by: Wolfram Sang <wsa@sang-engineering.com>
This commit is contained in:
parent
207e21a973
commit
faf3f4f8c8
|
@ -54,6 +54,9 @@ struct sh_mtu2_device {
|
||||||
|
|
||||||
struct sh_mtu2_channel *channels;
|
struct sh_mtu2_channel *channels;
|
||||||
unsigned int num_channels;
|
unsigned int num_channels;
|
||||||
|
|
||||||
|
bool legacy;
|
||||||
|
bool has_clockevent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static DEFINE_RAW_SPINLOCK(sh_mtu2_lock);
|
static DEFINE_RAW_SPINLOCK(sh_mtu2_lock);
|
||||||
|
@ -163,8 +166,12 @@ static inline unsigned long sh_mtu2_read(struct sh_mtu2_channel *ch, int reg_nr)
|
||||||
{
|
{
|
||||||
unsigned long offs;
|
unsigned long offs;
|
||||||
|
|
||||||
if (reg_nr == TSTR)
|
if (reg_nr == TSTR) {
|
||||||
return ioread8(ch->mtu->mapbase);
|
if (ch->mtu->legacy)
|
||||||
|
return ioread8(ch->mtu->mapbase);
|
||||||
|
else
|
||||||
|
return ioread8(ch->mtu->mapbase + 0x280);
|
||||||
|
}
|
||||||
|
|
||||||
offs = mtu2_reg_offs[reg_nr];
|
offs = mtu2_reg_offs[reg_nr];
|
||||||
|
|
||||||
|
@ -180,8 +187,10 @@ static inline void sh_mtu2_write(struct sh_mtu2_channel *ch, int reg_nr,
|
||||||
unsigned long offs;
|
unsigned long offs;
|
||||||
|
|
||||||
if (reg_nr == TSTR) {
|
if (reg_nr == TSTR) {
|
||||||
iowrite8(value, ch->mtu->mapbase);
|
if (ch->mtu->legacy)
|
||||||
return;
|
return iowrite8(value, ch->mtu->mapbase);
|
||||||
|
else
|
||||||
|
return iowrite8(value, ch->mtu->mapbase + 0x280);
|
||||||
}
|
}
|
||||||
|
|
||||||
offs = mtu2_reg_offs[reg_nr];
|
offs = mtu2_reg_offs[reg_nr];
|
||||||
|
@ -353,109 +362,168 @@ static void sh_mtu2_register_clockevent(struct sh_mtu2_channel *ch,
|
||||||
static int sh_mtu2_register(struct sh_mtu2_channel *ch, const char *name,
|
static int sh_mtu2_register(struct sh_mtu2_channel *ch, const char *name,
|
||||||
bool clockevent)
|
bool clockevent)
|
||||||
{
|
{
|
||||||
if (clockevent)
|
if (clockevent) {
|
||||||
|
ch->mtu->has_clockevent = true;
|
||||||
sh_mtu2_register_clockevent(ch, name);
|
sh_mtu2_register_clockevent(ch, name);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_mtu2_setup_channel(struct sh_mtu2_channel *ch,
|
static int sh_mtu2_setup_channel(struct sh_mtu2_channel *ch, unsigned int index,
|
||||||
struct sh_mtu2_device *mtu)
|
struct sh_mtu2_device *mtu)
|
||||||
{
|
{
|
||||||
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
static const unsigned int channel_offsets[] = {
|
||||||
|
0x300, 0x380, 0x000,
|
||||||
|
};
|
||||||
|
bool clockevent;
|
||||||
|
|
||||||
ch->mtu = mtu;
|
ch->mtu = mtu;
|
||||||
ch->index = cfg->timer_bit;
|
|
||||||
|
|
||||||
ch->irq = platform_get_irq(mtu->pdev, 0);
|
if (mtu->legacy) {
|
||||||
|
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||||
|
|
||||||
|
clockevent = cfg->clockevent_rating != 0;
|
||||||
|
|
||||||
|
ch->irq = platform_get_irq(mtu->pdev, 0);
|
||||||
|
ch->base = mtu->mapbase - cfg->channel_offset;
|
||||||
|
ch->index = cfg->timer_bit;
|
||||||
|
} else {
|
||||||
|
char name[6];
|
||||||
|
|
||||||
|
clockevent = true;
|
||||||
|
|
||||||
|
sprintf(name, "tgi%ua", index);
|
||||||
|
ch->irq = platform_get_irq_byname(mtu->pdev, name);
|
||||||
|
ch->base = mtu->mapbase + channel_offsets[index];
|
||||||
|
ch->index = index;
|
||||||
|
}
|
||||||
|
|
||||||
if (ch->irq < 0) {
|
if (ch->irq < 0) {
|
||||||
|
/* Skip channels with no declared interrupt. */
|
||||||
|
if (!mtu->legacy)
|
||||||
|
return 0;
|
||||||
|
|
||||||
dev_err(&mtu->pdev->dev, "ch%u: failed to get irq\n",
|
dev_err(&mtu->pdev->dev, "ch%u: failed to get irq\n",
|
||||||
ch->index);
|
ch->index);
|
||||||
return ch->irq;
|
return ch->irq;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sh_mtu2_register(ch, dev_name(&mtu->pdev->dev),
|
return sh_mtu2_register(ch, dev_name(&mtu->pdev->dev), clockevent);
|
||||||
cfg->clockevent_rating != 0);
|
}
|
||||||
|
|
||||||
|
static int sh_mtu2_map_memory(struct sh_mtu2_device *mtu)
|
||||||
|
{
|
||||||
|
struct resource *res;
|
||||||
|
|
||||||
|
res = platform_get_resource(mtu->pdev, IORESOURCE_MEM, 0);
|
||||||
|
if (!res) {
|
||||||
|
dev_err(&mtu->pdev->dev, "failed to get I/O memory\n");
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtu->mapbase = ioremap_nocache(res->start, resource_size(res));
|
||||||
|
if (mtu->mapbase == NULL)
|
||||||
|
return -ENXIO;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In legacy platform device configuration (with one device per channel)
|
||||||
|
* the resource points to the channel base address.
|
||||||
|
*/
|
||||||
|
if (mtu->legacy) {
|
||||||
|
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||||
|
mtu->mapbase += cfg->channel_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sh_mtu2_unmap_memory(struct sh_mtu2_device *mtu)
|
||||||
|
{
|
||||||
|
if (mtu->legacy) {
|
||||||
|
struct sh_timer_config *cfg = mtu->pdev->dev.platform_data;
|
||||||
|
mtu->mapbase -= cfg->channel_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
iounmap(mtu->mapbase);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
|
static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
|
||||||
struct platform_device *pdev)
|
struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
struct sh_timer_config *cfg = pdev->dev.platform_data;
|
||||||
struct resource *res;
|
const struct platform_device_id *id = pdev->id_entry;
|
||||||
void __iomem *base;
|
unsigned int i;
|
||||||
int ret;
|
int ret;
|
||||||
ret = -ENXIO;
|
|
||||||
|
|
||||||
mtu->pdev = pdev;
|
mtu->pdev = pdev;
|
||||||
|
mtu->legacy = id->driver_data;
|
||||||
|
|
||||||
if (!cfg) {
|
if (mtu->legacy && !cfg) {
|
||||||
dev_err(&mtu->pdev->dev, "missing platform data\n");
|
dev_err(&mtu->pdev->dev, "missing platform data\n");
|
||||||
goto err0;
|
return -ENXIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, mtu);
|
/* Get hold of clock. */
|
||||||
|
|
||||||
res = platform_get_resource(mtu->pdev, IORESOURCE_MEM, 0);
|
|
||||||
if (!res) {
|
|
||||||
dev_err(&mtu->pdev->dev, "failed to get I/O memory\n");
|
|
||||||
goto err0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Map memory, let base point to our channel and mapbase to the
|
|
||||||
* start/stop shared register.
|
|
||||||
*/
|
|
||||||
base = ioremap_nocache(res->start, resource_size(res));
|
|
||||||
if (base == NULL) {
|
|
||||||
dev_err(&mtu->pdev->dev, "failed to remap I/O memory\n");
|
|
||||||
goto err0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mtu->mapbase = base + cfg->channel_offset;
|
|
||||||
|
|
||||||
/* get hold of clock */
|
|
||||||
mtu->clk = clk_get(&mtu->pdev->dev, "mtu2_fck");
|
mtu->clk = clk_get(&mtu->pdev->dev, "mtu2_fck");
|
||||||
if (IS_ERR(mtu->clk)) {
|
if (IS_ERR(mtu->clk)) {
|
||||||
dev_err(&mtu->pdev->dev, "cannot get clock\n");
|
dev_err(&mtu->pdev->dev, "cannot get clock\n");
|
||||||
ret = PTR_ERR(mtu->clk);
|
return PTR_ERR(mtu->clk);
|
||||||
goto err1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = clk_prepare(mtu->clk);
|
ret = clk_prepare(mtu->clk);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto err2;
|
goto err_clk_put;
|
||||||
|
|
||||||
mtu->channels = kzalloc(sizeof(*mtu->channels), GFP_KERNEL);
|
/* Map the memory resource. */
|
||||||
if (mtu->channels == NULL) {
|
ret = sh_mtu2_map_memory(mtu);
|
||||||
ret = -ENOMEM;
|
if (ret < 0) {
|
||||||
goto err3;
|
dev_err(&mtu->pdev->dev, "failed to remap I/O memory\n");
|
||||||
|
goto err_clk_unprepare;
|
||||||
}
|
}
|
||||||
|
|
||||||
mtu->num_channels = 1;
|
/* Allocate and setup the channels. */
|
||||||
|
if (mtu->legacy)
|
||||||
|
mtu->num_channels = 1;
|
||||||
|
else
|
||||||
|
mtu->num_channels = 3;
|
||||||
|
|
||||||
mtu->channels[0].base = base;
|
mtu->channels = kzalloc(sizeof(*mtu->channels) * mtu->num_channels,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (mtu->channels == NULL) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
|
||||||
ret = sh_mtu2_setup_channel(&mtu->channels[0], mtu);
|
if (mtu->legacy) {
|
||||||
if (ret < 0)
|
ret = sh_mtu2_setup_channel(&mtu->channels[0], 0, mtu);
|
||||||
goto err3;
|
if (ret < 0)
|
||||||
|
goto err_unmap;
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < mtu->num_channels; ++i) {
|
||||||
|
ret = sh_mtu2_setup_channel(&mtu->channels[i], i, mtu);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err_unmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, mtu);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
err3:
|
|
||||||
|
err_unmap:
|
||||||
kfree(mtu->channels);
|
kfree(mtu->channels);
|
||||||
|
sh_mtu2_unmap_memory(mtu);
|
||||||
|
err_clk_unprepare:
|
||||||
clk_unprepare(mtu->clk);
|
clk_unprepare(mtu->clk);
|
||||||
err2:
|
err_clk_put:
|
||||||
clk_put(mtu->clk);
|
clk_put(mtu->clk);
|
||||||
err1:
|
|
||||||
iounmap(base);
|
|
||||||
err0:
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sh_mtu2_probe(struct platform_device *pdev)
|
static int sh_mtu2_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct sh_mtu2_device *mtu = platform_get_drvdata(pdev);
|
struct sh_mtu2_device *mtu = 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)) {
|
||||||
|
@ -484,7 +552,7 @@ static int sh_mtu2_probe(struct platform_device *pdev)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (cfg->clockevent_rating)
|
if (mtu->has_clockevent)
|
||||||
pm_runtime_irq_safe(&pdev->dev);
|
pm_runtime_irq_safe(&pdev->dev);
|
||||||
else
|
else
|
||||||
pm_runtime_idle(&pdev->dev);
|
pm_runtime_idle(&pdev->dev);
|
||||||
|
@ -497,12 +565,20 @@ static int sh_mtu2_remove(struct platform_device *pdev)
|
||||||
return -EBUSY; /* cannot unregister clockevent */
|
return -EBUSY; /* cannot unregister clockevent */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct platform_device_id sh_mtu2_id_table[] = {
|
||||||
|
{ "sh_mtu2", 1 },
|
||||||
|
{ "sh-mtu2", 0 },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(platform, sh_mtu2_id_table);
|
||||||
|
|
||||||
static struct platform_driver sh_mtu2_device_driver = {
|
static struct platform_driver sh_mtu2_device_driver = {
|
||||||
.probe = sh_mtu2_probe,
|
.probe = sh_mtu2_probe,
|
||||||
.remove = sh_mtu2_remove,
|
.remove = sh_mtu2_remove,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "sh_mtu2",
|
.name = "sh_mtu2",
|
||||||
}
|
},
|
||||||
|
.id_table = sh_mtu2_id_table,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int __init sh_mtu2_init(void)
|
static int __init sh_mtu2_init(void)
|
||||||
|
|
Loading…
Reference in New Issue