mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
d92df6fb81
As per kernel documentation, use raw_spinlock_t only in real critical core code, low-level interrupt handling, and places where disabling preemption or interrupts is required. Here the lock is for concurrent register access from different drivers, hence spin_lock() is sufficient. Reported-by: Pavel Machek <pavel@denx.de> Closes: https://lore.kernel.org/all/ZIL%2FitcJvV5s3Bnf@duo.ucw.cz/ Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com> Reviewed-by: Pavel Machek <pavel@denx.de> Link: https://lore.kernel.org/r/20230815073445.9579-3-biju.das.jz@bp.renesas.com Signed-off-by: Lee Jones <lee@kernel.org>
392 lines
11 KiB
C
392 lines
11 KiB
C
// 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(0x104, 0x090, 0x100, 0x128, 0x101, 0x102, 0x103, 0x126),
|
|
[RZ_MTU3_CHAN_1] = MTU_8BIT_CH_1_2(0x184, 0x091, 0x185, 0x180, 0x194, 0x181, 0x182),
|
|
[RZ_MTU3_CHAN_2] = MTU_8BIT_CH_1_2(0x204, 0x092, 0x205, 0x200, 0x20c, 0x201, 0x202),
|
|
[RZ_MTU3_CHAN_3] = MTU_8BIT_CH_3_4_6_7(0x008, 0x093, 0x02c, 0x000, 0x04c, 0x002, 0x004, 0x005, 0x038),
|
|
[RZ_MTU3_CHAN_4] = MTU_8BIT_CH_3_4_6_7(0x009, 0x094, 0x02d, 0x001, 0x04d, 0x003, 0x006, 0x007, 0x039),
|
|
[RZ_MTU3_CHAN_5] = MTU_8BIT_CH_5(0xab2, 0x1eb, 0xab4, 0xab6, 0xa84, 0xa85, 0xa86, 0xa94, 0xa95, 0xa96, 0xaa4, 0xaa5, 0xaa6),
|
|
[RZ_MTU3_CHAN_6] = MTU_8BIT_CH_3_4_6_7(0x808, 0x893, 0x82c, 0x800, 0x84c, 0x802, 0x804, 0x805, 0x838),
|
|
[RZ_MTU3_CHAN_7] = MTU_8BIT_CH_3_4_6_7(0x809, 0x894, 0x82d, 0x801, 0x84d, 0x803, 0x806, 0x807, 0x839),
|
|
[RZ_MTU3_CHAN_8] = MTU_8BIT_CH_8(0x404, 0x098, 0x400, 0x406, 0x401, 0x402, 0x403)
|
|
};
|
|
|
|
static const unsigned long rz_mtu3_16bit_ch_reg_offs[][12] = {
|
|
[RZ_MTU3_CHAN_0] = MTU_16BIT_CH_0(0x106, 0x108, 0x10a, 0x10c, 0x10e, 0x120, 0x122),
|
|
[RZ_MTU3_CHAN_1] = MTU_16BIT_CH_1_2(0x186, 0x188, 0x18a),
|
|
[RZ_MTU3_CHAN_2] = MTU_16BIT_CH_1_2(0x206, 0x208, 0x20a),
|
|
[RZ_MTU3_CHAN_3] = MTU_16BIT_CH_3_6(0x010, 0x018, 0x01a, 0x024, 0x026, 0x072),
|
|
[RZ_MTU3_CHAN_4] = MTU_16BIT_CH_4_7(0x012, 0x01c, 0x01e, 0x028, 0x2a, 0x074, 0x076, 0x040, 0x044, 0x046, 0x048, 0x04a),
|
|
[RZ_MTU3_CHAN_5] = MTU_16BIT_CH_5(0xa80, 0xa82, 0xa90, 0xa92, 0xaa0, 0xaa2),
|
|
[RZ_MTU3_CHAN_6] = MTU_16BIT_CH_3_6(0x810, 0x818, 0x81a, 0x824, 0x826, 0x872),
|
|
[RZ_MTU3_CHAN_7] = MTU_16BIT_CH_4_7(0x812, 0x81c, 0x81e, 0x828, 0x82a, 0x874, 0x876, 0x840, 0x844, 0x846, 0x848, 0x84a)
|
|
};
|
|
|
|
static const unsigned long rz_mtu3_32bit_ch_reg_offs[][5] = {
|
|
[RZ_MTU3_CHAN_1] = MTU_32BIT_CH_1(0x1a0, 0x1a4, 0x1a8),
|
|
[RZ_MTU3_CHAN_8] = MTU_32BIT_CH_8(0x408, 0x40c, 0x410, 0x414, 0x418)
|
|
};
|
|
|
|
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");
|