18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * JZ47xx SoCs TCU Operating System Timer driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Maarten ter Huurne <maarten@treewalker.org>
68c2ecf20Sopenharmony_ci * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/clk.h>
108c2ecf20Sopenharmony_ci#include <linux/clocksource.h>
118c2ecf20Sopenharmony_ci#include <linux/mfd/ingenic-tcu.h>
128c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
138c2ecf20Sopenharmony_ci#include <linux/of.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/pm.h>
168c2ecf20Sopenharmony_ci#include <linux/regmap.h>
178c2ecf20Sopenharmony_ci#include <linux/sched_clock.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define TCU_OST_TCSR_MASK	0xffc0
208c2ecf20Sopenharmony_ci#define TCU_OST_TCSR_CNT_MD	BIT(15)
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define TCU_OST_CHANNEL		15
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * The TCU_REG_OST_CNT{L,R} from <linux/mfd/ingenic-tcu.h> are only for the
268c2ecf20Sopenharmony_ci * regmap; these are for use with the __iomem pointer.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci#define OST_REG_CNTL		0x4
298c2ecf20Sopenharmony_ci#define OST_REG_CNTH		0x8
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistruct ingenic_ost_soc_info {
328c2ecf20Sopenharmony_ci	bool is64bit;
338c2ecf20Sopenharmony_ci};
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct ingenic_ost {
368c2ecf20Sopenharmony_ci	void __iomem *regs;
378c2ecf20Sopenharmony_ci	struct clk *clk;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	struct clocksource cs;
408c2ecf20Sopenharmony_ci};
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic struct ingenic_ost *ingenic_ost;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic u64 notrace ingenic_ost_read_cntl(void)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	/* Read using __iomem pointer instead of regmap to avoid locking */
478c2ecf20Sopenharmony_ci	return readl(ingenic_ost->regs + OST_REG_CNTL);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic u64 notrace ingenic_ost_read_cnth(void)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	/* Read using __iomem pointer instead of regmap to avoid locking */
538c2ecf20Sopenharmony_ci	return readl(ingenic_ost->regs + OST_REG_CNTH);
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic u64 notrace ingenic_ost_clocksource_readl(struct clocksource *cs)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	return ingenic_ost_read_cntl();
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic u64 notrace ingenic_ost_clocksource_readh(struct clocksource *cs)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	return ingenic_ost_read_cnth();
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int __init ingenic_ost_probe(struct platform_device *pdev)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	const struct ingenic_ost_soc_info *soc_info;
698c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
708c2ecf20Sopenharmony_ci	struct ingenic_ost *ost;
718c2ecf20Sopenharmony_ci	struct clocksource *cs;
728c2ecf20Sopenharmony_ci	struct regmap *map;
738c2ecf20Sopenharmony_ci	unsigned long rate;
748c2ecf20Sopenharmony_ci	int err;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	soc_info = device_get_match_data(dev);
778c2ecf20Sopenharmony_ci	if (!soc_info)
788c2ecf20Sopenharmony_ci		return -EINVAL;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	ost = devm_kzalloc(dev, sizeof(*ost), GFP_KERNEL);
818c2ecf20Sopenharmony_ci	if (!ost)
828c2ecf20Sopenharmony_ci		return -ENOMEM;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	ingenic_ost = ost;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	ost->regs = devm_platform_ioremap_resource(pdev, 0);
878c2ecf20Sopenharmony_ci	if (IS_ERR(ost->regs))
888c2ecf20Sopenharmony_ci		return PTR_ERR(ost->regs);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	map = device_node_to_regmap(dev->parent->of_node);
918c2ecf20Sopenharmony_ci	if (IS_ERR(map)) {
928c2ecf20Sopenharmony_ci		dev_err(dev, "regmap not found");
938c2ecf20Sopenharmony_ci		return PTR_ERR(map);
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	ost->clk = devm_clk_get(dev, "ost");
978c2ecf20Sopenharmony_ci	if (IS_ERR(ost->clk))
988c2ecf20Sopenharmony_ci		return PTR_ERR(ost->clk);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	err = clk_prepare_enable(ost->clk);
1018c2ecf20Sopenharmony_ci	if (err)
1028c2ecf20Sopenharmony_ci		return err;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	/* Clear counter high/low registers */
1058c2ecf20Sopenharmony_ci	if (soc_info->is64bit)
1068c2ecf20Sopenharmony_ci		regmap_write(map, TCU_REG_OST_CNTL, 0);
1078c2ecf20Sopenharmony_ci	regmap_write(map, TCU_REG_OST_CNTH, 0);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	/* Don't reset counter at compare value. */
1108c2ecf20Sopenharmony_ci	regmap_update_bits(map, TCU_REG_OST_TCSR,
1118c2ecf20Sopenharmony_ci			   TCU_OST_TCSR_MASK, TCU_OST_TCSR_CNT_MD);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	rate = clk_get_rate(ost->clk);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	/* Enable OST TCU channel */
1168c2ecf20Sopenharmony_ci	regmap_write(map, TCU_REG_TESR, BIT(TCU_OST_CHANNEL));
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	cs = &ost->cs;
1198c2ecf20Sopenharmony_ci	cs->name	= "ingenic-ost";
1208c2ecf20Sopenharmony_ci	cs->rating	= 320;
1218c2ecf20Sopenharmony_ci	cs->flags	= CLOCK_SOURCE_IS_CONTINUOUS;
1228c2ecf20Sopenharmony_ci	cs->mask	= CLOCKSOURCE_MASK(32);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (soc_info->is64bit)
1258c2ecf20Sopenharmony_ci		cs->read = ingenic_ost_clocksource_readl;
1268c2ecf20Sopenharmony_ci	else
1278c2ecf20Sopenharmony_ci		cs->read = ingenic_ost_clocksource_readh;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	err = clocksource_register_hz(cs, rate);
1308c2ecf20Sopenharmony_ci	if (err) {
1318c2ecf20Sopenharmony_ci		dev_err(dev, "clocksource registration failed");
1328c2ecf20Sopenharmony_ci		clk_disable_unprepare(ost->clk);
1338c2ecf20Sopenharmony_ci		return err;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	if (soc_info->is64bit)
1378c2ecf20Sopenharmony_ci		sched_clock_register(ingenic_ost_read_cntl, 32, rate);
1388c2ecf20Sopenharmony_ci	else
1398c2ecf20Sopenharmony_ci		sched_clock_register(ingenic_ost_read_cnth, 32, rate);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	return 0;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic int __maybe_unused ingenic_ost_suspend(struct device *dev)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	struct ingenic_ost *ost = dev_get_drvdata(dev);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	clk_disable(ost->clk);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	return 0;
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_cistatic int __maybe_unused ingenic_ost_resume(struct device *dev)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct ingenic_ost *ost = dev_get_drvdata(dev);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return clk_enable(ost->clk);
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic const struct dev_pm_ops __maybe_unused ingenic_ost_pm_ops = {
1618c2ecf20Sopenharmony_ci	/* _noirq: We want the OST clock to be gated last / ungated first */
1628c2ecf20Sopenharmony_ci	.suspend_noirq = ingenic_ost_suspend,
1638c2ecf20Sopenharmony_ci	.resume_noirq  = ingenic_ost_resume,
1648c2ecf20Sopenharmony_ci};
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic const struct ingenic_ost_soc_info jz4725b_ost_soc_info = {
1678c2ecf20Sopenharmony_ci	.is64bit = false,
1688c2ecf20Sopenharmony_ci};
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic const struct ingenic_ost_soc_info jz4770_ost_soc_info = {
1718c2ecf20Sopenharmony_ci	.is64bit = true,
1728c2ecf20Sopenharmony_ci};
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_cistatic const struct of_device_id ingenic_ost_of_match[] = {
1758c2ecf20Sopenharmony_ci	{ .compatible = "ingenic,jz4725b-ost", .data = &jz4725b_ost_soc_info, },
1768c2ecf20Sopenharmony_ci	{ .compatible = "ingenic,jz4770-ost", .data = &jz4770_ost_soc_info, },
1778c2ecf20Sopenharmony_ci	{ }
1788c2ecf20Sopenharmony_ci};
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic struct platform_driver ingenic_ost_driver = {
1818c2ecf20Sopenharmony_ci	.driver = {
1828c2ecf20Sopenharmony_ci		.name = "ingenic-ost",
1838c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SUSPEND
1848c2ecf20Sopenharmony_ci		.pm = &ingenic_ost_pm_ops,
1858c2ecf20Sopenharmony_ci#endif
1868c2ecf20Sopenharmony_ci		.of_match_table = ingenic_ost_of_match,
1878c2ecf20Sopenharmony_ci	},
1888c2ecf20Sopenharmony_ci};
1898c2ecf20Sopenharmony_cibuiltin_platform_driver_probe(ingenic_ost_driver, ingenic_ost_probe);
190