| 1 | |
| 2 | |
| 3 | * Ingenic XBurst SoCs SYSOST clocks driver
|
| 4 | * Copyright (c) 2020 周琰杰 (Zhou Yanjie) <[email protected]>
|
| 5 |
|
| 6 |
|
| 7 | #include <linux/bitfield.h>
|
| 8 | #include <linux/bitops.h>
|
| 9 | #include <linux/clk.h>
|
| 10 | #include <linux/clk-provider.h>
|
| 11 | #include <linux/clockchips.h>
|
| 12 | #include <linux/clocksource.h>
|
| 13 | #include <linux/interrupt.h>
|
| 14 | #include <linux/mfd/syscon.h>
|
| 15 | #include <linux/of_address.h>
|
| 16 | #include <linux/of_irq.h>
|
| 17 | #include <linux/sched_clock.h>
|
| 18 | #include <linux/slab.h>
|
| 19 | #include <linux/syscore_ops.h>
|
| 20 |
|
| 21 | #include <dt-bindings/clock/ingenic,sysost.h>
|
| 22 |
|
| 23 |
|
| 24 | #define OST_REG_OSTCCR 0x00
|
| 25 | #define OST_REG_OSTCR 0x08
|
| 26 | #define OST_REG_OSTFR 0x0c
|
| 27 | #define OST_REG_OSTMR 0x10
|
| 28 | #define OST_REG_OST1DFR 0x14
|
| 29 | #define OST_REG_OST1CNT 0x18
|
| 30 | #define OST_REG_OST2CNTL 0x20
|
| 31 | #define OST_REG_OSTCNT2HBUF 0x24
|
| 32 | #define OST_REG_OSTESR 0x34
|
| 33 | #define OST_REG_OSTECR 0x38
|
| 34 |
|
| 35 |
|
| 36 | #define OSTCCR_PRESCALE1_MASK 0x3
|
| 37 | #define OSTCCR_PRESCALE2_MASK 0xc
|
| 38 |
|
| 39 |
|
| 40 | #define OSTCR_OST1CLR BIT(0)
|
| 41 | #define OSTCR_OST2CLR BIT(1)
|
| 42 |
|
| 43 |
|
| 44 | #define OSTFR_FFLAG BIT(0)
|
| 45 |
|
| 46 |
|
| 47 | #define OSTMR_FMASK BIT(0)
|
| 48 |
|
| 49 |
|
| 50 | #define OSTESR_OST1ENS BIT(0)
|
| 51 | #define OSTESR_OST2ENS BIT(1)
|
| 52 |
|
| 53 |
|
| 54 | #define OSTECR_OST1ENC BIT(0)
|
| 55 | #define OSTECR_OST2ENC BIT(1)
|
| 56 |
|
| 57 | struct ingenic_soc_info {
|
| 58 | unsigned int num_channels;
|
| 59 | };
|
| 60 |
|
| 61 | struct ingenic_ost_clk_info {
|
| 62 | struct clk_init_data init_data;
|
| 63 | u8 ostccr_reg;
|
| 64 | };
|
| 65 |
|
| 66 | struct ingenic_ost_clk {
|
| 67 | struct clk_hw hw;
|
| 68 | unsigned int idx;
|
| 69 | struct ingenic_ost *ost;
|
| 70 | const struct ingenic_ost_clk_info *info;
|
| 71 | };
|
| 72 |
|
| 73 | struct ingenic_ost {
|
| 74 | void __iomem *base;
|
| 75 | const struct ingenic_soc_info *soc_info;
|
| 76 | struct clk *clk, *percpu_timer_clk, *global_timer_clk;
|
| 77 | struct clock_event_device cevt;
|
| 78 | struct clocksource cs;
|
| 79 | char name[20];
|
| 80 |
|
| 81 | struct clk_hw_onecell_data *clocks;
|
| 82 | };
|
| 83 |
|
| 84 | static struct ingenic_ost *ingenic_ost;
|
| 85 |
|
| 86 | static inline struct ingenic_ost_clk *to_ost_clk(struct clk_hw *hw)
|
| 87 | {
|
| 88 | return container_of(hw, struct ingenic_ost_clk, hw);
|
| 89 | }
|
| 90 |
|
| 91 | static unsigned long ingenic_ost_percpu_timer_recalc_rate(struct clk_hw *hw,
|
| 92 | unsigned long parent_rate)
|
| 93 | {
|
| 94 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw);
|
| 95 | const struct ingenic_ost_clk_info *info = ost_clk->info;
|
| 96 | unsigned int prescale;
|
| 97 |
|
| 98 | prescale = readl(ost_clk->ost->base + info->ostccr_reg);
|
| 99 |
|
| 100 | prescale = FIELD_GET(OSTCCR_PRESCALE1_MASK, prescale);
|
| 101 |
|
| 102 | return parent_rate >> (prescale * 2);
|
| 103 | }
|
| 104 |
|
| 105 | static unsigned long ingenic_ost_global_timer_recalc_rate(struct clk_hw *hw,
|
| 106 | unsigned long parent_rate)
|
| 107 | {
|
| 108 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw);
|
| 109 | const struct ingenic_ost_clk_info *info = ost_clk->info;
|
| 110 | unsigned int prescale;
|
| 111 |
|
| 112 | prescale = readl(ost_clk->ost->base + info->ostccr_reg);
|
| 113 |
|
| 114 | prescale = FIELD_GET(OSTCCR_PRESCALE2_MASK, prescale);
|
| 115 |
|
| 116 | return parent_rate >> (prescale * 2);
|
| 117 | }
|
| 118 |
|
| 119 | static u8 ingenic_ost_get_prescale(unsigned long rate, unsigned long req_rate)
|
| 120 | {
|
| 121 | u8 prescale;
|
| 122 |
|
| 123 | for (prescale = 0; prescale < 2; prescale++)
|
| 124 | if ((rate >> (prescale * 2)) <= req_rate)
|
| 125 | return prescale;
|
| 126 |
|
| 127 | return 2;
|
| 128 | }
|
| 129 |
|
| 130 | static int ingenic_ost_determine_rate(struct clk_hw *hw,
|
| 131 | struct clk_rate_request *req)
|
| 132 | {
|
| 133 | unsigned long rate = req->best_parent_rate;
|
| 134 | u8 prescale;
|
| 135 |
|
| 136 | if (req->rate > rate) {
|
| 137 | req->rate = rate;
|
| 138 |
|
| 139 | return 0;
|
| 140 | }
|
| 141 |
|
| 142 | prescale = ingenic_ost_get_prescale(rate, req->rate);
|
| 143 |
|
| 144 | req->rate = rate >> (prescale * 2);
|
| 145 |
|
| 146 | return 0;
|
| 147 | }
|
| 148 |
|
| 149 | static int ingenic_ost_percpu_timer_set_rate(struct clk_hw *hw, unsigned long req_rate,
|
| 150 | unsigned long parent_rate)
|
| 151 | {
|
| 152 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw);
|
| 153 | const struct ingenic_ost_clk_info *info = ost_clk->info;
|
| 154 | u8 prescale = ingenic_ost_get_prescale(parent_rate, req_rate);
|
| 155 | int val;
|
| 156 |
|
| 157 | val = readl(ost_clk->ost->base + info->ostccr_reg);
|
| 158 | val &= ~OSTCCR_PRESCALE1_MASK;
|
| 159 | val |= FIELD_PREP(OSTCCR_PRESCALE1_MASK, prescale);
|
| 160 | writel(val, ost_clk->ost->base + info->ostccr_reg);
|
| 161 |
|
| 162 | return 0;
|
| 163 | }
|
| 164 |
|
| 165 | static int ingenic_ost_global_timer_set_rate(struct clk_hw *hw, unsigned long req_rate,
|
| 166 | unsigned long parent_rate)
|
| 167 | {
|
| 168 | struct ingenic_ost_clk *ost_clk = to_ost_clk(hw);
|
| 169 | const struct ingenic_ost_clk_info *info = ost_clk->info;
|
| 170 | u8 prescale = ingenic_ost_get_prescale(parent_rate, req_rate);
|
| 171 | int val;
|
| 172 |
|
| 173 | val = readl(ost_clk->ost->base + info->ostccr_reg);
|
| 174 | val &= ~OSTCCR_PRESCALE2_MASK;
|
| 175 | val |= FIELD_PREP(OSTCCR_PRESCALE2_MASK, prescale);
|
| 176 | writel(val, ost_clk->ost->base + info->ostccr_reg);
|
| 177 |
|
| 178 | return 0;
|
| 179 | }
|
| 180 |
|
| 181 | static const struct clk_ops ingenic_ost_percpu_timer_ops = {
|
| 182 | .recalc_rate = ingenic_ost_percpu_timer_recalc_rate,
|
| 183 | .determine_rate = ingenic_ost_determine_rate,
|
| 184 | .set_rate = ingenic_ost_percpu_timer_set_rate,
|
| 185 | };
|
| 186 |
|
| 187 | static const struct clk_ops ingenic_ost_global_timer_ops = {
|
| 188 | .recalc_rate = ingenic_ost_global_timer_recalc_rate,
|
| 189 | .determine_rate = ingenic_ost_determine_rate,
|
| 190 | .set_rate = ingenic_ost_global_timer_set_rate,
|
| 191 | };
|
| 192 |
|
| 193 | static const char * const ingenic_ost_clk_parents[] = { "ext" };
|
| 194 |
|
| 195 | static const struct ingenic_ost_clk_info x1000_ost_clk_info[] = {
|
| 196 | [OST_CLK_PERCPU_TIMER] = {
|
| 197 | .init_data = {
|
| 198 | .name = "percpu timer",
|
| 199 | .parent_names = ingenic_ost_clk_parents,
|
| 200 | .num_parents = ARRAY_SIZE(ingenic_ost_clk_parents),
|
| 201 | .ops = &ingenic_ost_percpu_timer_ops,
|
| 202 | .flags = CLK_SET_RATE_UNGATE,
|
| 203 | },
|
| 204 | .ostccr_reg = OST_REG_OSTCCR,
|
| 205 | },
|
| 206 |
|
| 207 | [OST_CLK_GLOBAL_TIMER] = {
|
| 208 | .init_data = {
|
| 209 | .name = "global timer",
|
| 210 | .parent_names = ingenic_ost_clk_parents,
|
| 211 | .num_parents = ARRAY_SIZE(ingenic_ost_clk_parents),
|
| 212 | .ops = &ingenic_ost_global_timer_ops,
|
| 213 | .flags = CLK_SET_RATE_UNGATE,
|
| 214 | },
|
| 215 | .ostccr_reg = OST_REG_OSTCCR,
|
| 216 | },
|
| 217 | };
|
| 218 |
|
| 219 | static u64 notrace ingenic_ost_global_timer_read_cntl(void)
|
| 220 | {
|
| 221 | struct ingenic_ost *ost = ingenic_ost;
|
| 222 | unsigned int count;
|
| 223 |
|
| 224 | count = readl(ost->base + OST_REG_OST2CNTL);
|
| 225 |
|
| 226 | return count;
|
| 227 | }
|
| 228 |
|
| 229 | static u64 notrace ingenic_ost_clocksource_read(struct clocksource *cs)
|
| 230 | {
|
| 231 | return ingenic_ost_global_timer_read_cntl();
|
| 232 | }
|
| 233 |
|
| 234 | static inline struct ingenic_ost *to_ingenic_ost(struct clock_event_device *evt)
|
| 235 | {
|
| 236 | return container_of(evt, struct ingenic_ost, cevt);
|
| 237 | }
|
| 238 |
|
| 239 | static int ingenic_ost_cevt_set_state_shutdown(struct clock_event_device *evt)
|
| 240 | {
|
| 241 | struct ingenic_ost *ost = to_ingenic_ost(evt);
|
| 242 |
|
| 243 | writel(OSTECR_OST1ENC, ost->base + OST_REG_OSTECR);
|
| 244 |
|
| 245 | return 0;
|
| 246 | }
|
| 247 |
|
| 248 | static int ingenic_ost_cevt_set_next(unsigned long next,
|
| 249 | struct clock_event_device *evt)
|
| 250 | {
|
| 251 | struct ingenic_ost *ost = to_ingenic_ost(evt);
|
| 252 |
|
| 253 | writel((u32)~OSTFR_FFLAG, ost->base + OST_REG_OSTFR);
|
| 254 | writel(next, ost->base + OST_REG_OST1DFR);
|
| 255 | writel(OSTCR_OST1CLR, ost->base + OST_REG_OSTCR);
|
| 256 | writel(OSTESR_OST1ENS, ost->base + OST_REG_OSTESR);
|
| 257 | writel((u32)~OSTMR_FMASK, ost->base + OST_REG_OSTMR);
|
| 258 |
|
| 259 | return 0;
|
| 260 | }
|
| 261 |
|
| 262 | static irqreturn_t ingenic_ost_cevt_cb(int irq, void *dev_id)
|
| 263 | {
|
| 264 | struct clock_event_device *evt = dev_id;
|
| 265 | struct ingenic_ost *ost = to_ingenic_ost(evt);
|
| 266 |
|
| 267 | writel(OSTECR_OST1ENC, ost->base + OST_REG_OSTECR);
|
| 268 |
|
| 269 | if (evt->event_handler)
|
| 270 | evt->event_handler(evt);
|
| 271 |
|
| 272 | return IRQ_HANDLED;
|
| 273 | }
|
| 274 |
|
| 275 | static int __init ingenic_ost_register_clock(struct ingenic_ost *ost,
|
| 276 | unsigned int idx, const struct ingenic_ost_clk_info *info,
|
| 277 | struct clk_hw_onecell_data *clocks)
|
| 278 | {
|
| 279 | struct ingenic_ost_clk *ost_clk;
|
| 280 | int val, err;
|
| 281 |
|
| 282 | ost_clk = kzalloc_obj(*ost_clk);
|
| 283 | if (!ost_clk)
|
| 284 | return -ENOMEM;
|
| 285 |
|
| 286 | ost_clk->hw.init = &info->init_data;
|
| 287 | ost_clk->idx = idx;
|
| 288 | ost_clk->info = info;
|
| 289 | ost_clk->ost = ost;
|
| 290 |
|
| 291 |
|
| 292 | val = readl(ost->base + info->ostccr_reg);
|
| 293 | val &= ~(OSTCCR_PRESCALE1_MASK | OSTCCR_PRESCALE2_MASK);
|
| 294 | writel(val, ost->base + info->ostccr_reg);
|
| 295 |
|
| 296 | err = clk_hw_register(NULL, &ost_clk->hw);
|
| 297 | if (err) {
|
| 298 | kfree(ost_clk);
|
| 299 | return err;
|
| 300 | }
|
| 301 |
|
| 302 | clocks->hws[idx] = &ost_clk->hw;
|
| 303 |
|
| 304 | return 0;
|
| 305 | }
|
| 306 |
|
| 307 | static struct clk * __init ingenic_ost_get_clock(struct device_node *np, int id)
|
| 308 | {
|
| 309 | struct of_phandle_args args;
|
| 310 |
|
| 311 | args.np = np;
|
| 312 | args.args_count = 1;
|
| 313 | args.args[0] = id;
|
| 314 |
|
| 315 | return of_clk_get_from_provider(&args);
|
| 316 | }
|
| 317 |
|
| 318 | static int __init ingenic_ost_percpu_timer_init(struct device_node *np,
|
| 319 | struct ingenic_ost *ost)
|
| 320 | {
|
| 321 | unsigned int timer_virq, channel = OST_CLK_PERCPU_TIMER;
|
| 322 | unsigned long rate;
|
| 323 | int err;
|
| 324 |
|
| 325 | ost->percpu_timer_clk = ingenic_ost_get_clock(np, channel);
|
| 326 | if (IS_ERR(ost->percpu_timer_clk))
|
| 327 | return PTR_ERR(ost->percpu_timer_clk);
|
| 328 |
|
| 329 | err = clk_prepare_enable(ost->percpu_timer_clk);
|
| 330 | if (err)
|
| 331 | goto err_clk_put;
|
| 332 |
|
| 333 | rate = clk_get_rate(ost->percpu_timer_clk);
|
| 334 | if (!rate) {
|
| 335 | err = -EINVAL;
|
| 336 | goto err_clk_disable;
|
| 337 | }
|
| 338 |
|
| 339 | timer_virq = of_irq_get(np, 0);
|
| 340 | if (!timer_virq) {
|
| 341 | err = -EINVAL;
|
| 342 | goto err_clk_disable;
|
| 343 | }
|
| 344 |
|
| 345 | snprintf(ost->name, sizeof(ost->name), "OST percpu timer");
|
| 346 |
|
| 347 | err = request_irq(timer_virq, ingenic_ost_cevt_cb, IRQF_TIMER,
|
| 348 | ost->name, &ost->cevt);
|
| 349 | if (err)
|
| 350 | goto err_irq_dispose_mapping;
|
| 351 |
|
| 352 | ost->cevt.cpumask = cpumask_of(smp_processor_id());
|
| 353 | ost->cevt.features = CLOCK_EVT_FEAT_ONESHOT;
|
| 354 | ost->cevt.name = ost->name;
|
| 355 | ost->cevt.rating = 400;
|
| 356 | ost->cevt.set_state_shutdown = ingenic_ost_cevt_set_state_shutdown;
|
| 357 | ost->cevt.set_next_event = ingenic_ost_cevt_set_next;
|
| 358 |
|
| 359 | clockevents_config_and_register(&ost->cevt, rate, 4, 0xffffffff);
|
| 360 |
|
| 361 | return 0;
|
| 362 |
|
| 363 | err_irq_dispose_mapping:
|
| 364 | irq_dispose_mapping(timer_virq);
|
| 365 | err_clk_disable:
|
| 366 | clk_disable_unprepare(ost->percpu_timer_clk);
|
| 367 | err_clk_put:
|
| 368 | clk_put(ost->percpu_timer_clk);
|
| 369 | return err;
|
| 370 | }
|
| 371 |
|
| 372 | static int __init ingenic_ost_global_timer_init(struct device_node *np,
|
| 373 | struct ingenic_ost *ost)
|
| 374 | {
|
| 375 | unsigned int channel = OST_CLK_GLOBAL_TIMER;
|
| 376 | struct clocksource *cs = &ost->cs;
|
| 377 | unsigned long rate;
|
| 378 | int err;
|
| 379 |
|
| 380 | ost->global_timer_clk = ingenic_ost_get_clock(np, channel);
|
| 381 | if (IS_ERR(ost->global_timer_clk))
|
| 382 | return PTR_ERR(ost->global_timer_clk);
|
| 383 |
|
| 384 | err = clk_prepare_enable(ost->global_timer_clk);
|
| 385 | if (err)
|
| 386 | goto err_clk_put;
|
| 387 |
|
| 388 | rate = clk_get_rate(ost->global_timer_clk);
|
| 389 | if (!rate) {
|
| 390 | err = -EINVAL;
|
| 391 | goto err_clk_disable;
|
| 392 | }
|
| 393 |
|
| 394 |
|
| 395 | writel(OSTCR_OST2CLR, ost->base + OST_REG_OSTCR);
|
| 396 |
|
| 397 |
|
| 398 | writel(OSTESR_OST2ENS, ost->base + OST_REG_OSTESR);
|
| 399 |
|
| 400 | cs->name = "ingenic-ost";
|
| 401 | cs->rating = 400;
|
| 402 | cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
| 403 | cs->mask = CLOCKSOURCE_MASK(32);
|
| 404 | cs->read = ingenic_ost_clocksource_read;
|
| 405 |
|
| 406 | err = clocksource_register_hz(cs, rate);
|
| 407 | if (err)
|
| 408 | goto err_clk_disable;
|
| 409 |
|
| 410 | return 0;
|
| 411 |
|
| 412 | err_clk_disable:
|
| 413 | clk_disable_unprepare(ost->global_timer_clk);
|
| 414 | err_clk_put:
|
| 415 | clk_put(ost->global_timer_clk);
|
| 416 | return err;
|
| 417 | }
|
| 418 |
|
| 419 | static const struct ingenic_soc_info x1000_soc_info = {
|
| 420 | .num_channels = 2,
|
| 421 | };
|
| 422 |
|
| 423 | static const struct of_device_id __maybe_unused ingenic_ost_of_matches[] __initconst = {
|
| 424 | { .compatible = "ingenic,x1000-ost", .data = &x1000_soc_info },
|
| 425 | { }
|
| 426 | };
|
| 427 |
|
| 428 | static int __init ingenic_ost_probe(struct device_node *np)
|
| 429 | {
|
| 430 | const struct of_device_id *id = of_match_node(ingenic_ost_of_matches, np);
|
| 431 | struct ingenic_ost *ost;
|
| 432 | unsigned int i;
|
| 433 | int ret;
|
| 434 |
|
| 435 | ost = kzalloc_obj(*ost);
|
| 436 | if (!ost)
|
| 437 | return -ENOMEM;
|
| 438 |
|
| 439 | ost->base = of_io_request_and_map(np, 0, of_node_full_name(np));
|
| 440 | if (IS_ERR(ost->base)) {
|
| 441 | pr_err("%s: Failed to map OST registers\n", __func__);
|
| 442 | ret = PTR_ERR(ost->base);
|
| 443 | goto err_free_ost;
|
| 444 | }
|
| 445 |
|
| 446 | ost->clk = of_clk_get_by_name(np, "ost");
|
| 447 | if (IS_ERR(ost->clk)) {
|
| 448 | ret = PTR_ERR(ost->clk);
|
| 449 | pr_crit("%s: Cannot get OST clock\n", __func__);
|
| 450 | goto err_free_ost;
|
| 451 | }
|
| 452 |
|
| 453 | ret = clk_prepare_enable(ost->clk);
|
| 454 | if (ret) {
|
| 455 | pr_crit("%s: Unable to enable OST clock\n", __func__);
|
| 456 | goto err_put_clk;
|
| 457 | }
|
| 458 |
|
| 459 | ost->soc_info = id->data;
|
| 460 |
|
| 461 | ost->clocks = kzalloc_flex(*ost->clocks, hws,
|
| 462 | ost->soc_info->num_channels);
|
| 463 | if (!ost->clocks) {
|
| 464 | ret = -ENOMEM;
|
| 465 | goto err_clk_disable;
|
| 466 | }
|
| 467 |
|
| 468 | ost->clocks->num = ost->soc_info->num_channels;
|
| 469 |
|
| 470 | for (i = 0; i < ost->clocks->num; i++) {
|
| 471 | ret = ingenic_ost_register_clock(ost, i, &x1000_ost_clk_info[i], ost->clocks);
|
| 472 | if (ret) {
|
| 473 | pr_crit("%s: Cannot register clock %d\n", __func__, i);
|
| 474 | goto err_unregister_ost_clocks;
|
| 475 | }
|
| 476 | }
|
| 477 |
|
| 478 | ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, ost->clocks);
|
| 479 | if (ret) {
|
| 480 | pr_crit("%s: Cannot add OF clock provider\n", __func__);
|
| 481 | goto err_unregister_ost_clocks;
|
| 482 | }
|
| 483 |
|
| 484 | ingenic_ost = ost;
|
| 485 |
|
| 486 | return 0;
|
| 487 |
|
| 488 | err_unregister_ost_clocks:
|
| 489 | for (i = 0; i < ost->clocks->num; i++)
|
| 490 | if (ost->clocks->hws[i])
|
| 491 | clk_hw_unregister(ost->clocks->hws[i]);
|
| 492 | kfree(ost->clocks);
|
| 493 | err_clk_disable:
|
| 494 | clk_disable_unprepare(ost->clk);
|
| 495 | err_put_clk:
|
| 496 | clk_put(ost->clk);
|
| 497 | err_free_ost:
|
| 498 | kfree(ost);
|
| 499 | return ret;
|
| 500 | }
|
| 501 |
|
| 502 | static int __init ingenic_ost_init(struct device_node *np)
|
| 503 | {
|
| 504 | struct ingenic_ost *ost;
|
| 505 | unsigned long rate;
|
| 506 | int ret;
|
| 507 |
|
| 508 | ret = ingenic_ost_probe(np);
|
| 509 | if (ret) {
|
| 510 | pr_crit("%s: Failed to initialize OST clocks: %d\n", __func__, ret);
|
| 511 | return ret;
|
| 512 | }
|
| 513 |
|
| 514 | of_node_clear_flag(np, OF_POPULATED);
|
| 515 |
|
| 516 | ost = ingenic_ost;
|
| 517 | if (IS_ERR(ost))
|
| 518 | return PTR_ERR(ost);
|
| 519 |
|
| 520 | ret = ingenic_ost_global_timer_init(np, ost);
|
| 521 | if (ret) {
|
| 522 | pr_crit("%s: Unable to init global timer: %x\n", __func__, ret);
|
| 523 | goto err_free_ingenic_ost;
|
| 524 | }
|
| 525 |
|
| 526 | ret = ingenic_ost_percpu_timer_init(np, ost);
|
| 527 | if (ret)
|
| 528 | goto err_ost_global_timer_cleanup;
|
| 529 |
|
| 530 |
|
| 531 | rate = clk_get_rate(ost->global_timer_clk);
|
| 532 | sched_clock_register(ingenic_ost_global_timer_read_cntl, 32, rate);
|
| 533 |
|
| 534 | return 0;
|
| 535 |
|
| 536 | err_ost_global_timer_cleanup:
|
| 537 | clocksource_unregister(&ost->cs);
|
| 538 | clk_disable_unprepare(ost->global_timer_clk);
|
| 539 | clk_put(ost->global_timer_clk);
|
| 540 | err_free_ingenic_ost:
|
| 541 | kfree(ost);
|
| 542 | return ret;
|
| 543 | }
|
| 544 |
|
| 545 | TIMER_OF_DECLARE(x1000_ost, "ingenic,x1000-ost", ingenic_ost_init);
|