1 /* 2 * (C) Copyright 2017 Rockchip Electronics Co., Ltd 3 * 4 * SPDX-License-Identifier: GPL-2.0 5 */ 6 7 #include <common.h> 8 #include <clk-uclass.h> 9 #include <dm.h> 10 #include <errno.h> 11 #include <syscon.h> 12 #include <asm/io.h> 13 #include <asm/arch/clock.h> 14 #include <asm/arch/cru_rk322x.h> 15 #include <asm/arch/hardware.h> 16 #include <dm/lists.h> 17 #include <dt-bindings/clock/rk3228-cru.h> 18 #include <linux/log2.h> 19 20 DECLARE_GLOBAL_DATA_PTR; 21 22 #define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1)) 23 24 #ifndef CONFIG_SPL_BUILD 25 #define RK322x_CLK_DUMP(_id, _name, _iscru) \ 26 { \ 27 .id = _id, \ 28 .name = _name, \ 29 .is_cru = _iscru, \ 30 } 31 #endif 32 33 static struct rockchip_pll_rate_table rk322x_pll_rates[] = { 34 /* _mhz, _refdiv, _fbdiv, _postdiv1, _postdiv2, _dsmpd, _frac */ 35 RK3036_PLL_RATE(1200000000, 1, 50, 1, 1, 1, 0), 36 #ifndef CONFIG_SPL_BUILD 37 RK3036_PLL_RATE(1188000000, 1, 99, 2, 1, 1, 0), 38 RK3036_PLL_RATE(1008000000, 1, 84, 2, 1, 1, 0), 39 #endif 40 RK3036_PLL_RATE(816000000, 1, 68, 2, 1, 1, 0), 41 RK3036_PLL_RATE(800000000, 1, 100, 3, 1, 1, 0), 42 RK3036_PLL_RATE(600000000, 1, 75, 3, 1, 1, 0), 43 #ifndef CONFIG_SPL_BUILD 44 RK3036_PLL_RATE(594000000, 2, 99, 2, 1, 1, 0), 45 RK3036_PLL_RATE(500000000, 1, 125, 6, 1, 1, 0), 46 RK3036_PLL_RATE(400000000, 1, 50, 3, 1, 1, 0), 47 #endif 48 { /* sentinel */ }, 49 }; 50 51 #define RK322x_CPUCLK_RATE(_rate, _aclk_div, _pclk_div) \ 52 { \ 53 .rate = _rate##U, \ 54 .aclk_div = _aclk_div, \ 55 .pclk_div = _pclk_div, \ 56 } 57 58 static struct rockchip_cpu_rate_table rk322x_cpu_rates[] = { 59 RK322x_CPUCLK_RATE(1200000000, 1, 5), 60 RK322x_CPUCLK_RATE(1008000000, 1, 5), 61 RK322x_CPUCLK_RATE(816000000, 1, 3), 62 RK322x_CPUCLK_RATE(600000000, 1, 3), 63 }; 64 65 #ifndef CONFIG_SPL_BUILD 66 static const struct rk322x_clk_info clks_dump[] = { 67 RK322x_CLK_DUMP(PLL_APLL, "apll", true), 68 RK322x_CLK_DUMP(PLL_DPLL, "dpll", true), 69 RK322x_CLK_DUMP(PLL_CPLL, "cpll", true), 70 RK322x_CLK_DUMP(PLL_GPLL, "gpll", true), 71 RK322x_CLK_DUMP(ARMCLK, "armclk", true), 72 RK322x_CLK_DUMP(ACLK_CPU, "aclk_bus", true), 73 RK322x_CLK_DUMP(HCLK_CPU, "hclk_bus", true), 74 RK322x_CLK_DUMP(PCLK_CPU, "pclk_bus", true), 75 RK322x_CLK_DUMP(ACLK_PERI, "aclk_peri", true), 76 RK322x_CLK_DUMP(HCLK_PERI, "hclk_peri", true), 77 RK322x_CLK_DUMP(PCLK_PERI, "pclk_peri", true), 78 }; 79 #endif 80 81 static struct rockchip_pll_clock rk322x_pll_clks[] = { 82 [APLL] = PLL(pll_rk3036, PLL_APLL, RK2928_PLL_CON(0), 83 RK2928_MODE_CON, 0, 10, 0, rk322x_pll_rates), 84 [DPLL] = PLL(pll_rk3036, PLL_DPLL, RK2928_PLL_CON(3), 85 RK2928_MODE_CON, 4, 10, 0, rk322x_pll_rates), 86 [CPLL] = PLL(pll_rk3036, PLL_CPLL, RK2928_PLL_CON(6), 87 RK2928_MODE_CON, 8, 10, 0, rk322x_pll_rates), 88 [GPLL] = PLL(pll_rk3036, PLL_GPLL, RK2928_PLL_CON(9), 89 RK2928_MODE_CON, 12, 10, 0, rk322x_pll_rates), 90 }; 91 92 static ulong rk322x_armclk_set_clk(struct rk322x_clk_priv *priv, ulong hz) 93 { 94 struct rk322x_cru *cru = priv->cru; 95 const struct rockchip_cpu_rate_table *rate; 96 ulong old_rate; 97 98 rate = rockchip_get_cpu_settings(rk322x_cpu_rates, hz); 99 if (!rate) { 100 printf("%s unsupported rate\n", __func__); 101 return -EINVAL; 102 } 103 104 /* 105 * select apll as cpu/core clock pll source and 106 * set up dependent divisors for PERI and ACLK clocks. 107 * core hz : apll = 1:1 108 */ 109 old_rate = rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 110 priv->cru, APLL); 111 if (old_rate > hz) { 112 if (rockchip_pll_set_rate(&rk322x_pll_clks[APLL], 113 priv->cru, APLL, hz)) 114 return -EINVAL; 115 rk_clrsetreg(&cru->cru_clksel_con[0], 116 CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK, 117 CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT | 118 0 << CORE_DIV_CON_SHIFT); 119 rk_clrsetreg(&cru->cru_clksel_con[1], 120 CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK, 121 rate->aclk_div << CORE_ACLK_DIV_SHIFT | 122 rate->pclk_div << CORE_PERI_DIV_SHIFT); 123 } else if (old_rate < hz) { 124 rk_clrsetreg(&cru->cru_clksel_con[1], 125 CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK, 126 rate->aclk_div << CORE_ACLK_DIV_SHIFT | 127 rate->pclk_div << CORE_PERI_DIV_SHIFT); 128 rk_clrsetreg(&cru->cru_clksel_con[0], 129 CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK, 130 CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT | 131 0 << CORE_DIV_CON_SHIFT); 132 if (rockchip_pll_set_rate(&rk322x_pll_clks[APLL], 133 priv->cru, APLL, hz)) 134 return -EINVAL; 135 } 136 137 return rockchip_pll_get_rate(&rk322x_pll_clks[APLL], priv->cru, APLL); 138 } 139 140 static ulong rk322x_mmc_get_clk(struct rk322x_clk_priv *priv, 141 int periph) 142 { 143 struct rk322x_cru *cru = priv->cru; 144 uint src_rate; 145 uint div, mux; 146 u32 con; 147 148 switch (periph) { 149 case HCLK_EMMC: 150 case SCLK_EMMC: 151 case SCLK_EMMC_SAMPLE: 152 con = readl(&cru->cru_clksel_con[11]); 153 mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT; 154 con = readl(&cru->cru_clksel_con[12]); 155 div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT; 156 break; 157 case HCLK_SDMMC: 158 case SCLK_SDMMC: 159 case SCLK_SDMMC_SAMPLE: 160 con = readl(&cru->cru_clksel_con[11]); 161 mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT; 162 div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT; 163 break; 164 case SCLK_SDIO: 165 case SCLK_SDIO_SAMPLE: 166 con = readl(&cru->cru_clksel_con[11]); 167 mux = (con & SDIO_PLL_MASK) >> SDIO_PLL_SHIFT; 168 con = readl(&cru->cru_clksel_con[12]); 169 div = (con & SDIO_DIV_MASK) >> SDIO_DIV_SHIFT; 170 break; 171 default: 172 return -EINVAL; 173 } 174 175 src_rate = mux == EMMC_SEL_24M ? OSC_HZ : priv->gpll_hz; 176 return DIV_TO_RATE(src_rate, div) / 2; 177 } 178 179 #ifndef CONFIG_SPL_BUILD 180 static ulong rk322x_mac_set_clk(struct rk322x_clk_priv *priv, uint freq) 181 { 182 struct rk322x_cru *cru = priv->cru; 183 ulong ret; 184 185 /* 186 * The gmac clock can be derived either from an external clock 187 * or can be generated from internally by a divider from SCLK_MAC. 188 */ 189 if (readl(&cru->cru_clksel_con[5]) & BIT(5)) { 190 /* An external clock will always generate the right rate... */ 191 ret = freq; 192 } else { 193 u32 con = readl(&cru->cru_clksel_con[5]); 194 ulong pll_rate; 195 u8 div; 196 197 if (con & MAC_PLL_SEL_MASK) 198 pll_rate = priv->gpll_hz; 199 else 200 /* CPLL is not set */ 201 return -EPERM; 202 203 div = DIV_ROUND_UP(pll_rate, freq) - 1; 204 if (div <= 0x1f) 205 rk_clrsetreg(&cru->cru_clksel_con[5], CLK_MAC_DIV_MASK, 206 div << CLK_MAC_DIV_SHIFT); 207 else 208 debug("Unsupported div for gmac:%d\n", div); 209 210 return DIV_TO_RATE(pll_rate, div); 211 } 212 213 return ret; 214 } 215 #endif 216 217 static ulong rk322x_mmc_set_clk(struct rk322x_clk_priv *priv, 218 int periph, uint freq) 219 { 220 struct rk322x_cru *cru = priv->cru; 221 int src_clk_div; 222 int mux; 223 224 /* mmc clock defaulg div 2 internal, need provide double in cru */ 225 src_clk_div = DIV_ROUND_UP(priv->gpll_hz / 2, freq); 226 227 if (src_clk_div > 128) { 228 src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq); 229 assert(src_clk_div - 1 < 128); 230 mux = EMMC_SEL_24M; 231 } else { 232 mux = EMMC_SEL_GPLL; 233 } 234 235 switch (periph) { 236 case HCLK_EMMC: 237 case SCLK_EMMC: 238 case SCLK_EMMC_SAMPLE: 239 rk_clrsetreg(&cru->cru_clksel_con[11], 240 EMMC_PLL_MASK, 241 mux << EMMC_PLL_SHIFT); 242 rk_clrsetreg(&cru->cru_clksel_con[12], 243 EMMC_DIV_MASK, 244 (src_clk_div - 1) << EMMC_DIV_SHIFT); 245 break; 246 case HCLK_SDMMC: 247 case SCLK_SDMMC: 248 case SCLK_SDMMC_SAMPLE: 249 rk_clrsetreg(&cru->cru_clksel_con[11], 250 MMC0_PLL_MASK | MMC0_DIV_MASK, 251 mux << MMC0_PLL_SHIFT | 252 (src_clk_div - 1) << MMC0_DIV_SHIFT); 253 break; 254 case SCLK_SDIO: 255 case SCLK_SDIO_SAMPLE: 256 rk_clrsetreg(&cru->cru_clksel_con[11], 257 SDIO_PLL_MASK, 258 mux << SDIO_PLL_SHIFT); 259 rk_clrsetreg(&cru->cru_clksel_con[12], 260 SDIO_DIV_MASK, 261 (src_clk_div - 1) << SDIO_DIV_SHIFT); 262 break; 263 default: 264 return -EINVAL; 265 } 266 267 return rk322x_mmc_get_clk(priv, periph); 268 } 269 270 static ulong rk322x_bus_get_clk(struct rk322x_clk_priv *priv, ulong clk_id) 271 { 272 struct rk322x_cru *cru = priv->cru; 273 u32 div, con, parent; 274 275 switch (clk_id) { 276 case ACLK_CPU: 277 con = readl(&cru->cru_clksel_con[0]); 278 div = (con & BUS_ACLK_DIV_MASK) >> BUS_ACLK_DIV_SHIFT; 279 parent = priv->gpll_hz; 280 break; 281 case HCLK_CPU: 282 con = readl(&cru->cru_clksel_con[1]); 283 div = (con & BUS_HCLK_DIV_MASK) >> BUS_HCLK_DIV_SHIFT; 284 parent = rk322x_bus_get_clk(priv, ACLK_CPU); 285 break; 286 case PCLK_CPU: 287 case PCLK_I2C0: 288 case PCLK_I2C1: 289 case PCLK_I2C2: 290 case PCLK_I2C3: 291 case PCLK_PWM: 292 con = readl(&cru->cru_clksel_con[1]); 293 div = (con & BUS_PCLK_DIV_MASK) >> BUS_PCLK_DIV_SHIFT; 294 parent = rk322x_bus_get_clk(priv, ACLK_CPU); 295 break; 296 default: 297 return -ENOENT; 298 } 299 300 return DIV_TO_RATE(parent, div); 301 } 302 303 static ulong rk322x_bus_set_clk(struct rk322x_clk_priv *priv, 304 ulong clk_id, ulong hz) 305 { 306 struct rk322x_cru *cru = priv->cru; 307 int src_clk_div; 308 309 /* 310 * select gpll as pd_bus bus clock source and 311 * set up dependent divisors for PCLK/HCLK and ACLK clocks. 312 */ 313 switch (clk_id) { 314 case ACLK_CPU: 315 src_clk_div = DIV_ROUND_UP(priv->gpll_hz, hz); 316 assert(src_clk_div - 1 < 32); 317 rk_clrsetreg(&cru->cru_clksel_con[0], 318 BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK, 319 BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT | 320 (src_clk_div - 1) << BUS_ACLK_DIV_SHIFT); 321 break; 322 case HCLK_CPU: 323 src_clk_div = DIV_ROUND_UP(rk322x_bus_get_clk(priv, 324 ACLK_CPU), 325 hz); 326 assert(src_clk_div - 1 < 4); 327 rk_clrsetreg(&cru->cru_clksel_con[1], 328 BUS_HCLK_DIV_MASK, 329 (src_clk_div - 1) << BUS_HCLK_DIV_SHIFT); 330 break; 331 case PCLK_CPU: 332 src_clk_div = DIV_ROUND_UP(rk322x_bus_get_clk(priv, 333 ACLK_CPU), 334 hz); 335 assert(src_clk_div - 1 < 8); 336 rk_clrsetreg(&cru->cru_clksel_con[1], 337 BUS_PCLK_DIV_MASK, 338 (src_clk_div - 1) << BUS_PCLK_DIV_SHIFT); 339 break; 340 default: 341 printf("do not support this bus freq\n"); 342 return -EINVAL; 343 } 344 345 return rk322x_bus_get_clk(priv, clk_id); 346 } 347 348 static ulong rk322x_peri_get_clk(struct rk322x_clk_priv *priv, ulong clk_id) 349 { 350 struct rk322x_cru *cru = priv->cru; 351 u32 div, con, parent; 352 353 switch (clk_id) { 354 case ACLK_PERI: 355 con = readl(&cru->cru_clksel_con[10]); 356 div = (con & PERI_ACLK_DIV_MASK) >> PERI_ACLK_DIV_SHIFT; 357 parent = priv->gpll_hz; 358 break; 359 case HCLK_PERI: 360 con = readl(&cru->cru_clksel_con[10]); 361 div = (con & PERI_HCLK_DIV_MASK) >> PERI_HCLK_DIV_SHIFT; 362 parent = rk322x_peri_get_clk(priv, ACLK_PERI); 363 break; 364 case PCLK_PERI: 365 con = readl(&cru->cru_clksel_con[10]); 366 div = (con & PERI_PCLK_DIV_MASK) >> PERI_PCLK_DIV_SHIFT; 367 parent = rk322x_peri_get_clk(priv, ACLK_PERI); 368 break; 369 default: 370 return -ENOENT; 371 } 372 373 return DIV_TO_RATE(parent, div); 374 } 375 376 static ulong rk322x_peri_set_clk(struct rk322x_clk_priv *priv, 377 ulong clk_id, ulong hz) 378 { 379 struct rk322x_cru *cru = priv->cru; 380 int src_clk_div; 381 382 /* 383 * select gpll as pd_bus bus clock source and 384 * set up dependent divisors for PCLK/HCLK and ACLK clocks. 385 */ 386 switch (clk_id) { 387 case ACLK_PERI: 388 src_clk_div = DIV_ROUND_UP(priv->gpll_hz, hz); 389 assert(src_clk_div - 1 < 32); 390 rk_clrsetreg(&cru->cru_clksel_con[10], 391 PERI_PLL_SEL_MASK | PERI_ACLK_DIV_MASK, 392 PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT | 393 (src_clk_div - 1) << PERI_ACLK_DIV_SHIFT); 394 break; 395 case HCLK_PERI: 396 src_clk_div = DIV_ROUND_UP(rk322x_peri_get_clk(priv, 397 ACLK_PERI), 398 hz); 399 assert(src_clk_div - 1 < 4); 400 rk_clrsetreg(&cru->cru_clksel_con[10], 401 PERI_HCLK_DIV_MASK, 402 (src_clk_div - 1) << PERI_HCLK_DIV_SHIFT); 403 break; 404 case PCLK_PERI: 405 src_clk_div = DIV_ROUND_UP(rk322x_peri_get_clk(priv, 406 ACLK_PERI), 407 hz); 408 assert(src_clk_div - 1 < 8); 409 rk_clrsetreg(&cru->cru_clksel_con[10], 410 PERI_PCLK_DIV_MASK, 411 (src_clk_div - 1) << PERI_PCLK_DIV_SHIFT); 412 break; 413 default: 414 printf("do not support this bus freq\n"); 415 return -EINVAL; 416 } 417 418 return rk322x_peri_get_clk(priv, clk_id); 419 } 420 421 static ulong rk322x_spi_get_clk(struct rk322x_clk_priv *priv) 422 { 423 struct rk322x_cru *cru = priv->cru; 424 u32 div, con, parent; 425 426 con = readl(&cru->cru_clksel_con[25]); 427 div = (con & SPI_DIV_MASK) >> SPI_DIV_SHIFT; 428 parent = priv->gpll_hz; 429 430 return DIV_TO_RATE(parent, div); 431 } 432 433 static ulong rk322x_spi_set_clk(struct rk322x_clk_priv *priv, ulong hz) 434 { 435 struct rk322x_cru *cru = priv->cru; 436 int div; 437 438 div = DIV_ROUND_UP(priv->gpll_hz, hz); 439 assert(div - 1 < 128); 440 rk_clrsetreg(&cru->cru_clksel_con[25], 441 SPI_PLL_SEL_MASK | SPI_DIV_MASK, 442 SPI_PLL_SEL_GPLL << SPI_PLL_SEL_SHIFT | 443 (div - 1) << SPI_DIV_SHIFT); 444 return rk322x_spi_get_clk(priv); 445 } 446 447 #ifndef CONFIG_SPL_BUILD 448 static ulong rk322x_vop_get_clk(struct rk322x_clk_priv *priv, ulong clk_id) 449 { 450 struct rk322x_cru *cru = priv->cru; 451 u32 div, con, sel, parent; 452 453 switch (clk_id) { 454 case ACLK_VOP: 455 con = readl(&cru->cru_clksel_con[33]); 456 div = (con & ACLK_VOP_DIV_CON_MASK) >> ACLK_VOP_DIV_CON_SHIFT; 457 parent = priv->gpll_hz; 458 break; 459 case DCLK_VOP: 460 con = readl(&cru->cru_clksel_con[27]); 461 con = (con & DCLK_LCDC_SEL_MASK) >> DCLK_LCDC_SEL_SHIFT; 462 if (con) { 463 sel = readl(&cru->cru_clksel_con[27]); 464 sel = (sel & DCLK_LCDC_PLL_SEL_MASK) >> 465 DCLK_LCDC_PLL_SEL_SHIFT; 466 if (sel) 467 parent = priv->cpll_hz; 468 else 469 parent = priv->gpll_hz; 470 471 con = readl(&cru->cru_clksel_con[27]); 472 div = (con & DCLK_LCDC_DIV_CON_MASK) >> 473 DCLK_LCDC_DIV_CON_SHIFT; 474 } else { 475 parent = priv->cpll_hz; 476 div = 1; 477 } 478 break; 479 default: 480 return -ENOENT; 481 } 482 483 return DIV_TO_RATE(parent, div); 484 } 485 486 static ulong rk322x_vop_set_clk(struct rk322x_clk_priv *priv, 487 ulong clk_id, uint hz) 488 { 489 struct rk322x_cru *cru = priv->cru; 490 int src_clk_div; 491 u32 con, parent; 492 493 switch (clk_id) { 494 case ACLK_VOP: 495 src_clk_div = DIV_ROUND_UP(priv->gpll_hz, hz); 496 assert(src_clk_div - 1 < 32); 497 rk_clrsetreg(&cru->cru_clksel_con[33], 498 ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK, 499 ACLK_VOP_PLL_SEL_GPLL << ACLK_VOP_PLL_SEL_SHIFT | 500 (src_clk_div - 1) << ACLK_VOP_DIV_CON_SHIFT); 501 break; 502 case DCLK_VOP: 503 con = readl(&cru->cru_clksel_con[27]); 504 con = (con & DCLK_LCDC_SEL_MASK) >> DCLK_LCDC_SEL_SHIFT; 505 if (con) { 506 parent = readl(&cru->cru_clksel_con[27]); 507 parent = (parent & DCLK_LCDC_PLL_SEL_MASK) >> 508 DCLK_LCDC_PLL_SEL_SHIFT; 509 if (parent) 510 src_clk_div = DIV_ROUND_UP(priv->cpll_hz, hz); 511 else 512 src_clk_div = DIV_ROUND_UP(priv->gpll_hz, hz); 513 514 assert(src_clk_div - 1 < 256); 515 rk_clrsetreg(&cru->cru_clksel_con[27], 516 DCLK_LCDC_DIV_CON_MASK, 517 (src_clk_div - 1) << 518 DCLK_LCDC_DIV_CON_SHIFT); 519 } 520 break; 521 default: 522 printf("do not support this vop freq\n"); 523 return -EINVAL; 524 } 525 526 return rk322x_vop_get_clk(priv, clk_id); 527 } 528 529 static ulong rk322x_crypto_get_clk(struct rk322x_clk_priv *priv, ulong clk_id) 530 { 531 struct rk322x_cru *cru = priv->cru; 532 u32 div, con, parent; 533 534 switch (clk_id) { 535 case SCLK_CRYPTO: 536 con = readl(&cru->cru_clksel_con[24]); 537 div = (con & CRYPTO_DIV_MASK) >> CRYPTO_DIV_SHIFT; 538 parent = priv->gpll_hz; 539 break; 540 default: 541 return -ENOENT; 542 } 543 544 return DIV_TO_RATE(parent, div); 545 } 546 547 static ulong rk322x_crypto_set_clk(struct rk322x_clk_priv *priv, ulong clk_id, 548 ulong hz) 549 { 550 struct rk322x_cru *cru = priv->cru; 551 int src_clk_div; 552 553 src_clk_div = DIV_ROUND_UP(priv->gpll_hz, hz); 554 assert(src_clk_div - 1 <= 31); 555 556 /* 557 * select gpll as crypto clock source and 558 * set up dependent divisors for crypto clocks. 559 */ 560 switch (clk_id) { 561 case SCLK_CRYPTO: 562 rk_clrsetreg(&cru->cru_clksel_con[24], 563 CRYPTO_PLL_SEL_MASK | CRYPTO_DIV_MASK, 564 CRYPTO_PLL_SEL_GPLL << CRYPTO_PLL_SEL_SHIFT | 565 (src_clk_div - 1) << CRYPTO_DIV_SHIFT); 566 break; 567 default: 568 printf("do not support this peri freq\n"); 569 return -EINVAL; 570 } 571 572 return rk322x_crypto_get_clk(priv, clk_id); 573 } 574 #endif 575 576 static ulong rk322x_clk_get_rate(struct clk *clk) 577 { 578 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 579 ulong rate; 580 581 switch (clk->id) { 582 case PLL_APLL: 583 case PLL_DPLL: 584 case PLL_CPLL: 585 case PLL_GPLL: 586 rate = rockchip_pll_get_rate(&rk322x_pll_clks[clk->id - 1], 587 priv->cru, clk->id - 1); 588 break; 589 case ARMCLK: 590 rate = rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 591 priv->cru, APLL); 592 break; 593 case HCLK_EMMC: 594 case SCLK_EMMC: 595 case SCLK_EMMC_SAMPLE: 596 case HCLK_SDMMC: 597 case SCLK_SDMMC: 598 case SCLK_SDMMC_SAMPLE: 599 case SCLK_SDIO: 600 case SCLK_SDIO_SAMPLE: 601 rate = rk322x_mmc_get_clk(priv, clk->id); 602 break; 603 case SCLK_SPI0: 604 rate = rk322x_spi_get_clk(priv); 605 break; 606 case ACLK_CPU: 607 case HCLK_CPU: 608 case PCLK_CPU: 609 case PCLK_I2C0: 610 case PCLK_I2C1: 611 case PCLK_I2C2: 612 case PCLK_I2C3: 613 case PCLK_PWM: 614 rate = rk322x_bus_get_clk(priv, clk->id); 615 break; 616 case ACLK_PERI: 617 case HCLK_PERI: 618 case PCLK_PERI: 619 rate = rk322x_peri_get_clk(priv, clk->id); 620 break; 621 #ifndef CONFIG_SPL_BUILD 622 case DCLK_VOP: 623 case ACLK_VOP: 624 rate = rk322x_vop_get_clk(priv, clk->id); 625 break; 626 case SCLK_CRYPTO: 627 rate = rk322x_crypto_get_clk(priv, clk->id); 628 break; 629 #endif 630 default: 631 return -ENOENT; 632 } 633 634 return rate; 635 } 636 637 static ulong rk322x_clk_set_rate(struct clk *clk, ulong rate) 638 { 639 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 640 ulong ret = 0; 641 642 switch (clk->id) { 643 case PLL_APLL: 644 case PLL_DPLL: 645 ret = rockchip_pll_set_rate(&rk322x_pll_clks[clk->id - 1], 646 priv->cru, clk->id - 1, rate); 647 break; 648 case PLL_CPLL: 649 ret = rockchip_pll_set_rate(&rk322x_pll_clks[CPLL], 650 priv->cru, CPLL, rate); 651 priv->cpll_hz = rate; 652 break; 653 case PLL_GPLL: 654 ret = rockchip_pll_set_rate(&rk322x_pll_clks[GPLL], 655 priv->cru, GPLL, rate); 656 priv->gpll_hz = rate; 657 break; 658 case ARMCLK: 659 if (priv->armclk_hz) 660 ret = rk322x_armclk_set_clk(priv, rate); 661 priv->armclk_hz = rate; 662 break; 663 case HCLK_EMMC: 664 case SCLK_EMMC: 665 case SCLK_EMMC_SAMPLE: 666 case HCLK_SDMMC: 667 case SCLK_SDMMC: 668 case SCLK_SDMMC_SAMPLE: 669 case SCLK_SDIO: 670 case SCLK_SDIO_SAMPLE: 671 ret = rk322x_mmc_set_clk(priv, clk->id, rate); 672 break; 673 case SCLK_DDRC: 674 ret = rockchip_pll_set_rate(&rk322x_pll_clks[DPLL], 675 priv->cru, DPLL, rate); 676 break; 677 case SCLK_SPI0: 678 rate = rk322x_spi_set_clk(priv, rate); 679 break; 680 case ACLK_CPU: 681 case HCLK_CPU: 682 case PCLK_CPU: 683 ret = rk322x_bus_set_clk(priv, clk->id, rate); 684 break; 685 case ACLK_PERI: 686 case HCLK_PERI: 687 case PCLK_PERI: 688 ret = rk322x_peri_set_clk(priv, clk->id, rate); 689 break; 690 #ifndef CONFIG_SPL_BUILD 691 case SCLK_MAC_SRC: 692 case SCLK_MAC: 693 ret = rk322x_mac_set_clk(priv, rate); 694 break; 695 case DCLK_VOP: 696 case ACLK_VOP: 697 ret = rk322x_vop_set_clk(priv, clk->id, rate); 698 break; 699 case SCLK_CRYPTO: 700 ret = rk322x_crypto_set_clk(priv, clk->id, rate); 701 break; 702 #endif 703 default: 704 return -ENOENT; 705 } 706 707 return ret; 708 } 709 710 #ifndef CONFIG_SPL_BUILD 711 static int rk322x_gmac_set_parent(struct clk *clk, struct clk *parent) 712 { 713 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 714 struct rk322x_cru *cru = priv->cru; 715 716 /* 717 * If the requested parent is in the same clock-controller and the id 718 * is SCLK_MAC_SRC ("sclk_gmac_src"), switch to the internal clock. 719 */ 720 if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_SRC)) { 721 debug("%s: switching RGMII to SCLK_MAC_SRC\n", __func__); 722 rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), 0); 723 return 0; 724 } 725 726 /* 727 * If the requested parent is in the same clock-controller and the id 728 * is SCLK_MAC_EXTCLK (sclk_mac_extclk), switch to the external clock. 729 */ 730 if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_EXTCLK)) { 731 debug("%s: switching RGMII to SCLK_MAC_EXTCLK\n", __func__); 732 rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), BIT(5)); 733 return 0; 734 } 735 736 return -EINVAL; 737 } 738 739 static int rk322x_gmac_extclk_set_parent(struct clk *clk, struct clk *parent) 740 { 741 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 742 const char *clock_output_name; 743 struct rk322x_cru *cru = priv->cru; 744 int ret; 745 746 ret = dev_read_string_index(parent->dev, "clock-output-names", 747 parent->id, &clock_output_name); 748 if (ret < 0) 749 return -ENODATA; 750 751 if (!strcmp(clock_output_name, "ext_gmac")) { 752 debug("%s: switching gmac extclk to ext_gmac\n", __func__); 753 rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), 0); 754 return 0; 755 } else if (!strcmp(clock_output_name, "phy_50m_out")) { 756 debug("%s: switching gmac extclk to phy_50m_out\n", __func__); 757 rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), BIT(10)); 758 return 0; 759 } 760 761 return -EINVAL; 762 } 763 764 static int rk322x_lcdc_set_parent(struct clk *clk, struct clk *parent) 765 { 766 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 767 768 if (parent->id == HDMIPHY) 769 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 770 DCLK_LCDC_SEL_MASK, 771 DCLK_LCDC_SEL_HDMIPHY << DCLK_LCDC_SEL_SHIFT); 772 else if (parent->id == PLL_CPLL) 773 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 774 DCLK_LCDC_SEL_MASK | DCLK_LCDC_PLL_SEL_MASK, 775 (DCLK_LCDC_SEL_PLL << DCLK_LCDC_SEL_SHIFT) | 776 (DCLK_LCDC_PLL_SEL_CPLL << 777 DCLK_LCDC_PLL_SEL_SHIFT)); 778 else 779 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 780 DCLK_LCDC_SEL_MASK | DCLK_LCDC_PLL_SEL_MASK, 781 (DCLK_LCDC_SEL_PLL << DCLK_LCDC_SEL_SHIFT) | 782 (DCLK_LCDC_PLL_SEL_GPLL << 783 DCLK_LCDC_PLL_SEL_SHIFT)); 784 785 return 0; 786 } 787 #endif 788 789 static int rk322x_clk_set_parent(struct clk *clk, struct clk *parent) 790 { 791 switch (clk->id) { 792 #ifndef CONFIG_SPL_BUILD 793 case SCLK_MAC: 794 return rk322x_gmac_set_parent(clk, parent); 795 case SCLK_MAC_EXTCLK: 796 return rk322x_gmac_extclk_set_parent(clk, parent); 797 case DCLK_VOP: 798 return rk322x_lcdc_set_parent(clk, parent); 799 #endif 800 } 801 802 debug("%s: unsupported clk %ld\n", __func__, clk->id); 803 return -ENOENT; 804 } 805 806 #define ROCKCHIP_MMC_DELAY_SEL BIT(10) 807 #define ROCKCHIP_MMC_DEGREE_MASK 0x3 808 #define ROCKCHIP_MMC_DELAYNUM_OFFSET 2 809 #define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET) 810 811 #define PSECS_PER_SEC 1000000000000LL 812 /* 813 * Each fine delay is between 44ps-77ps. Assume each fine delay is 60ps to 814 * simplify calculations. So 45degs could be anywhere between 33deg and 57.8deg. 815 */ 816 #define ROCKCHIP_MMC_DELAY_ELEMENT_PSEC 60 817 818 int rk322x_mmc_get_phase(struct clk *clk) 819 { 820 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 821 struct rk322x_cru *cru = priv->cru; 822 u32 raw_value, delay_num; 823 u16 degrees = 0; 824 ulong rate; 825 826 rate = rk322x_clk_get_rate(clk); 827 828 if (rate < 0) 829 return rate; 830 831 if (clk->id == SCLK_EMMC_SAMPLE) 832 raw_value = readl(&cru->cru_emmc_con[1]); 833 else if (clk->id == SCLK_SDMMC_SAMPLE) 834 raw_value = readl(&cru->cru_sdmmc_con[1]); 835 else 836 raw_value = readl(&cru->cru_sdio_con[1]); 837 838 raw_value >>= 1; 839 degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90; 840 841 if (raw_value & ROCKCHIP_MMC_DELAY_SEL) { 842 /* degrees/delaynum * 10000 */ 843 unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) * 844 36 * (rate / 1000000); 845 846 delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK); 847 delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET; 848 degrees += DIV_ROUND_CLOSEST(delay_num * factor, 10000); 849 } 850 851 return degrees % 360; 852 } 853 854 int rk322x_mmc_set_phase(struct clk *clk, u32 degrees) 855 { 856 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 857 struct rk322x_cru *cru = priv->cru; 858 u8 nineties, remainder, delay_num; 859 u32 raw_value, delay; 860 ulong rate; 861 862 rate = rk322x_clk_get_rate(clk); 863 864 if (rate < 0) 865 return rate; 866 867 nineties = degrees / 90; 868 remainder = (degrees % 90); 869 870 /* 871 * Convert to delay; do a little extra work to make sure we 872 * don't overflow 32-bit / 64-bit numbers. 873 */ 874 delay = 10000000; /* PSECS_PER_SEC / 10000 / 10 */ 875 delay *= remainder; 876 delay = DIV_ROUND_CLOSEST(delay, (rate / 1000) * 36 * 877 (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10)); 878 879 delay_num = (u8)min_t(u32, delay, 255); 880 881 raw_value = delay_num ? ROCKCHIP_MMC_DELAY_SEL : 0; 882 raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET; 883 raw_value |= nineties; 884 885 raw_value <<= 1; 886 if (clk->id == SCLK_EMMC_SAMPLE) 887 writel(raw_value | 0xffff0000, &cru->cru_emmc_con[1]); 888 else if (clk->id == SCLK_SDMMC_SAMPLE) 889 writel(raw_value | 0xffff0000, &cru->cru_sdmmc_con[1]); 890 else 891 writel(raw_value | 0xffff0000, &cru->cru_sdio_con[1]); 892 893 debug("mmc set_phase(%d) delay_nums=%u reg=%#x actual_degrees=%d\n", 894 degrees, delay_num, raw_value, rk322x_mmc_get_phase(clk)); 895 896 return 0; 897 } 898 899 static int rk322x_clk_get_phase(struct clk *clk) 900 { 901 int ret; 902 903 debug("%s %ld\n", __func__, clk->id); 904 switch (clk->id) { 905 case SCLK_EMMC_SAMPLE: 906 case SCLK_SDMMC_SAMPLE: 907 case SCLK_SDIO_SAMPLE: 908 ret = rk322x_mmc_get_phase(clk); 909 break; 910 default: 911 return -ENOENT; 912 } 913 914 return ret; 915 } 916 917 static int rk322x_clk_set_phase(struct clk *clk, int degrees) 918 { 919 int ret; 920 921 debug("%s %ld\n", __func__, clk->id); 922 switch (clk->id) { 923 case SCLK_EMMC_SAMPLE: 924 case SCLK_SDMMC_SAMPLE: 925 case SCLK_SDIO_SAMPLE: 926 ret = rk322x_mmc_set_phase(clk, degrees); 927 break; 928 default: 929 return -ENOENT; 930 } 931 932 return ret; 933 } 934 935 static struct clk_ops rk322x_clk_ops = { 936 .get_rate = rk322x_clk_get_rate, 937 .set_rate = rk322x_clk_set_rate, 938 .set_parent = rk322x_clk_set_parent, 939 .get_phase = rk322x_clk_get_phase, 940 .set_phase = rk322x_clk_set_phase, 941 }; 942 943 static int rk322x_clk_ofdata_to_platdata(struct udevice *dev) 944 { 945 struct rk322x_clk_priv *priv = dev_get_priv(dev); 946 947 priv->cru = dev_read_addr_ptr(dev); 948 949 return 0; 950 } 951 952 #ifndef CONFIG_TPL_BUILD 953 static void rkclk_init(struct rk322x_clk_priv *priv) 954 { 955 struct rk322x_cru *cru = priv->cru; 956 957 if (rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 958 priv->cru, APLL) != APLL_HZ) 959 rk322x_armclk_set_clk(priv, APLL_HZ); 960 961 priv->gpll_hz = rockchip_pll_get_rate(&rk322x_pll_clks[GPLL], 962 priv->cru, GPLL); 963 priv->cpll_hz = rockchip_pll_get_rate(&rk322x_pll_clks[CPLL], 964 priv->cru, CPLL); 965 966 /* before set pll set child div first */ 967 rk322x_bus_set_clk(priv, ACLK_CPU, ACLK_BUS_HZ / 4); 968 rk322x_peri_set_clk(priv, ACLK_PERI, ACLK_PERI_HZ / 4); 969 rk322x_mmc_set_clk(priv, SCLK_EMMC, 50000000); 970 rk322x_mmc_set_clk(priv, SCLK_SDMMC, 50000000); 971 rk322x_mmc_set_clk(priv, SCLK_SDIO, 50000000); 972 rk_clrsetreg(&cru->cru_clksel_con[2], (0x1 << 14) | 973 (0x1f << 8), (1 << 14) | (0xb << 8)); 974 rk_clrsetreg(&cru->cru_clksel_con[23], (0x1f << 0) | (0x1f << 8), 975 (0x1f << 0) | (5 << 8)); 976 rk_clrsetreg(&cru->cru_clksel_con[33], 977 ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK, 978 ACLK_VOP_PLL_SEL_GPLL << ACLK_VOP_PLL_SEL_SHIFT | 979 3 << ACLK_VOP_DIV_CON_SHIFT); 980 rk_clrsetreg(&cru->cru_clksel_con[22], 0x1f << 0, 5 << 0); 981 rk_clrsetreg(&cru->cru_clksel_con[24], 0x1f << 0, 0xb << 0); 982 rk_clrsetreg(&cru->cru_clksel_con[28], (0x1f << 8) | (0x1f << 0), 983 (5 << 8) | (5 << 0)); 984 rk_clrsetreg(&cru->cru_clksel_con[31], (0x1f << 8) | (0x1f << 0), 985 (5 << 8) | (5 << 0)); 986 rk_clrsetreg(&cru->cru_clksel_con[32], 0x1f << 0, 5 << 0); 987 rk_clrsetreg(&cru->cru_clksel_con[33], (0x1f << 8) | (0x1f << 0), 988 (5 << 8) | (5 << 0)); 989 rk_clrsetreg(&cru->cru_clksel_con[34], (0x1f << 8) | (0x1f << 0), 990 (5 << 8) | (3 << 0)); 991 992 rockchip_pll_set_rate(&rk322x_pll_clks[GPLL], 993 priv->cru, GPLL, GPLL_HZ); 994 priv->gpll_hz = GPLL_HZ; 995 996 rockchip_pll_set_rate(&rk322x_pll_clks[CPLL], 997 priv->cru, CPLL, CPLL_HZ); 998 priv->cpll_hz = CPLL_HZ; 999 1000 rk322x_bus_set_clk(priv, ACLK_CPU, ACLK_BUS_HZ); 1001 rk322x_bus_set_clk(priv, HCLK_CPU, ACLK_BUS_HZ / 2); 1002 rk322x_bus_set_clk(priv, PCLK_CPU, ACLK_BUS_HZ / 2); 1003 rk322x_peri_set_clk(priv, ACLK_PERI, ACLK_PERI_HZ); 1004 rk322x_peri_set_clk(priv, HCLK_PERI, ACLK_PERI_HZ / 2); 1005 rk322x_peri_set_clk(priv, PCLK_PERI, ACLK_PERI_HZ / 2); 1006 /*rk322x_mmc_set_clk(priv, SCLK_EMMC, rate);*/ 1007 1008 /* set usbphy and hdmiphy from phy */ 1009 rk_clrsetreg(&cru->cru_misc_con, (0x1 << 13) | 1010 (0x1 << 15), (0 << 15) | (0 << 13)); 1011 } 1012 #endif 1013 1014 static int rk322x_clk_probe(struct udevice *dev) 1015 { 1016 #ifndef CONFIG_TPL_BUILD 1017 struct rk322x_clk_priv *priv = dev_get_priv(dev); 1018 int ret = 0; 1019 1020 priv->sync_kernel = false; 1021 if (!priv->armclk_enter_hz) 1022 priv->armclk_enter_hz = 1023 rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 1024 priv->cru, APLL); 1025 rkclk_init(priv); 1026 if (!priv->armclk_init_hz) 1027 priv->armclk_init_hz = 1028 rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 1029 priv->cru, APLL); 1030 ret = clk_set_defaults(dev); 1031 if (ret) 1032 debug("%s clk_set_defaults failed %d\n", __func__, ret); 1033 else 1034 priv->sync_kernel = true; 1035 #endif 1036 return 0; 1037 } 1038 1039 static int rk322x_clk_bind(struct udevice *dev) 1040 { 1041 int ret; 1042 struct udevice *sys_child, *sf_child; 1043 struct sysreset_reg *priv; 1044 struct softreset_reg *sf_priv; 1045 1046 /* The reset driver does not have a device node, so bind it here */ 1047 ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset", 1048 &sys_child); 1049 if (ret) { 1050 debug("Warning: No sysreset driver: ret=%d\n", ret); 1051 } else { 1052 priv = malloc(sizeof(struct sysreset_reg)); 1053 priv->glb_srst_fst_value = offsetof(struct rk322x_cru, 1054 cru_glb_srst_fst_value); 1055 priv->glb_srst_snd_value = offsetof(struct rk322x_cru, 1056 cru_glb_srst_snd_value); 1057 sys_child->priv = priv; 1058 } 1059 1060 ret = device_bind_driver_to_node(dev, "rockchip_reset", "reset", 1061 dev_ofnode(dev), &sf_child); 1062 if (ret) { 1063 debug("Warning: No rockchip reset driver: ret=%d\n", ret); 1064 } else { 1065 sf_priv = malloc(sizeof(struct softreset_reg)); 1066 sf_priv->sf_reset_offset = offsetof(struct rk322x_cru, 1067 cru_softrst_con[0]); 1068 sf_priv->sf_reset_num = 9; 1069 sf_child->priv = sf_priv; 1070 } 1071 1072 return 0; 1073 } 1074 1075 static const struct udevice_id rk322x_clk_ids[] = { 1076 { .compatible = "rockchip,rk3228-cru" }, 1077 { } 1078 }; 1079 1080 U_BOOT_DRIVER(rockchip_rk322x_cru) = { 1081 .name = "clk_rk322x", 1082 .id = UCLASS_CLK, 1083 .of_match = rk322x_clk_ids, 1084 .priv_auto_alloc_size = sizeof(struct rk322x_clk_priv), 1085 .ofdata_to_platdata = rk322x_clk_ofdata_to_platdata, 1086 .ops = &rk322x_clk_ops, 1087 .bind = rk322x_clk_bind, 1088 .probe = rk322x_clk_probe, 1089 }; 1090 1091 #ifndef CONFIG_SPL_BUILD 1092 /** 1093 * soc_clk_dump() - Print clock frequencies 1094 * Returns zero on success 1095 * 1096 * Implementation for the clk dump command. 1097 */ 1098 int soc_clk_dump(void) 1099 { 1100 struct udevice *cru_dev; 1101 struct rk322x_clk_priv *priv; 1102 const struct rk322x_clk_info *clk_dump; 1103 struct clk clk; 1104 unsigned long clk_count = ARRAY_SIZE(clks_dump); 1105 unsigned long rate; 1106 int i, ret; 1107 1108 ret = uclass_get_device_by_driver(UCLASS_CLK, 1109 DM_GET_DRIVER(rockchip_rk322x_cru), 1110 &cru_dev); 1111 if (ret) { 1112 printf("%s failed to get cru device\n", __func__); 1113 return ret; 1114 } 1115 1116 priv = dev_get_priv(cru_dev); 1117 printf("CLK: (%s. arm: enter %lu KHz, init %lu KHz, kernel %lu%s)\n", 1118 priv->sync_kernel ? "sync kernel" : "uboot", 1119 priv->armclk_enter_hz / 1000, 1120 priv->armclk_init_hz / 1000, 1121 priv->set_armclk_rate ? priv->armclk_hz / 1000 : 0, 1122 priv->set_armclk_rate ? " KHz" : "N/A"); 1123 for (i = 0; i < clk_count; i++) { 1124 clk_dump = &clks_dump[i]; 1125 if (clk_dump->name) { 1126 clk.id = clk_dump->id; 1127 if (clk_dump->is_cru) 1128 ret = clk_request(cru_dev, &clk); 1129 if (ret < 0) 1130 return ret; 1131 1132 rate = clk_get_rate(&clk); 1133 clk_free(&clk); 1134 if (i == 0) { 1135 if (rate < 0) 1136 printf(" %s %s\n", clk_dump->name, 1137 "unknown"); 1138 else 1139 printf(" %s %lu KHz\n", clk_dump->name, 1140 rate / 1000); 1141 } else { 1142 if (rate < 0) 1143 printf(" %s %s\n", clk_dump->name, 1144 "unknown"); 1145 else 1146 printf(" %s %lu KHz\n", clk_dump->name, 1147 rate / 1000); 1148 } 1149 } 1150 } 1151 1152 return 0; 1153 } 1154 #endif 1155 1156