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_SHIFT) & 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: 692 ret = rk322x_mac_set_clk(priv, rate); 693 break; 694 case DCLK_VOP: 695 case ACLK_VOP: 696 ret = rk322x_vop_set_clk(priv, clk->id, rate); 697 break; 698 case SCLK_CRYPTO: 699 ret = rk322x_crypto_set_clk(priv, clk->id, rate); 700 break; 701 #endif 702 default: 703 return -ENOENT; 704 } 705 706 return ret; 707 } 708 709 #ifndef CONFIG_SPL_BUILD 710 static int rk322x_gmac_set_parent(struct clk *clk, struct clk *parent) 711 { 712 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 713 struct rk322x_cru *cru = priv->cru; 714 715 /* 716 * If the requested parent is in the same clock-controller and the id 717 * is SCLK_MAC_SRC ("sclk_gmac_src"), switch to the internal clock. 718 */ 719 if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_SRC)) { 720 debug("%s: switching RGMII to SCLK_MAC_SRC\n", __func__); 721 rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), 0); 722 return 0; 723 } 724 725 /* 726 * If the requested parent is in the same clock-controller and the id 727 * is SCLK_MAC_EXTCLK (sclk_mac_extclk), switch to the external clock. 728 */ 729 if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_EXTCLK)) { 730 debug("%s: switching RGMII to SCLK_MAC_EXTCLK\n", __func__); 731 rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), BIT(5)); 732 return 0; 733 } 734 735 return -EINVAL; 736 } 737 738 static int rk322x_gmac_extclk_set_parent(struct clk *clk, struct clk *parent) 739 { 740 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 741 const char *clock_output_name; 742 struct rk322x_cru *cru = priv->cru; 743 int ret; 744 745 ret = dev_read_string_index(parent->dev, "clock-output-names", 746 parent->id, &clock_output_name); 747 if (ret < 0) 748 return -ENODATA; 749 750 if (!strcmp(clock_output_name, "ext_gmac")) { 751 debug("%s: switching gmac extclk to ext_gmac\n", __func__); 752 rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), 0); 753 return 0; 754 } else if (!strcmp(clock_output_name, "phy_50m_out")) { 755 debug("%s: switching gmac extclk to phy_50m_out\n", __func__); 756 rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), BIT(10)); 757 return 0; 758 } 759 760 return -EINVAL; 761 } 762 763 static int rk322x_lcdc_set_parent(struct clk *clk, struct clk *parent) 764 { 765 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 766 767 if (parent->id == HDMIPHY) 768 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 769 DCLK_LCDC_SEL_MASK, 770 DCLK_LCDC_SEL_HDMIPHY << DCLK_LCDC_SEL_SHIFT); 771 else if (parent->id == PLL_CPLL) 772 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 773 DCLK_LCDC_SEL_MASK | DCLK_LCDC_PLL_SEL_MASK, 774 (DCLK_LCDC_SEL_PLL << DCLK_LCDC_SEL_SHIFT) | 775 (DCLK_LCDC_PLL_SEL_CPLL << 776 DCLK_LCDC_PLL_SEL_SHIFT)); 777 else 778 rk_clrsetreg(&priv->cru->cru_clksel_con[27], 779 DCLK_LCDC_SEL_MASK | DCLK_LCDC_PLL_SEL_MASK, 780 (DCLK_LCDC_SEL_PLL << DCLK_LCDC_SEL_SHIFT) | 781 (DCLK_LCDC_PLL_SEL_GPLL << 782 DCLK_LCDC_PLL_SEL_SHIFT)); 783 784 return 0; 785 } 786 #endif 787 788 static int rk322x_clk_set_parent(struct clk *clk, struct clk *parent) 789 { 790 switch (clk->id) { 791 #ifndef CONFIG_SPL_BUILD 792 case SCLK_MAC: 793 return rk322x_gmac_set_parent(clk, parent); 794 case SCLK_MAC_EXTCLK: 795 return rk322x_gmac_extclk_set_parent(clk, parent); 796 case DCLK_VOP: 797 return rk322x_lcdc_set_parent(clk, parent); 798 #endif 799 } 800 801 debug("%s: unsupported clk %ld\n", __func__, clk->id); 802 return -ENOENT; 803 } 804 805 #define ROCKCHIP_MMC_DELAY_SEL BIT(10) 806 #define ROCKCHIP_MMC_DEGREE_MASK 0x3 807 #define ROCKCHIP_MMC_DELAYNUM_OFFSET 2 808 #define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET) 809 810 #define PSECS_PER_SEC 1000000000000LL 811 /* 812 * Each fine delay is between 44ps-77ps. Assume each fine delay is 60ps to 813 * simplify calculations. So 45degs could be anywhere between 33deg and 57.8deg. 814 */ 815 #define ROCKCHIP_MMC_DELAY_ELEMENT_PSEC 60 816 817 int rk322x_mmc_get_phase(struct clk *clk) 818 { 819 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 820 struct rk322x_cru *cru = priv->cru; 821 u32 raw_value, delay_num; 822 u16 degrees = 0; 823 ulong rate; 824 825 rate = rk322x_clk_get_rate(clk); 826 827 if (rate < 0) 828 return rate; 829 830 if (clk->id == SCLK_EMMC_SAMPLE) 831 raw_value = readl(&cru->cru_emmc_con[1]); 832 else if (clk->id == SCLK_SDMMC_SAMPLE) 833 raw_value = readl(&cru->cru_sdmmc_con[1]); 834 else 835 raw_value = readl(&cru->cru_sdio_con[1]); 836 837 raw_value >>= 1; 838 degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90; 839 840 if (raw_value & ROCKCHIP_MMC_DELAY_SEL) { 841 /* degrees/delaynum * 10000 */ 842 unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) * 843 36 * (rate / 1000000); 844 845 delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK); 846 delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET; 847 degrees += DIV_ROUND_CLOSEST(delay_num * factor, 10000); 848 } 849 850 return degrees % 360; 851 } 852 853 int rk322x_mmc_set_phase(struct clk *clk, u32 degrees) 854 { 855 struct rk322x_clk_priv *priv = dev_get_priv(clk->dev); 856 struct rk322x_cru *cru = priv->cru; 857 u8 nineties, remainder, delay_num; 858 u32 raw_value, delay; 859 ulong rate; 860 861 rate = rk322x_clk_get_rate(clk); 862 863 if (rate < 0) 864 return rate; 865 866 nineties = degrees / 90; 867 remainder = (degrees % 90); 868 869 /* 870 * Convert to delay; do a little extra work to make sure we 871 * don't overflow 32-bit / 64-bit numbers. 872 */ 873 delay = 10000000; /* PSECS_PER_SEC / 10000 / 10 */ 874 delay *= remainder; 875 delay = DIV_ROUND_CLOSEST(delay, (rate / 1000) * 36 * 876 (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10)); 877 878 delay_num = (u8)min_t(u32, delay, 255); 879 880 raw_value = delay_num ? ROCKCHIP_MMC_DELAY_SEL : 0; 881 raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET; 882 raw_value |= nineties; 883 884 raw_value <<= 1; 885 if (clk->id == SCLK_EMMC_SAMPLE) 886 writel(raw_value | 0xffff0000, &cru->cru_emmc_con[1]); 887 else if (clk->id == SCLK_SDMMC_SAMPLE) 888 writel(raw_value | 0xffff0000, &cru->cru_sdmmc_con[1]); 889 else 890 writel(raw_value | 0xffff0000, &cru->cru_sdio_con[1]); 891 892 debug("mmc set_phase(%d) delay_nums=%u reg=%#x actual_degrees=%d\n", 893 degrees, delay_num, raw_value, rk322x_mmc_get_phase(clk)); 894 895 return 0; 896 } 897 898 static int rk322x_clk_get_phase(struct clk *clk) 899 { 900 int ret; 901 902 debug("%s %ld\n", __func__, clk->id); 903 switch (clk->id) { 904 case SCLK_EMMC_SAMPLE: 905 case SCLK_SDMMC_SAMPLE: 906 case SCLK_SDIO_SAMPLE: 907 ret = rk322x_mmc_get_phase(clk); 908 break; 909 default: 910 return -ENOENT; 911 } 912 913 return ret; 914 } 915 916 static int rk322x_clk_set_phase(struct clk *clk, int degrees) 917 { 918 int ret; 919 920 debug("%s %ld\n", __func__, clk->id); 921 switch (clk->id) { 922 case SCLK_EMMC_SAMPLE: 923 case SCLK_SDMMC_SAMPLE: 924 case SCLK_SDIO_SAMPLE: 925 ret = rk322x_mmc_set_phase(clk, degrees); 926 break; 927 default: 928 return -ENOENT; 929 } 930 931 return ret; 932 } 933 934 static struct clk_ops rk322x_clk_ops = { 935 .get_rate = rk322x_clk_get_rate, 936 .set_rate = rk322x_clk_set_rate, 937 .set_parent = rk322x_clk_set_parent, 938 .get_phase = rk322x_clk_get_phase, 939 .set_phase = rk322x_clk_set_phase, 940 }; 941 942 static int rk322x_clk_ofdata_to_platdata(struct udevice *dev) 943 { 944 struct rk322x_clk_priv *priv = dev_get_priv(dev); 945 946 priv->cru = dev_read_addr_ptr(dev); 947 948 return 0; 949 } 950 951 #ifndef CONFIG_TPL_BUILD 952 static void rkclk_init(struct rk322x_clk_priv *priv) 953 { 954 struct rk322x_cru *cru = priv->cru; 955 956 if (rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 957 priv->cru, APLL) != APLL_HZ) 958 rk322x_armclk_set_clk(priv, APLL_HZ); 959 960 priv->gpll_hz = rockchip_pll_get_rate(&rk322x_pll_clks[GPLL], 961 priv->cru, GPLL); 962 priv->cpll_hz = rockchip_pll_get_rate(&rk322x_pll_clks[CPLL], 963 priv->cru, CPLL); 964 965 /* before set pll set child div first */ 966 rk322x_bus_set_clk(priv, ACLK_CPU, ACLK_BUS_HZ / 4); 967 rk322x_peri_set_clk(priv, ACLK_PERI, ACLK_PERI_HZ / 4); 968 rk322x_mmc_set_clk(priv, SCLK_EMMC, 50000000); 969 rk322x_mmc_set_clk(priv, SCLK_SDMMC, 50000000); 970 rk322x_mmc_set_clk(priv, SCLK_SDIO, 50000000); 971 rk_clrsetreg(&cru->cru_clksel_con[2], (0x1 << 14) | 972 (0x1f << 8), (1 << 14) | (0xb << 8)); 973 rk_clrsetreg(&cru->cru_clksel_con[23], (0x1f << 0) | (0x1f << 8), 974 (0x1f << 0) | (5 << 8)); 975 rk_clrsetreg(&cru->cru_clksel_con[33], 976 ACLK_VOP_PLL_SEL_MASK | ACLK_VOP_DIV_CON_MASK, 977 ACLK_VOP_PLL_SEL_GPLL << ACLK_VOP_PLL_SEL_SHIFT | 978 3 << ACLK_VOP_DIV_CON_SHIFT); 979 rk_clrsetreg(&cru->cru_clksel_con[22], 0x1f << 0, 5 << 0); 980 rk_clrsetreg(&cru->cru_clksel_con[24], 0x1f << 0, 0xb << 0); 981 rk_clrsetreg(&cru->cru_clksel_con[28], (0x1f << 8) | (0x1f << 0), 982 (5 << 8) | (5 << 0)); 983 rk_clrsetreg(&cru->cru_clksel_con[31], (0x1f << 8) | (0x1f << 0), 984 (5 << 8) | (5 << 0)); 985 rk_clrsetreg(&cru->cru_clksel_con[32], 0x1f << 0, 5 << 0); 986 rk_clrsetreg(&cru->cru_clksel_con[33], (0x1f << 8) | (0x1f << 0), 987 (5 << 8) | (5 << 0)); 988 rk_clrsetreg(&cru->cru_clksel_con[34], (0x1f << 8) | (0x1f << 0), 989 (5 << 8) | (3 << 0)); 990 991 rockchip_pll_set_rate(&rk322x_pll_clks[GPLL], 992 priv->cru, GPLL, GPLL_HZ); 993 priv->gpll_hz = GPLL_HZ; 994 995 rockchip_pll_set_rate(&rk322x_pll_clks[CPLL], 996 priv->cru, CPLL, CPLL_HZ); 997 priv->cpll_hz = CPLL_HZ; 998 999 rk322x_bus_set_clk(priv, ACLK_CPU, ACLK_BUS_HZ); 1000 rk322x_bus_set_clk(priv, HCLK_CPU, ACLK_BUS_HZ / 2); 1001 rk322x_bus_set_clk(priv, PCLK_CPU, ACLK_BUS_HZ / 2); 1002 rk322x_peri_set_clk(priv, ACLK_PERI, ACLK_PERI_HZ); 1003 rk322x_peri_set_clk(priv, HCLK_PERI, ACLK_PERI_HZ / 2); 1004 rk322x_peri_set_clk(priv, PCLK_PERI, ACLK_PERI_HZ / 2); 1005 /*rk322x_mmc_set_clk(priv, SCLK_EMMC, rate);*/ 1006 1007 /* set usbphy and hdmiphy from phy */ 1008 rk_clrsetreg(&cru->cru_misc_con, (0x1 << 13) | 1009 (0x1 << 15), (0 << 15) | (0 << 13)); 1010 } 1011 #endif 1012 1013 static int rk322x_clk_probe(struct udevice *dev) 1014 { 1015 #ifndef CONFIG_TPL_BUILD 1016 struct rk322x_clk_priv *priv = dev_get_priv(dev); 1017 int ret = 0; 1018 1019 priv->sync_kernel = false; 1020 if (!priv->armclk_enter_hz) 1021 priv->armclk_enter_hz = 1022 rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 1023 priv->cru, APLL); 1024 rkclk_init(priv); 1025 if (!priv->armclk_init_hz) 1026 priv->armclk_init_hz = 1027 rockchip_pll_get_rate(&rk322x_pll_clks[APLL], 1028 priv->cru, APLL); 1029 ret = clk_set_defaults(dev); 1030 if (ret) 1031 debug("%s clk_set_defaults failed %d\n", __func__, ret); 1032 else 1033 priv->sync_kernel = true; 1034 #endif 1035 return 0; 1036 } 1037 1038 static int rk322x_clk_bind(struct udevice *dev) 1039 { 1040 int ret; 1041 struct udevice *sys_child, *sf_child; 1042 struct sysreset_reg *priv; 1043 struct softreset_reg *sf_priv; 1044 1045 /* The reset driver does not have a device node, so bind it here */ 1046 ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset", 1047 &sys_child); 1048 if (ret) { 1049 debug("Warning: No sysreset driver: ret=%d\n", ret); 1050 } else { 1051 priv = malloc(sizeof(struct sysreset_reg)); 1052 priv->glb_srst_fst_value = offsetof(struct rk322x_cru, 1053 cru_glb_srst_fst_value); 1054 priv->glb_srst_snd_value = offsetof(struct rk322x_cru, 1055 cru_glb_srst_snd_value); 1056 sys_child->priv = priv; 1057 } 1058 1059 ret = device_bind_driver_to_node(dev, "rockchip_reset", "reset", 1060 dev_ofnode(dev), &sf_child); 1061 if (ret) { 1062 debug("Warning: No rockchip reset driver: ret=%d\n", ret); 1063 } else { 1064 sf_priv = malloc(sizeof(struct softreset_reg)); 1065 sf_priv->sf_reset_offset = offsetof(struct rk322x_cru, 1066 cru_softrst_con[0]); 1067 sf_priv->sf_reset_num = 9; 1068 sf_child->priv = sf_priv; 1069 } 1070 1071 return 0; 1072 } 1073 1074 static const struct udevice_id rk322x_clk_ids[] = { 1075 { .compatible = "rockchip,rk3228-cru" }, 1076 { } 1077 }; 1078 1079 U_BOOT_DRIVER(rockchip_rk322x_cru) = { 1080 .name = "clk_rk322x", 1081 .id = UCLASS_CLK, 1082 .of_match = rk322x_clk_ids, 1083 .priv_auto_alloc_size = sizeof(struct rk322x_clk_priv), 1084 .ofdata_to_platdata = rk322x_clk_ofdata_to_platdata, 1085 .ops = &rk322x_clk_ops, 1086 .bind = rk322x_clk_bind, 1087 .probe = rk322x_clk_probe, 1088 }; 1089 1090 #ifndef CONFIG_SPL_BUILD 1091 /** 1092 * soc_clk_dump() - Print clock frequencies 1093 * Returns zero on success 1094 * 1095 * Implementation for the clk dump command. 1096 */ 1097 int soc_clk_dump(void) 1098 { 1099 struct udevice *cru_dev; 1100 struct rk322x_clk_priv *priv; 1101 const struct rk322x_clk_info *clk_dump; 1102 struct clk clk; 1103 unsigned long clk_count = ARRAY_SIZE(clks_dump); 1104 unsigned long rate; 1105 int i, ret; 1106 1107 ret = uclass_get_device_by_driver(UCLASS_CLK, 1108 DM_GET_DRIVER(rockchip_rk322x_cru), 1109 &cru_dev); 1110 if (ret) { 1111 printf("%s failed to get cru device\n", __func__); 1112 return ret; 1113 } 1114 1115 priv = dev_get_priv(cru_dev); 1116 printf("CLK: (%s. arm: enter %lu KHz, init %lu KHz, kernel %lu%s)\n", 1117 priv->sync_kernel ? "sync kernel" : "uboot", 1118 priv->armclk_enter_hz / 1000, 1119 priv->armclk_init_hz / 1000, 1120 priv->set_armclk_rate ? priv->armclk_hz / 1000 : 0, 1121 priv->set_armclk_rate ? " KHz" : "N/A"); 1122 for (i = 0; i < clk_count; i++) { 1123 clk_dump = &clks_dump[i]; 1124 if (clk_dump->name) { 1125 clk.id = clk_dump->id; 1126 if (clk_dump->is_cru) 1127 ret = clk_request(cru_dev, &clk); 1128 if (ret < 0) 1129 return ret; 1130 1131 rate = clk_get_rate(&clk); 1132 clk_free(&clk); 1133 if (i == 0) { 1134 if (rate < 0) 1135 printf(" %s %s\n", clk_dump->name, 1136 "unknown"); 1137 else 1138 printf(" %s %lu KHz\n", clk_dump->name, 1139 rate / 1000); 1140 } else { 1141 if (rate < 0) 1142 printf(" %s %s\n", clk_dump->name, 1143 "unknown"); 1144 else 1145 printf(" %s %lu KHz\n", clk_dump->name, 1146 rate / 1000); 1147 } 1148 } 1149 } 1150 1151 return 0; 1152 } 1153 #endif 1154 1155