Commit | Line | Data |
---|---|---|
ca7b72b5 MH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * JZ47xx SoCs TCU Operating System Timer driver | |
4 | * | |
5 | * Copyright (C) 2016 Maarten ter Huurne <maarten@treewalker.org> | |
6 | * Copyright (C) 2020 Paul Cercueil <paul@crapouillou.net> | |
7 | */ | |
8 | ||
9 | #include <linux/clk.h> | |
10 | #include <linux/clocksource.h> | |
11 | #include <linux/mfd/ingenic-tcu.h> | |
12 | #include <linux/mfd/syscon.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/pm.h> | |
16 | #include <linux/regmap.h> | |
17 | #include <linux/sched_clock.h> | |
18 | ||
19 | #define TCU_OST_TCSR_MASK 0xffc0 | |
20 | #define TCU_OST_TCSR_CNT_MD BIT(15) | |
21 | ||
22 | #define TCU_OST_CHANNEL 15 | |
23 | ||
24 | /* | |
25 | * The TCU_REG_OST_CNT{L,R} from <linux/mfd/ingenic-tcu.h> are only for the | |
26 | * regmap; these are for use with the __iomem pointer. | |
27 | */ | |
28 | #define OST_REG_CNTL 0x4 | |
29 | #define OST_REG_CNTH 0x8 | |
30 | ||
31 | struct ingenic_ost_soc_info { | |
32 | bool is64bit; | |
33 | }; | |
34 | ||
35 | struct ingenic_ost { | |
36 | void __iomem *regs; | |
37 | struct clk *clk; | |
38 | ||
39 | struct clocksource cs; | |
40 | }; | |
41 | ||
42 | static struct ingenic_ost *ingenic_ost; | |
43 | ||
44 | static u64 notrace ingenic_ost_read_cntl(void) | |
45 | { | |
46 | /* Read using __iomem pointer instead of regmap to avoid locking */ | |
47 | return readl(ingenic_ost->regs + OST_REG_CNTL); | |
48 | } | |
49 | ||
50 | static u64 notrace ingenic_ost_read_cnth(void) | |
51 | { | |
52 | /* Read using __iomem pointer instead of regmap to avoid locking */ | |
53 | return readl(ingenic_ost->regs + OST_REG_CNTH); | |
54 | } | |
55 | ||
56 | static u64 notrace ingenic_ost_clocksource_readl(struct clocksource *cs) | |
57 | { | |
58 | return ingenic_ost_read_cntl(); | |
59 | } | |
60 | ||
61 | static u64 notrace ingenic_ost_clocksource_readh(struct clocksource *cs) | |
62 | { | |
63 | return ingenic_ost_read_cnth(); | |
64 | } | |
65 | ||
66 | static int __init ingenic_ost_probe(struct platform_device *pdev) | |
67 | { | |
68 | const struct ingenic_ost_soc_info *soc_info; | |
69 | struct device *dev = &pdev->dev; | |
70 | struct ingenic_ost *ost; | |
71 | struct clocksource *cs; | |
72 | struct regmap *map; | |
73 | unsigned long rate; | |
74 | int err; | |
75 | ||
76 | soc_info = device_get_match_data(dev); | |
77 | if (!soc_info) | |
78 | return -EINVAL; | |
79 | ||
80 | ost = devm_kzalloc(dev, sizeof(*ost), GFP_KERNEL); | |
81 | if (!ost) | |
82 | return -ENOMEM; | |
83 | ||
84 | ingenic_ost = ost; | |
85 | ||
86 | ost->regs = devm_platform_ioremap_resource(pdev, 0); | |
87 | if (IS_ERR(ost->regs)) | |
88 | return PTR_ERR(ost->regs); | |
89 | ||
90 | map = device_node_to_regmap(dev->parent->of_node); | |
91 | if (!map) { | |
92 | dev_err(dev, "regmap not found"); | |
93 | return -EINVAL; | |
94 | } | |
95 | ||
96 | ost->clk = devm_clk_get(dev, "ost"); | |
97 | if (IS_ERR(ost->clk)) | |
98 | return PTR_ERR(ost->clk); | |
99 | ||
100 | err = clk_prepare_enable(ost->clk); | |
101 | if (err) | |
102 | return err; | |
103 | ||
104 | /* Clear counter high/low registers */ | |
105 | if (soc_info->is64bit) | |
106 | regmap_write(map, TCU_REG_OST_CNTL, 0); | |
107 | regmap_write(map, TCU_REG_OST_CNTH, 0); | |
108 | ||
109 | /* Don't reset counter at compare value. */ | |
110 | regmap_update_bits(map, TCU_REG_OST_TCSR, | |
111 | TCU_OST_TCSR_MASK, TCU_OST_TCSR_CNT_MD); | |
112 | ||
113 | rate = clk_get_rate(ost->clk); | |
114 | ||
115 | /* Enable OST TCU channel */ | |
116 | regmap_write(map, TCU_REG_TESR, BIT(TCU_OST_CHANNEL)); | |
117 | ||
118 | cs = &ost->cs; | |
119 | cs->name = "ingenic-ost"; | |
120 | cs->rating = 320; | |
121 | cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; | |
122 | cs->mask = CLOCKSOURCE_MASK(32); | |
123 | ||
124 | if (soc_info->is64bit) | |
125 | cs->read = ingenic_ost_clocksource_readl; | |
126 | else | |
127 | cs->read = ingenic_ost_clocksource_readh; | |
128 | ||
129 | err = clocksource_register_hz(cs, rate); | |
130 | if (err) { | |
131 | dev_err(dev, "clocksource registration failed"); | |
132 | clk_disable_unprepare(ost->clk); | |
133 | return err; | |
134 | } | |
135 | ||
136 | if (soc_info->is64bit) | |
137 | sched_clock_register(ingenic_ost_read_cntl, 32, rate); | |
138 | else | |
139 | sched_clock_register(ingenic_ost_read_cnth, 32, rate); | |
140 | ||
141 | return 0; | |
142 | } | |
143 | ||
144 | static int __maybe_unused ingenic_ost_suspend(struct device *dev) | |
145 | { | |
146 | struct ingenic_ost *ost = dev_get_drvdata(dev); | |
147 | ||
148 | clk_disable(ost->clk); | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | static int __maybe_unused ingenic_ost_resume(struct device *dev) | |
154 | { | |
155 | struct ingenic_ost *ost = dev_get_drvdata(dev); | |
156 | ||
157 | return clk_enable(ost->clk); | |
158 | } | |
159 | ||
160 | static const struct dev_pm_ops __maybe_unused ingenic_ost_pm_ops = { | |
161 | /* _noirq: We want the OST clock to be gated last / ungated first */ | |
162 | .suspend_noirq = ingenic_ost_suspend, | |
163 | .resume_noirq = ingenic_ost_resume, | |
164 | }; | |
165 | ||
166 | static const struct ingenic_ost_soc_info jz4725b_ost_soc_info = { | |
167 | .is64bit = false, | |
168 | }; | |
169 | ||
170 | static const struct ingenic_ost_soc_info jz4770_ost_soc_info = { | |
171 | .is64bit = true, | |
172 | }; | |
173 | ||
174 | static const struct of_device_id ingenic_ost_of_match[] = { | |
175 | { .compatible = "ingenic,jz4725b-ost", .data = &jz4725b_ost_soc_info, }, | |
176 | { .compatible = "ingenic,jz4770-ost", .data = &jz4770_ost_soc_info, }, | |
177 | { } | |
178 | }; | |
179 | ||
180 | static struct platform_driver ingenic_ost_driver = { | |
181 | .driver = { | |
182 | .name = "ingenic-ost", | |
183 | #ifdef CONFIG_PM_SUSPEND | |
184 | .pm = &ingenic_ost_pm_ops, | |
185 | #endif | |
186 | .of_match_table = ingenic_ost_of_match, | |
187 | }, | |
188 | }; | |
189 | builtin_platform_driver_probe(ingenic_ost_driver, ingenic_ost_probe); |