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(addr: 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(addr: 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, size: sizeof(*ost), GFP_KERNEL); |
81 | if (!ost) |
82 | return -ENOMEM; |
83 | |
84 | ingenic_ost = ost; |
85 | |
86 | ost->regs = devm_platform_ioremap_resource(pdev, index: 0); |
87 | if (IS_ERR(ptr: ost->regs)) |
88 | return PTR_ERR(ptr: ost->regs); |
89 | |
90 | map = device_node_to_regmap(np: dev->parent->of_node); |
91 | if (IS_ERR(ptr: map)) { |
92 | dev_err(dev, "regmap not found" ); |
93 | return PTR_ERR(ptr: map); |
94 | } |
95 | |
96 | ost->clk = devm_clk_get(dev, id: "ost" ); |
97 | if (IS_ERR(ptr: ost->clk)) |
98 | return PTR_ERR(ptr: ost->clk); |
99 | |
100 | err = clk_prepare_enable(clk: 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, val: 0); |
107 | regmap_write(map, TCU_REG_OST_CNTH, val: 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(clk: 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, hz: rate); |
130 | if (err) { |
131 | dev_err(dev, "clocksource registration failed" ); |
132 | clk_disable_unprepare(clk: ost->clk); |
133 | return err; |
134 | } |
135 | |
136 | if (soc_info->is64bit) |
137 | sched_clock_register(read: ingenic_ost_read_cntl, bits: 32, rate); |
138 | else |
139 | sched_clock_register(read: ingenic_ost_read_cnth, bits: 32, rate); |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | static int ingenic_ost_suspend(struct device *dev) |
145 | { |
146 | struct ingenic_ost *ost = dev_get_drvdata(dev); |
147 | |
148 | clk_disable(clk: ost->clk); |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | static int ingenic_ost_resume(struct device *dev) |
154 | { |
155 | struct ingenic_ost *ost = dev_get_drvdata(dev); |
156 | |
157 | return clk_enable(clk: ost->clk); |
158 | } |
159 | |
160 | static const struct dev_pm_ops 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 jz4760b_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,jz4760b-ost" , .data = &jz4760b_ost_soc_info, }, |
177 | { .compatible = "ingenic,jz4770-ost" , .data = &jz4760b_ost_soc_info, }, |
178 | { } |
179 | }; |
180 | |
181 | static struct platform_driver ingenic_ost_driver = { |
182 | .driver = { |
183 | .name = "ingenic-ost" , |
184 | .pm = pm_sleep_ptr(&ingenic_ost_pm_ops), |
185 | .of_match_table = ingenic_ost_of_match, |
186 | }, |
187 | }; |
188 | builtin_platform_driver_probe(ingenic_ost_driver, ingenic_ost_probe); |
189 | |