// SPDX-License-Identifier: GPL-2.0
/*
* Renesas RZ/G2L Multi-Function Timer Pulse Unit 3(MTU3a) Core driver
*
* Copyright (C) 2023 Renesas Electronics Corporation
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mfd/core.h>
#include <linux/mfd/rz-mtu3.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#include "rz-mtu3.h"
struct rz_mtu3_priv {
void __iomem *mmio;
struct reset_control *rstc;
spinlock_t lock;
};
/******* MTU3 registers (original offset is +0x1200) *******/
static const unsigned long rz_mtu3_8bit_ch_reg_offs[][13 ] = {
[RZ_MTU3_CHAN_0] = MTU_8BIT_CH_0(0 x104, 0 x090, 0 x100, 0 x128, 0 x101, 0 x102, 0 x103, 0 x126),
[RZ_MTU3_CHAN_1] = MTU_8BIT_CH_1_2(0 x184, 0 x091, 0 x185, 0 x180, 0 x194, 0 x181, 0 x182),
[RZ_MTU3_CHAN_2] = MTU_8BIT_CH_1_2(0 x204, 0 x092, 0 x205, 0 x200, 0 x20c, 0 x201, 0 x202),
[RZ_MTU3_CHAN_3] = MTU_8BIT_CH_3_4_6_7(0 x008, 0 x093, 0 x02c, 0 x000, 0 x04c, 0 x002, 0 x004, 0 x005, 0 x038),
[RZ_MTU3_CHAN_4] = MTU_8BIT_CH_3_4_6_7(0 x009, 0 x094, 0 x02d, 0 x001, 0 x04d, 0 x003, 0 x006, 0 x007, 0 x039),
[RZ_MTU3_CHAN_5] = MTU_8BIT_CH_5(0 xab2, 0 x895, 0 xab4, 0 xab6, 0 xa84, 0 xa85, 0 xa86, 0 xa94, 0 xa95, 0 xa96, 0 xaa4, 0 xaa5, 0 xaa6),
[RZ_MTU3_CHAN_6] = MTU_8BIT_CH_3_4_6_7(0 x808, 0 x893, 0 x82c, 0 x800, 0 x84c, 0 x802, 0 x804, 0 x805, 0 x838),
[RZ_MTU3_CHAN_7] = MTU_8BIT_CH_3_4_6_7(0 x809, 0 x894, 0 x82d, 0 x801, 0 x84d, 0 x803, 0 x806, 0 x807, 0 x839),
[RZ_MTU3_CHAN_8] = MTU_8BIT_CH_8(0 x404, 0 x098, 0 x400, 0 x406, 0 x401, 0 x402, 0 x403)
};
static const unsigned long rz_mtu3_16bit_ch_reg_offs[][12 ] = {
[RZ_MTU3_CHAN_0] = MTU_16BIT_CH_0(0 x106, 0 x108, 0 x10a, 0 x10c, 0 x10e, 0 x120, 0 x122),
[RZ_MTU3_CHAN_1] = MTU_16BIT_CH_1_2(0 x186, 0 x188, 0 x18a),
[RZ_MTU3_CHAN_2] = MTU_16BIT_CH_1_2(0 x206, 0 x208, 0 x20a),
[RZ_MTU3_CHAN_3] = MTU_16BIT_CH_3_6(0 x010, 0 x018, 0 x01a, 0 x024, 0 x026, 0 x072),
[RZ_MTU3_CHAN_4] = MTU_16BIT_CH_4_7(0 x012, 0 x01c, 0 x01e, 0 x028, 0 x2a, 0 x074, 0 x076, 0 x040, 0 x044, 0 x046, 0 x048, 0 x04a),
[RZ_MTU3_CHAN_5] = MTU_16BIT_CH_5(0 xa80, 0 xa82, 0 xa90, 0 xa92, 0 xaa0, 0 xaa2),
[RZ_MTU3_CHAN_6] = MTU_16BIT_CH_3_6(0 x810, 0 x818, 0 x81a, 0 x824, 0 x826, 0 x872),
[RZ_MTU3_CHAN_7] = MTU_16BIT_CH_4_7(0 x812, 0 x81c, 0 x81e, 0 x828, 0 x82a, 0 x874, 0 x876, 0 x840, 0 x844, 0 x846, 0 x848, 0 x84a)
};
static const unsigned long rz_mtu3_32bit_ch_reg_offs[][5 ] = {
[RZ_MTU3_CHAN_1] = MTU_32BIT_CH_1(0 x1a0, 0 x1a4, 0 x1a8),
[RZ_MTU3_CHAN_8] = MTU_32BIT_CH_8(0 x408, 0 x40c, 0 x410, 0 x414, 0 x418)
};
static bool rz_mtu3_is_16bit_shared_reg(u16 offset)
{
return (offset == RZ_MTU3_TDDRA || offset == RZ_MTU3_TDDRB ||
offset == RZ_MTU3_TCDRA || offset == RZ_MTU3_TCDRB ||
offset == RZ_MTU3_TCBRA || offset == RZ_MTU3_TCBRB ||
offset == RZ_MTU3_TCNTSA || offset == RZ_MTU3_TCNTSB);
}
u16 rz_mtu3_shared_reg_read(struct rz_mtu3_channel *ch, u16 offset)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
if (rz_mtu3_is_16bit_shared_reg(offset))
return readw(priv->mmio + offset);
else
return readb(priv->mmio + offset);
}
EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_read);
u8 rz_mtu3_8bit_ch_read(struct rz_mtu3_channel *ch, u16 offset)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
ch_offs = rz_mtu3_8bit_ch_reg_offs[ch->channel_number][offset];
return readb(priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_8bit_ch_read);
u16 rz_mtu3_16bit_ch_read(struct rz_mtu3_channel *ch, u16 offset)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
/* MTU8 doesn't have 16-bit registers */
if (ch->channel_number == RZ_MTU3_CHAN_8)
return 0 ;
ch_offs = rz_mtu3_16bit_ch_reg_offs[ch->channel_number][offset];
return readw(priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_16bit_ch_read);
u32 rz_mtu3_32bit_ch_read(struct rz_mtu3_channel *ch, u16 offset)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
if (ch->channel_number != RZ_MTU3_CHAN_1 && ch->channel_number != RZ_MTU3_CHAN_8)
return 0 ;
ch_offs = rz_mtu3_32bit_ch_reg_offs[ch->channel_number][offset];
return readl(priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_32bit_ch_read);
void rz_mtu3_8bit_ch_write(struct rz_mtu3_channel *ch, u16 offset, u8 val)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
ch_offs = rz_mtu3_8bit_ch_reg_offs[ch->channel_number][offset];
writeb(val, priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_8bit_ch_write);
void rz_mtu3_16bit_ch_write(struct rz_mtu3_channel *ch, u16 offset, u16 val)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
/* MTU8 doesn't have 16-bit registers */
if (ch->channel_number == RZ_MTU3_CHAN_8)
return ;
ch_offs = rz_mtu3_16bit_ch_reg_offs[ch->channel_number][offset];
writew(val, priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_16bit_ch_write);
void rz_mtu3_32bit_ch_write(struct rz_mtu3_channel *ch, u16 offset, u32 val)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
u16 ch_offs;
if (ch->channel_number != RZ_MTU3_CHAN_1 && ch->channel_number != RZ_MTU3_CHAN_8)
return ;
ch_offs = rz_mtu3_32bit_ch_reg_offs[ch->channel_number][offset];
writel(val, priv->mmio + ch_offs);
}
EXPORT_SYMBOL_GPL(rz_mtu3_32bit_ch_write);
void rz_mtu3_shared_reg_write(struct rz_mtu3_channel *ch, u16 offset, u16 value)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
if (rz_mtu3_is_16bit_shared_reg(offset))
writew(value, priv->mmio + offset);
else
writeb((u8)value, priv->mmio + offset);
}
EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_write);
void rz_mtu3_shared_reg_update_bit(struct rz_mtu3_channel *ch, u16 offset,
u16 pos, u8 val)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
unsigned long tmdr, flags;
spin_lock_irqsave(&priv->lock, flags);
tmdr = rz_mtu3_shared_reg_read(ch, offset);
__assign_bit(pos, &tmdr, !!val);
rz_mtu3_shared_reg_write(ch, offset, tmdr);
spin_unlock_irqrestore(&priv->lock, flags);
}
EXPORT_SYMBOL_GPL(rz_mtu3_shared_reg_update_bit);
static u16 rz_mtu3_get_tstr_offset(struct rz_mtu3_channel *ch)
{
u16 offset;
switch (ch->channel_number) {
case RZ_MTU3_CHAN_0:
case RZ_MTU3_CHAN_1:
case RZ_MTU3_CHAN_2:
case RZ_MTU3_CHAN_3:
case RZ_MTU3_CHAN_4:
case RZ_MTU3_CHAN_8:
offset = RZ_MTU3_TSTRA;
break ;
case RZ_MTU3_CHAN_5:
offset = RZ_MTU3_TSTR;
break ;
case RZ_MTU3_CHAN_6:
case RZ_MTU3_CHAN_7:
offset = RZ_MTU3_TSTRB;
break ;
default :
offset = 0 ;
break ;
}
return offset;
}
static u8 rz_mtu3_get_tstr_bit_pos(struct rz_mtu3_channel *ch)
{
u8 bitpos;
switch (ch->channel_number) {
case RZ_MTU3_CHAN_0:
case RZ_MTU3_CHAN_1:
case RZ_MTU3_CHAN_2:
case RZ_MTU3_CHAN_6:
case RZ_MTU3_CHAN_7:
bitpos = ch->channel_number;
break ;
case RZ_MTU3_CHAN_3:
bitpos = 6 ;
break ;
case RZ_MTU3_CHAN_4:
bitpos = 7 ;
break ;
case RZ_MTU3_CHAN_5:
bitpos = 2 ;
break ;
case RZ_MTU3_CHAN_8:
bitpos = 3 ;
break ;
default :
bitpos = 0 ;
break ;
}
return bitpos;
}
static void rz_mtu3_start_stop_ch(struct rz_mtu3_channel *ch, bool start)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
unsigned long flags, tstr;
u16 offset;
u8 bitpos;
offset = rz_mtu3_get_tstr_offset(ch);
bitpos = rz_mtu3_get_tstr_bit_pos(ch);
/* start stop register shared by multiple timer channels */
spin_lock_irqsave(&priv->lock, flags);
tstr = rz_mtu3_shared_reg_read(ch, offset);
__assign_bit(bitpos, &tstr, start);
rz_mtu3_shared_reg_write(ch, offset, tstr);
spin_unlock_irqrestore(&priv->lock, flags);
}
bool rz_mtu3_is_enabled(struct rz_mtu3_channel *ch)
{
struct rz_mtu3 *mtu = dev_get_drvdata(ch->dev->parent);
struct rz_mtu3_priv *priv = mtu->priv_data;
unsigned long flags, tstr;
u16 offset;
u8 bitpos;
offset = rz_mtu3_get_tstr_offset(ch);
bitpos = rz_mtu3_get_tstr_bit_pos(ch);
/* start stop register shared by multiple timer channels */
spin_lock_irqsave(&priv->lock, flags);
tstr = rz_mtu3_shared_reg_read(ch, offset);
spin_unlock_irqrestore(&priv->lock, flags);
return tstr & BIT(bitpos);
}
EXPORT_SYMBOL_GPL(rz_mtu3_is_enabled);
int rz_mtu3_enable(struct rz_mtu3_channel *ch)
{
/* enable channel */
rz_mtu3_start_stop_ch(ch, true );
return 0 ;
}
EXPORT_SYMBOL_GPL(rz_mtu3_enable);
void rz_mtu3_disable(struct rz_mtu3_channel *ch)
{
/* disable channel */
rz_mtu3_start_stop_ch(ch, false );
}
EXPORT_SYMBOL_GPL(rz_mtu3_disable);
static void rz_mtu3_reset_assert(void *data)
{
struct rz_mtu3 *mtu = dev_get_drvdata(data);
struct rz_mtu3_priv *priv = mtu->priv_data;
mfd_remove_devices(data);
reset_control_assert(priv->rstc);
}
static const struct mfd_cell rz_mtu3_devs[] = {
{
.name = "rz-mtu3-counter" ,
},
{
.name = "pwm-rz-mtu3" ,
},
};
static int rz_mtu3_probe(struct platform_device *pdev)
{
struct rz_mtu3_priv *priv;
struct rz_mtu3 *ddata;
unsigned int i;
int ret;
ddata = devm_kzalloc(&pdev->dev, sizeof (*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
ddata->priv_data = devm_kzalloc(&pdev->dev, sizeof (*priv), GFP_KERNEL);
if (!ddata->priv_data)
return -ENOMEM;
priv = ddata->priv_data;
priv->mmio = devm_platform_ioremap_resource(pdev, 0 );
if (IS_ERR(priv->mmio))
return PTR_ERR(priv->mmio);
priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(priv->rstc))
return PTR_ERR(priv->rstc);
ddata->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ddata->clk))
return PTR_ERR(ddata->clk);
reset_control_deassert(priv->rstc);
spin_lock_init(&priv->lock);
platform_set_drvdata(pdev, ddata);
for (i = 0 ; i < RZ_MTU_NUM_CHANNELS; i++) {
ddata->channels[i].channel_number = i;
ddata->channels[i].is_busy = false ;
mutex_init(&ddata->channels[i].lock);
}
ret = mfd_add_devices(&pdev->dev, 0 , rz_mtu3_devs,
ARRAY_SIZE(rz_mtu3_devs), NULL, 0 , NULL);
if (ret < 0 )
goto err_assert;
return devm_add_action_or_reset(&pdev->dev, rz_mtu3_reset_assert,
&pdev->dev);
err_assert:
reset_control_assert(priv->rstc);
return ret;
}
static const struct of_device_id rz_mtu3_of_match[] = {
{ .compatible = "renesas,rz-mtu3" , },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rz_mtu3_of_match);
static struct platform_driver rz_mtu3_driver = {
.probe = rz_mtu3_probe,
.driver = {
.name = "rz-mtu3" ,
.of_match_table = rz_mtu3_of_match,
},
};
module_platform_driver(rz_mtu3_driver);
MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>" );
MODULE_DESCRIPTION("Renesas RZ/G2L MTU3a Core Driver" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=97 H=95 G=95
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland