-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Maarten ter Huurne
authored and
Daniel Lezcano
committed
Feb 27, 2020
1 parent
5be8bad
commit ca7b72b
Showing
3 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |