1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. 4 * Copyright (c) 2025, Luca Weiss <luca.weiss@fairphone.com> 5 */ 6 7 #include <linux/clk-provider.h> 8 #include <linux/module.h> 9 #include <linux/platform_device.h> 10 #include <linux/regmap.h> 11 12 #include <dt-bindings/clock/qcom,milos-gpucc.h> 13 14 #include "clk-alpha-pll.h" 15 #include "clk-branch.h" 16 #include "clk-pll.h" 17 #include "clk-rcg.h" 18 #include "clk-regmap.h" 19 #include "clk-regmap-divider.h" 20 #include "clk-regmap-mux.h" 21 #include "common.h" 22 #include "gdsc.h" 23 #include "reset.h" 24 25 /* Need to match the order of clocks in DT binding */ 26 enum { 27 DT_BI_TCXO, 28 DT_GPLL0_OUT_MAIN, 29 DT_GPLL0_OUT_MAIN_DIV, 30 }; 31 32 enum { 33 P_BI_TCXO, 34 P_GPLL0_OUT_MAIN, 35 P_GPLL0_OUT_MAIN_DIV, 36 P_GPU_CC_PLL0_OUT_EVEN, 37 P_GPU_CC_PLL0_OUT_MAIN, 38 P_GPU_CC_PLL0_OUT_ODD, 39 }; 40 41 static const struct pll_vco lucid_ole_vco[] = { 42 { 249600000, 2300000000, 0 }, 43 }; 44 45 /* 700.0 MHz Configuration */ 46 static const struct alpha_pll_config gpu_cc_pll0_config = { 47 .l = 0x24, 48 .alpha = 0x7555, 49 .config_ctl_val = 0x20485699, 50 .config_ctl_hi_val = 0x00182261, 51 .config_ctl_hi1_val = 0x82aa299c, 52 .test_ctl_val = 0x00000000, 53 .test_ctl_hi_val = 0x00000003, 54 .test_ctl_hi1_val = 0x00009000, 55 .test_ctl_hi2_val = 0x00000034, 56 .user_ctl_val = 0x00000400, 57 .user_ctl_hi_val = 0x00000005, 58 }; 59 60 static struct clk_alpha_pll gpu_cc_pll0 = { 61 .offset = 0x0, 62 .config = &gpu_cc_pll0_config, 63 .vco_table = lucid_ole_vco, 64 .num_vco = ARRAY_SIZE(lucid_ole_vco), 65 .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], 66 .clkr = { 67 .hw.init = &(const struct clk_init_data) { 68 .name = "gpu_cc_pll0", 69 .parent_data = &(const struct clk_parent_data) { 70 .index = DT_BI_TCXO, 71 }, 72 .num_parents = 1, 73 .ops = &clk_alpha_pll_lucid_evo_ops, 74 }, 75 }, 76 }; 77 78 static const struct clk_div_table post_div_table_gpu_cc_pll0_out_even[] = { 79 { 0x1, 2 }, 80 { } 81 }; 82 83 static struct clk_alpha_pll_postdiv gpu_cc_pll0_out_even = { 84 .offset = 0x0, 85 .post_div_shift = 10, 86 .post_div_table = post_div_table_gpu_cc_pll0_out_even, 87 .num_post_div = ARRAY_SIZE(post_div_table_gpu_cc_pll0_out_even), 88 .width = 4, 89 .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE], 90 .clkr.hw.init = &(const struct clk_init_data) { 91 .name = "gpu_cc_pll0_out_even", 92 .parent_hws = (const struct clk_hw*[]) { 93 &gpu_cc_pll0.clkr.hw, 94 }, 95 .num_parents = 1, 96 .flags = CLK_SET_RATE_PARENT, 97 .ops = &clk_alpha_pll_postdiv_lucid_ole_ops, 98 }, 99 }; 100 101 static const struct parent_map gpu_cc_parent_map_0[] = { 102 { P_BI_TCXO, 0 }, 103 { P_GPLL0_OUT_MAIN, 5 }, 104 { P_GPLL0_OUT_MAIN_DIV, 6 }, 105 }; 106 107 static const struct clk_parent_data gpu_cc_parent_data_0[] = { 108 { .index = DT_BI_TCXO }, 109 { .index = DT_GPLL0_OUT_MAIN }, 110 { .index = DT_GPLL0_OUT_MAIN_DIV }, 111 }; 112 113 static const struct parent_map gpu_cc_parent_map_1[] = { 114 { P_BI_TCXO, 0 }, 115 { P_GPU_CC_PLL0_OUT_MAIN, 1 }, 116 { P_GPU_CC_PLL0_OUT_EVEN, 2 }, 117 { P_GPU_CC_PLL0_OUT_ODD, 3 }, 118 { P_GPLL0_OUT_MAIN, 5 }, 119 { P_GPLL0_OUT_MAIN_DIV, 6 }, 120 }; 121 122 static const struct clk_parent_data gpu_cc_parent_data_1[] = { 123 { .index = DT_BI_TCXO }, 124 { .hw = &gpu_cc_pll0.clkr.hw }, 125 { .hw = &gpu_cc_pll0_out_even.clkr.hw }, 126 { .hw = &gpu_cc_pll0.clkr.hw }, 127 { .index = DT_GPLL0_OUT_MAIN }, 128 { .index = DT_GPLL0_OUT_MAIN_DIV }, 129 }; 130 131 static const struct freq_tbl ftbl_gpu_cc_ff_clk_src[] = { 132 F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), 133 { } 134 }; 135 136 static struct clk_rcg2 gpu_cc_ff_clk_src = { 137 .cmd_rcgr = 0x9474, 138 .mnd_width = 0, 139 .hid_width = 5, 140 .parent_map = gpu_cc_parent_map_0, 141 .freq_tbl = ftbl_gpu_cc_ff_clk_src, 142 .clkr.hw.init = &(const struct clk_init_data) { 143 .name = "gpu_cc_ff_clk_src", 144 .parent_data = gpu_cc_parent_data_0, 145 .num_parents = ARRAY_SIZE(gpu_cc_parent_data_0), 146 .flags = CLK_SET_RATE_PARENT, 147 .ops = &clk_rcg2_shared_ops, 148 }, 149 }; 150 151 static const struct freq_tbl ftbl_gpu_cc_gmu_clk_src[] = { 152 F(19200000, P_BI_TCXO, 1, 0, 0), 153 F(350000000, P_GPU_CC_PLL0_OUT_EVEN, 1, 0, 0), 154 F(650000000, P_GPU_CC_PLL0_OUT_EVEN, 1, 0, 0), 155 F(687500000, P_GPU_CC_PLL0_OUT_EVEN, 1, 0, 0), 156 { } 157 }; 158 159 static struct clk_rcg2 gpu_cc_gmu_clk_src = { 160 .cmd_rcgr = 0x9318, 161 .mnd_width = 0, 162 .hid_width = 5, 163 .parent_map = gpu_cc_parent_map_1, 164 .freq_tbl = ftbl_gpu_cc_gmu_clk_src, 165 .clkr.hw.init = &(const struct clk_init_data) { 166 .name = "gpu_cc_gmu_clk_src", 167 .parent_data = gpu_cc_parent_data_1, 168 .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1), 169 .flags = CLK_SET_RATE_PARENT, 170 .ops = &clk_rcg2_shared_ops, 171 }, 172 }; 173 174 static const struct freq_tbl ftbl_gpu_cc_hub_clk_src[] = { 175 F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0), 176 F(300000000, P_GPLL0_OUT_MAIN, 2, 0, 0), 177 F(400000000, P_GPLL0_OUT_MAIN, 1.5, 0, 0), 178 { } 179 }; 180 181 static struct clk_rcg2 gpu_cc_hub_clk_src = { 182 .cmd_rcgr = 0x93ec, 183 .mnd_width = 0, 184 .hid_width = 5, 185 .parent_map = gpu_cc_parent_map_1, 186 .freq_tbl = ftbl_gpu_cc_hub_clk_src, 187 .clkr.hw.init = &(const struct clk_init_data) { 188 .name = "gpu_cc_hub_clk_src", 189 .parent_data = gpu_cc_parent_data_1, 190 .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1), 191 .flags = CLK_SET_RATE_PARENT, 192 .ops = &clk_rcg2_shared_ops, 193 }, 194 }; 195 196 static struct clk_regmap_div gpu_cc_hub_div_clk_src = { 197 .reg = 0x942c, 198 .shift = 0, 199 .width = 4, 200 .clkr.hw.init = &(const struct clk_init_data) { 201 .name = "gpu_cc_hub_div_clk_src", 202 .parent_hws = (const struct clk_hw*[]) { 203 &gpu_cc_hub_clk_src.clkr.hw, 204 }, 205 .num_parents = 1, 206 .flags = CLK_SET_RATE_PARENT, 207 .ops = &clk_regmap_div_ro_ops, 208 }, 209 }; 210 211 static struct clk_branch gpu_cc_ahb_clk = { 212 .halt_reg = 0x90bc, 213 .halt_check = BRANCH_HALT_DELAY, 214 .clkr = { 215 .enable_reg = 0x90bc, 216 .enable_mask = BIT(0), 217 .hw.init = &(const struct clk_init_data) { 218 .name = "gpu_cc_ahb_clk", 219 .parent_hws = (const struct clk_hw*[]) { 220 &gpu_cc_hub_div_clk_src.clkr.hw, 221 }, 222 .num_parents = 1, 223 .flags = CLK_SET_RATE_PARENT, 224 .ops = &clk_branch2_ops, 225 }, 226 }, 227 }; 228 229 static struct clk_branch gpu_cc_cx_accu_shift_clk = { 230 .halt_reg = 0x910c, 231 .halt_check = BRANCH_HALT_VOTED, 232 .clkr = { 233 .enable_reg = 0x910c, 234 .enable_mask = BIT(0), 235 .hw.init = &(const struct clk_init_data) { 236 .name = "gpu_cc_cx_accu_shift_clk", 237 .ops = &clk_branch2_ops, 238 }, 239 }, 240 }; 241 242 static struct clk_branch gpu_cc_cx_ff_clk = { 243 .halt_reg = 0x90ec, 244 .halt_check = BRANCH_HALT, 245 .clkr = { 246 .enable_reg = 0x90ec, 247 .enable_mask = BIT(0), 248 .hw.init = &(const struct clk_init_data) { 249 .name = "gpu_cc_cx_ff_clk", 250 .parent_hws = (const struct clk_hw*[]) { 251 &gpu_cc_ff_clk_src.clkr.hw, 252 }, 253 .num_parents = 1, 254 .flags = CLK_SET_RATE_PARENT, 255 .ops = &clk_branch2_ops, 256 }, 257 }, 258 }; 259 260 static struct clk_branch gpu_cc_cx_gmu_clk = { 261 .halt_reg = 0x90d4, 262 .halt_check = BRANCH_HALT_VOTED, 263 .clkr = { 264 .enable_reg = 0x90d4, 265 .enable_mask = BIT(0), 266 .hw.init = &(const struct clk_init_data) { 267 .name = "gpu_cc_cx_gmu_clk", 268 .parent_hws = (const struct clk_hw*[]) { 269 &gpu_cc_gmu_clk_src.clkr.hw, 270 }, 271 .num_parents = 1, 272 .flags = CLK_SET_RATE_PARENT, 273 .ops = &clk_branch2_aon_ops, 274 }, 275 }, 276 }; 277 278 static struct clk_branch gpu_cc_cxo_clk = { 279 .halt_reg = 0x90e4, 280 .halt_check = BRANCH_HALT, 281 .clkr = { 282 .enable_reg = 0x90e4, 283 .enable_mask = BIT(0), 284 .hw.init = &(const struct clk_init_data) { 285 .name = "gpu_cc_cxo_clk", 286 .ops = &clk_branch2_ops, 287 }, 288 }, 289 }; 290 291 static struct clk_branch gpu_cc_dpm_clk = { 292 .halt_reg = 0x9110, 293 .halt_check = BRANCH_HALT, 294 .clkr = { 295 .enable_reg = 0x9110, 296 .enable_mask = BIT(0), 297 .hw.init = &(const struct clk_init_data) { 298 .name = "gpu_cc_dpm_clk", 299 .ops = &clk_branch2_ops, 300 }, 301 }, 302 }; 303 304 static struct clk_branch gpu_cc_freq_measure_clk = { 305 .halt_reg = 0x900c, 306 .halt_check = BRANCH_HALT, 307 .clkr = { 308 .enable_reg = 0x900c, 309 .enable_mask = BIT(0), 310 .hw.init = &(const struct clk_init_data) { 311 .name = "gpu_cc_freq_measure_clk", 312 .ops = &clk_branch2_ops, 313 }, 314 }, 315 }; 316 317 static struct clk_branch gpu_cc_gx_accu_shift_clk = { 318 .halt_reg = 0x9070, 319 .halt_check = BRANCH_HALT_VOTED, 320 .clkr = { 321 .enable_reg = 0x9070, 322 .enable_mask = BIT(0), 323 .hw.init = &(const struct clk_init_data) { 324 .name = "gpu_cc_gx_accu_shift_clk", 325 .ops = &clk_branch2_ops, 326 }, 327 }, 328 }; 329 330 static struct clk_branch gpu_cc_gx_acd_ahb_ff_clk = { 331 .halt_reg = 0x9068, 332 .halt_check = BRANCH_HALT, 333 .clkr = { 334 .enable_reg = 0x9068, 335 .enable_mask = BIT(0), 336 .hw.init = &(const struct clk_init_data) { 337 .name = "gpu_cc_gx_acd_ahb_ff_clk", 338 .parent_hws = (const struct clk_hw*[]) { 339 &gpu_cc_ff_clk_src.clkr.hw, 340 }, 341 .num_parents = 1, 342 .flags = CLK_SET_RATE_PARENT, 343 .ops = &clk_branch2_ops, 344 }, 345 }, 346 }; 347 348 static struct clk_branch gpu_cc_gx_gmu_clk = { 349 .halt_reg = 0x9060, 350 .halt_check = BRANCH_HALT, 351 .clkr = { 352 .enable_reg = 0x9060, 353 .enable_mask = BIT(0), 354 .hw.init = &(const struct clk_init_data) { 355 .name = "gpu_cc_gx_gmu_clk", 356 .parent_hws = (const struct clk_hw*[]) { 357 &gpu_cc_gmu_clk_src.clkr.hw, 358 }, 359 .num_parents = 1, 360 .flags = CLK_SET_RATE_PARENT, 361 .ops = &clk_branch2_ops, 362 }, 363 }, 364 }; 365 366 static struct clk_branch gpu_cc_gx_rcg_ahb_ff_clk = { 367 .halt_reg = 0x906c, 368 .halt_check = BRANCH_HALT_VOTED, 369 .clkr = { 370 .enable_reg = 0x906c, 371 .enable_mask = BIT(0), 372 .hw.init = &(const struct clk_init_data) { 373 .name = "gpu_cc_gx_rcg_ahb_ff_clk", 374 .parent_hws = (const struct clk_hw*[]) { 375 &gpu_cc_ff_clk_src.clkr.hw, 376 }, 377 .num_parents = 1, 378 .flags = CLK_SET_RATE_PARENT, 379 .ops = &clk_branch2_ops, 380 }, 381 }, 382 }; 383 384 static struct clk_branch gpu_cc_hlos1_vote_gpu_smmu_clk = { 385 .halt_reg = 0x7000, 386 .halt_check = BRANCH_HALT_VOTED, 387 .clkr = { 388 .enable_reg = 0x7000, 389 .enable_mask = BIT(0), 390 .hw.init = &(const struct clk_init_data) { 391 .name = "gpu_cc_hlos1_vote_gpu_smmu_clk", 392 .ops = &clk_branch2_ops, 393 }, 394 }, 395 }; 396 397 static struct clk_branch gpu_cc_hub_aon_clk = { 398 .halt_reg = 0x93e8, 399 .halt_check = BRANCH_HALT_VOTED, 400 .clkr = { 401 .enable_reg = 0x93e8, 402 .enable_mask = BIT(0), 403 .hw.init = &(const struct clk_init_data) { 404 .name = "gpu_cc_hub_aon_clk", 405 .parent_hws = (const struct clk_hw*[]) { 406 &gpu_cc_hub_clk_src.clkr.hw, 407 }, 408 .num_parents = 1, 409 .flags = CLK_SET_RATE_PARENT, 410 .ops = &clk_branch2_aon_ops, 411 }, 412 }, 413 }; 414 415 static struct clk_branch gpu_cc_hub_cx_int_clk = { 416 .halt_reg = 0x90e8, 417 .halt_check = BRANCH_HALT_VOTED, 418 .clkr = { 419 .enable_reg = 0x90e8, 420 .enable_mask = BIT(0), 421 .hw.init = &(const struct clk_init_data) { 422 .name = "gpu_cc_hub_cx_int_clk", 423 .parent_hws = (const struct clk_hw*[]) { 424 &gpu_cc_hub_clk_src.clkr.hw, 425 }, 426 .num_parents = 1, 427 .flags = CLK_SET_RATE_PARENT, 428 .ops = &clk_branch2_aon_ops, 429 }, 430 }, 431 }; 432 433 static struct clk_branch gpu_cc_memnoc_gfx_clk = { 434 .halt_reg = 0x90f4, 435 .halt_check = BRANCH_HALT_VOTED, 436 .clkr = { 437 .enable_reg = 0x90f4, 438 .enable_mask = BIT(0), 439 .hw.init = &(const struct clk_init_data) { 440 .name = "gpu_cc_memnoc_gfx_clk", 441 .ops = &clk_branch2_ops, 442 }, 443 }, 444 }; 445 446 static struct gdsc gpu_cc_cx_gdsc = { 447 .gdscr = 0x9080, 448 .gds_hw_ctrl = 0x9094, 449 .en_rest_wait_val = 0x2, 450 .en_few_wait_val = 0x2, 451 .clk_dis_wait_val = 0x8, 452 .pd = { 453 .name = "gpu_cc_cx_gdsc", 454 }, 455 .pwrsts = PWRSTS_OFF_ON, 456 .flags = RETAIN_FF_ENABLE | VOTABLE, 457 }; 458 459 static struct clk_regmap *gpu_cc_milos_clocks[] = { 460 [GPU_CC_AHB_CLK] = &gpu_cc_ahb_clk.clkr, 461 [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_cc_cx_accu_shift_clk.clkr, 462 [GPU_CC_CX_FF_CLK] = &gpu_cc_cx_ff_clk.clkr, 463 [GPU_CC_CX_GMU_CLK] = &gpu_cc_cx_gmu_clk.clkr, 464 [GPU_CC_CXO_CLK] = &gpu_cc_cxo_clk.clkr, 465 [GPU_CC_DPM_CLK] = &gpu_cc_dpm_clk.clkr, 466 [GPU_CC_FF_CLK_SRC] = &gpu_cc_ff_clk_src.clkr, 467 [GPU_CC_FREQ_MEASURE_CLK] = &gpu_cc_freq_measure_clk.clkr, 468 [GPU_CC_GMU_CLK_SRC] = &gpu_cc_gmu_clk_src.clkr, 469 [GPU_CC_GX_ACCU_SHIFT_CLK] = &gpu_cc_gx_accu_shift_clk.clkr, 470 [GPU_CC_GX_ACD_AHB_FF_CLK] = &gpu_cc_gx_acd_ahb_ff_clk.clkr, 471 [GPU_CC_GX_GMU_CLK] = &gpu_cc_gx_gmu_clk.clkr, 472 [GPU_CC_GX_RCG_AHB_FF_CLK] = &gpu_cc_gx_rcg_ahb_ff_clk.clkr, 473 [GPU_CC_HLOS1_VOTE_GPU_SMMU_CLK] = &gpu_cc_hlos1_vote_gpu_smmu_clk.clkr, 474 [GPU_CC_HUB_AON_CLK] = &gpu_cc_hub_aon_clk.clkr, 475 [GPU_CC_HUB_CLK_SRC] = &gpu_cc_hub_clk_src.clkr, 476 [GPU_CC_HUB_CX_INT_CLK] = &gpu_cc_hub_cx_int_clk.clkr, 477 [GPU_CC_HUB_DIV_CLK_SRC] = &gpu_cc_hub_div_clk_src.clkr, 478 [GPU_CC_MEMNOC_GFX_CLK] = &gpu_cc_memnoc_gfx_clk.clkr, 479 [GPU_CC_PLL0] = &gpu_cc_pll0.clkr, 480 [GPU_CC_PLL0_OUT_EVEN] = &gpu_cc_pll0_out_even.clkr, 481 }; 482 483 static struct gdsc *gpu_cc_milos_gdscs[] = { 484 [GPU_CC_CX_GDSC] = &gpu_cc_cx_gdsc, 485 }; 486 487 static const struct qcom_reset_map gpu_cc_milos_resets[] = { 488 [GPU_CC_CB_BCR] = { 0x93a0 }, 489 [GPU_CC_CX_BCR] = { 0x907c }, 490 [GPU_CC_FAST_HUB_BCR] = { 0x93e4 }, 491 [GPU_CC_FF_BCR] = { 0x9470 }, 492 [GPU_CC_GMU_BCR] = { 0x9314 }, 493 [GPU_CC_GX_BCR] = { 0x905c }, 494 [GPU_CC_RBCPR_BCR] = { 0x91e0 }, 495 [GPU_CC_XO_BCR] = { 0x9000 }, 496 }; 497 498 static struct clk_alpha_pll *gpu_cc_milos_plls[] = { 499 &gpu_cc_pll0, 500 }; 501 502 static const u32 gpu_cc_milos_critical_cbcrs[] = { 503 0x93a4, /* GPU_CC_CB_CLK */ 504 0x9008, /* GPU_CC_CXO_AON_CLK */ 505 0x9010, /* GPU_CC_DEMET_CLK */ 506 0x9064, /* GPU_CC_GX_AHB_FF_CLK */ 507 0x93a8, /* GPU_CC_RSCC_HUB_AON_CLK */ 508 0x9004, /* GPU_CC_RSCC_XO_AON_CLK */ 509 0x90cc, /* GPU_CC_SLEEP_CLK */ 510 }; 511 512 static const struct regmap_config gpu_cc_milos_regmap_config = { 513 .reg_bits = 32, 514 .reg_stride = 4, 515 .val_bits = 32, 516 .max_register = 0x95e8, 517 .fast_io = true, 518 }; 519 520 static const struct qcom_cc_driver_data gpu_cc_milos_driver_data = { 521 .alpha_plls = gpu_cc_milos_plls, 522 .num_alpha_plls = ARRAY_SIZE(gpu_cc_milos_plls), 523 .clk_cbcrs = gpu_cc_milos_critical_cbcrs, 524 .num_clk_cbcrs = ARRAY_SIZE(gpu_cc_milos_critical_cbcrs), 525 }; 526 527 static const struct qcom_cc_desc gpu_cc_milos_desc = { 528 .config = &gpu_cc_milos_regmap_config, 529 .clks = gpu_cc_milos_clocks, 530 .num_clks = ARRAY_SIZE(gpu_cc_milos_clocks), 531 .resets = gpu_cc_milos_resets, 532 .num_resets = ARRAY_SIZE(gpu_cc_milos_resets), 533 .gdscs = gpu_cc_milos_gdscs, 534 .num_gdscs = ARRAY_SIZE(gpu_cc_milos_gdscs), 535 .use_rpm = true, 536 .driver_data = &gpu_cc_milos_driver_data, 537 }; 538 539 static const struct of_device_id gpu_cc_milos_match_table[] = { 540 { .compatible = "qcom,milos-gpucc" }, 541 { } 542 }; 543 MODULE_DEVICE_TABLE(of, gpu_cc_milos_match_table); 544 545 static int gpu_cc_milos_probe(struct platform_device *pdev) 546 { 547 return qcom_cc_probe(pdev, &gpu_cc_milos_desc); 548 } 549 550 static struct platform_driver gpu_cc_milos_driver = { 551 .probe = gpu_cc_milos_probe, 552 .driver = { 553 .name = "gpu_cc-milos", 554 .of_match_table = gpu_cc_milos_match_table, 555 }, 556 }; 557 558 module_platform_driver(gpu_cc_milos_driver); 559 560 MODULE_DESCRIPTION("QTI GPU_CC Milos Driver"); 561 MODULE_LICENSE("GPL"); 562