mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
352408aff9
The OST in the JZ4760B SoC works exactly the same as in the JZ4770. But since the JZ4760B is older, its Device Tree string does not fall back to the JZ4770 one; so add support for the JZ4760B compatible string here. Signed-off-by: Paul Cercueil <paul@crapouillou.net> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Link: https://lore.kernel.org/r/20210308212302.10288-3-paul@crapouillou.net
191 lines
4.5 KiB
C
191 lines
4.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* JZ47xx SoCs TCU Operating System Timer driver
|
|
*
|
|
* Copyright (C) 2016 Maarten ter Huurne <maarten@treewalker.org>
|
|
* Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clocksource.h>
|
|
#include <linux/mfd/ingenic-tcu.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/sched_clock.h>
|
|
|
|
#define TCU_OST_TCSR_MASK 0xffc0
|
|
#define TCU_OST_TCSR_CNT_MD BIT(15)
|
|
|
|
#define TCU_OST_CHANNEL 15
|
|
|
|
/*
|
|
* The TCU_REG_OST_CNT{L,R} from <linux/mfd/ingenic-tcu.h> are only for the
|
|
* regmap; these are for use with the __iomem pointer.
|
|
*/
|
|
#define OST_REG_CNTL 0x4
|
|
#define OST_REG_CNTH 0x8
|
|
|
|
struct ingenic_ost_soc_info {
|
|
bool is64bit;
|
|
};
|
|
|
|
struct ingenic_ost {
|
|
void __iomem *regs;
|
|
struct clk *clk;
|
|
|
|
struct clocksource cs;
|
|
};
|
|
|
|
static struct ingenic_ost *ingenic_ost;
|
|
|
|
static u64 notrace ingenic_ost_read_cntl(void)
|
|
{
|
|
/* Read using __iomem pointer instead of regmap to avoid locking */
|
|
return readl(ingenic_ost->regs + OST_REG_CNTL);
|
|
}
|
|
|
|
static u64 notrace ingenic_ost_read_cnth(void)
|
|
{
|
|
/* Read using __iomem pointer instead of regmap to avoid locking */
|
|
return readl(ingenic_ost->regs + OST_REG_CNTH);
|
|
}
|
|
|
|
static u64 notrace ingenic_ost_clocksource_readl(struct clocksource *cs)
|
|
{
|
|
return ingenic_ost_read_cntl();
|
|
}
|
|
|
|
static u64 notrace ingenic_ost_clocksource_readh(struct clocksource *cs)
|
|
{
|
|
return ingenic_ost_read_cnth();
|
|
}
|
|
|
|
static int __init ingenic_ost_probe(struct platform_device *pdev)
|
|
{
|
|
const struct ingenic_ost_soc_info *soc_info;
|
|
struct device *dev = &pdev->dev;
|
|
struct ingenic_ost *ost;
|
|
struct clocksource *cs;
|
|
struct regmap *map;
|
|
unsigned long rate;
|
|
int err;
|
|
|
|
soc_info = device_get_match_data(dev);
|
|
if (!soc_info)
|
|
return -EINVAL;
|
|
|
|
ost = devm_kzalloc(dev, sizeof(*ost), GFP_KERNEL);
|
|
if (!ost)
|
|
return -ENOMEM;
|
|
|
|
ingenic_ost = ost;
|
|
|
|
ost->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(ost->regs))
|
|
return PTR_ERR(ost->regs);
|
|
|
|
map = device_node_to_regmap(dev->parent->of_node);
|
|
if (!map) {
|
|
dev_err(dev, "regmap not found");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ost->clk = devm_clk_get(dev, "ost");
|
|
if (IS_ERR(ost->clk))
|
|
return PTR_ERR(ost->clk);
|
|
|
|
err = clk_prepare_enable(ost->clk);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Clear counter high/low registers */
|
|
if (soc_info->is64bit)
|
|
regmap_write(map, TCU_REG_OST_CNTL, 0);
|
|
regmap_write(map, TCU_REG_OST_CNTH, 0);
|
|
|
|
/* Don't reset counter at compare value. */
|
|
regmap_update_bits(map, TCU_REG_OST_TCSR,
|
|
TCU_OST_TCSR_MASK, TCU_OST_TCSR_CNT_MD);
|
|
|
|
rate = clk_get_rate(ost->clk);
|
|
|
|
/* Enable OST TCU channel */
|
|
regmap_write(map, TCU_REG_TESR, BIT(TCU_OST_CHANNEL));
|
|
|
|
cs = &ost->cs;
|
|
cs->name = "ingenic-ost";
|
|
cs->rating = 320;
|
|
cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
|
cs->mask = CLOCKSOURCE_MASK(32);
|
|
|
|
if (soc_info->is64bit)
|
|
cs->read = ingenic_ost_clocksource_readl;
|
|
else
|
|
cs->read = ingenic_ost_clocksource_readh;
|
|
|
|
err = clocksource_register_hz(cs, rate);
|
|
if (err) {
|
|
dev_err(dev, "clocksource registration failed");
|
|
clk_disable_unprepare(ost->clk);
|
|
return err;
|
|
}
|
|
|
|
if (soc_info->is64bit)
|
|
sched_clock_register(ingenic_ost_read_cntl, 32, rate);
|
|
else
|
|
sched_clock_register(ingenic_ost_read_cnth, 32, rate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused ingenic_ost_suspend(struct device *dev)
|
|
{
|
|
struct ingenic_ost *ost = dev_get_drvdata(dev);
|
|
|
|
clk_disable(ost->clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused ingenic_ost_resume(struct device *dev)
|
|
{
|
|
struct ingenic_ost *ost = dev_get_drvdata(dev);
|
|
|
|
return clk_enable(ost->clk);
|
|
}
|
|
|
|
static const struct dev_pm_ops __maybe_unused ingenic_ost_pm_ops = {
|
|
/* _noirq: We want the OST clock to be gated last / ungated first */
|
|
.suspend_noirq = ingenic_ost_suspend,
|
|
.resume_noirq = ingenic_ost_resume,
|
|
};
|
|
|
|
static const struct ingenic_ost_soc_info jz4725b_ost_soc_info = {
|
|
.is64bit = false,
|
|
};
|
|
|
|
static const struct ingenic_ost_soc_info jz4760b_ost_soc_info = {
|
|
.is64bit = true,
|
|
};
|
|
|
|
static const struct of_device_id ingenic_ost_of_match[] = {
|
|
{ .compatible = "ingenic,jz4725b-ost", .data = &jz4725b_ost_soc_info, },
|
|
{ .compatible = "ingenic,jz4760b-ost", .data = &jz4760b_ost_soc_info, },
|
|
{ .compatible = "ingenic,jz4770-ost", .data = &jz4760b_ost_soc_info, },
|
|
{ }
|
|
};
|
|
|
|
static struct platform_driver ingenic_ost_driver = {
|
|
.driver = {
|
|
.name = "ingenic-ost",
|
|
#ifdef CONFIG_PM_SUSPEND
|
|
.pm = &ingenic_ost_pm_ops,
|
|
#endif
|
|
.of_match_table = ingenic_ost_of_match,
|
|
},
|
|
};
|
|
builtin_platform_driver_probe(ingenic_ost_driver, ingenic_ost_probe);
|