diff options
author | Maarten ter Huurne <maarten@treewalker.org> | 2020-02-12 15:04:08 -0300 |
---|---|---|
committer | Daniel Lezcano <daniel.lezcano@linaro.org> | 2020-02-27 09:37:29 +0100 |
commit | ca7b72b5a5f248b72c88441a93bdcee22f42b9b3 (patch) | |
tree | e21c7bcc1666f7bbb1c8699ae019f6bfb83640fe /drivers/clocksource/ingenic-ost.c | |
parent | 5be8badcb64be4c6ac5b0e8b882eca8eb175ec2d (diff) |
clocksource: Add driver for the Ingenic JZ47xx OST
OST is the OS Timer, a 64-bit timer/counter with buffered reading.
SoCs before the JZ4770 had (if any) a 32-bit OST; the JZ4770 and
JZ4780 have a 64-bit OST.
This driver will register both a clocksource and a sched_clock to the
system.
Signed-off-by: Maarten ter Huurne <maarten@treewalker.org>
Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Tested-by: Mathieu Malaterre <malat@debian.org>
Tested-by: Artur Rojek <contact@artur-rojek.eu>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://lore.kernel.org/r/20200212180408.30872-1-paul@crapouillou.net
Diffstat (limited to 'drivers/clocksource/ingenic-ost.c')
-rw-r--r-- | drivers/clocksource/ingenic-ost.c | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/drivers/clocksource/ingenic-ost.c b/drivers/clocksource/ingenic-ost.c new file mode 100644 index 000000000000..029efc2731b4 --- /dev/null +++ b/drivers/clocksource/ingenic-ost.c @@ -0,0 +1,189 @@ +// 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 jz4770_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,jz4770-ost", .data = &jz4770_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); |