1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Copyright (c) 2016 Allwinnertech Co., Ltd. 4 * Copyright (C) 2017-2018 Bootlin 5 * 6 * Maxime Ripard <maxime.ripard@free-electrons.com> 7 */ 8 9 #include <linux/bitops.h> 10 #include <linux/clk.h> 11 #include <linux/module.h> 12 #include <linux/of_address.h> 13 #include <linux/platform_device.h> 14 #include <linux/regmap.h> 15 #include <linux/reset.h> 16 17 #include <linux/phy/phy.h> 18 #include <linux/phy/phy-mipi-dphy.h> 19 20 #define SUN6I_DPHY_GCTL_REG 0x00 21 #define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) 22 #define SUN6I_DPHY_GCTL_EN BIT(0) 23 24 #define SUN6I_DPHY_TX_CTL_REG 0x04 25 #define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) 26 27 #define SUN6I_DPHY_RX_CTL_REG 0x08 28 #define SUN6I_DPHY_RX_CTL_EN_DBC BIT(31) 29 #define SUN6I_DPHY_RX_CTL_RX_CLK_FORCE BIT(24) 30 #define SUN6I_DPHY_RX_CTL_RX_D3_FORCE BIT(23) 31 #define SUN6I_DPHY_RX_CTL_RX_D2_FORCE BIT(22) 32 #define SUN6I_DPHY_RX_CTL_RX_D1_FORCE BIT(21) 33 #define SUN6I_DPHY_RX_CTL_RX_D0_FORCE BIT(20) 34 35 #define SUN6I_DPHY_TX_TIME0_REG 0x10 36 #define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) 37 #define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) 38 #define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) 39 40 #define SUN6I_DPHY_TX_TIME1_REG 0x14 41 #define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) 42 #define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) 43 #define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) 44 #define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) 45 46 #define SUN6I_DPHY_TX_TIME2_REG 0x18 47 #define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) 48 49 #define SUN6I_DPHY_TX_TIME3_REG 0x1c 50 51 #define SUN6I_DPHY_TX_TIME4_REG 0x20 52 #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) 53 #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) 54 55 #define SUN6I_DPHY_RX_TIME0_REG 0x30 56 #define SUN6I_DPHY_RX_TIME0_HS_RX_SYNC(n) (((n) & 0xff) << 24) 57 #define SUN6I_DPHY_RX_TIME0_HS_RX_CLK_MISS(n) (((n) & 0xff) << 16) 58 #define SUN6I_DPHY_RX_TIME0_LP_RX(n) (((n) & 0xff) << 8) 59 60 #define SUN6I_DPHY_RX_TIME1_REG 0x34 61 #define SUN6I_DPHY_RX_TIME1_RX_DLY(n) (((n) & 0xfff) << 20) 62 #define SUN6I_DPHY_RX_TIME1_LP_RX_ULPS_WP(n) ((n) & 0xfffff) 63 64 #define SUN6I_DPHY_RX_TIME2_REG 0x38 65 #define SUN6I_DPHY_RX_TIME2_HS_RX_ANA1(n) (((n) & 0xff) << 8) 66 #define SUN6I_DPHY_RX_TIME2_HS_RX_ANA0(n) ((n) & 0xff) 67 68 #define SUN6I_DPHY_RX_TIME3_REG 0x40 69 #define SUN6I_DPHY_RX_TIME3_LPRST_DLY(n) (((n) & 0xffff) << 16) 70 71 #define SUN6I_DPHY_ANA0_REG 0x4c 72 #define SUN6I_DPHY_ANA0_REG_PWS BIT(31) 73 #define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) 74 #define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) 75 #define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) 76 #define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) 77 #define SUN6I_DPHY_ANA0_REG_SFB(n) (((n) & 3) << 2) 78 79 #define SUN6I_DPHY_ANA1_REG 0x50 80 #define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) 81 #define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) 82 #define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) 83 84 #define SUN6I_DPHY_ANA2_REG 0x54 85 #define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) 86 #define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) 87 #define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) 88 #define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) 89 90 #define SUN6I_DPHY_ANA3_REG 0x58 91 #define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) 92 #define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) 93 #define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) 94 #define SUN6I_DPHY_ANA3_EN_DIV BIT(26) 95 #define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) 96 #define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) 97 #define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) 98 99 #define SUN6I_DPHY_ANA4_REG 0x5c 100 #define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) 101 #define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) 102 #define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) 103 #define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) 104 #define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) 105 #define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) 106 #define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) 107 #define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) 108 #define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) 109 110 #define SUN6I_DPHY_DBG5_REG 0xf4 111 112 enum sun6i_dphy_direction { 113 SUN6I_DPHY_DIRECTION_TX, 114 SUN6I_DPHY_DIRECTION_RX, 115 }; 116 117 struct sun6i_dphy { 118 struct clk *bus_clk; 119 struct clk *mod_clk; 120 struct regmap *regs; 121 struct reset_control *reset; 122 123 struct phy *phy; 124 struct phy_configure_opts_mipi_dphy config; 125 126 enum sun6i_dphy_direction direction; 127 }; 128 129 static int sun6i_dphy_init(struct phy *phy) 130 { 131 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 132 133 reset_control_deassert(dphy->reset); 134 clk_prepare_enable(dphy->mod_clk); 135 clk_set_rate_exclusive(dphy->mod_clk, 150000000); 136 137 return 0; 138 } 139 140 static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) 141 { 142 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 143 int ret; 144 145 ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); 146 if (ret) 147 return ret; 148 149 memcpy(&dphy->config, opts, sizeof(dphy->config)); 150 151 return 0; 152 } 153 154 static int sun6i_dphy_tx_power_on(struct sun6i_dphy *dphy) 155 { 156 u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); 157 158 regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, 159 SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); 160 161 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, 162 SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | 163 SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | 164 SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); 165 166 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, 167 SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | 168 SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | 169 SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | 170 SUN6I_DPHY_TX_TIME1_CLK_POST(10)); 171 172 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, 173 SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); 174 175 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); 176 177 regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, 178 SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | 179 SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); 180 181 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 182 SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | 183 SUN6I_DPHY_GCTL_EN); 184 185 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 186 SUN6I_DPHY_ANA0_REG_PWS | 187 SUN6I_DPHY_ANA0_REG_DMPC | 188 SUN6I_DPHY_ANA0_REG_SLV(7) | 189 SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | 190 SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); 191 192 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 193 SUN6I_DPHY_ANA1_REG_CSMPS(1) | 194 SUN6I_DPHY_ANA1_REG_SVTT(7)); 195 196 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 197 SUN6I_DPHY_ANA4_REG_CKDV(1) | 198 SUN6I_DPHY_ANA4_REG_TMSC(1) | 199 SUN6I_DPHY_ANA4_REG_TMSD(1) | 200 SUN6I_DPHY_ANA4_REG_TXDNSC(1) | 201 SUN6I_DPHY_ANA4_REG_TXDNSD(1) | 202 SUN6I_DPHY_ANA4_REG_TXPUSC(1) | 203 SUN6I_DPHY_ANA4_REG_TXPUSD(1) | 204 SUN6I_DPHY_ANA4_REG_DMPLVC | 205 SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); 206 207 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 208 SUN6I_DPHY_ANA2_REG_ENIB); 209 udelay(5); 210 211 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 212 SUN6I_DPHY_ANA3_EN_LDOR | 213 SUN6I_DPHY_ANA3_EN_LDOC | 214 SUN6I_DPHY_ANA3_EN_LDOD); 215 udelay(1); 216 217 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, 218 SUN6I_DPHY_ANA3_EN_VTTC | 219 SUN6I_DPHY_ANA3_EN_VTTD_MASK, 220 SUN6I_DPHY_ANA3_EN_VTTC | 221 SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); 222 udelay(1); 223 224 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, 225 SUN6I_DPHY_ANA3_EN_DIV, 226 SUN6I_DPHY_ANA3_EN_DIV); 227 udelay(1); 228 229 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, 230 SUN6I_DPHY_ANA2_EN_CK_CPU, 231 SUN6I_DPHY_ANA2_EN_CK_CPU); 232 udelay(1); 233 234 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, 235 SUN6I_DPHY_ANA1_REG_VTTMODE, 236 SUN6I_DPHY_ANA1_REG_VTTMODE); 237 238 regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, 239 SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, 240 SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); 241 242 return 0; 243 } 244 245 static int sun6i_dphy_rx_power_on(struct sun6i_dphy *dphy) 246 { 247 /* Physical clock rate is actually half of symbol rate with DDR. */ 248 unsigned long mipi_symbol_rate = dphy->config.hs_clk_rate; 249 unsigned long dphy_clk_rate; 250 unsigned int rx_dly; 251 unsigned int lprst_dly; 252 u32 value; 253 254 dphy_clk_rate = clk_get_rate(dphy->mod_clk); 255 if (!dphy_clk_rate) 256 return -EINVAL; 257 258 /* Hardcoded timing parameters from the Allwinner BSP. */ 259 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME0_REG, 260 SUN6I_DPHY_RX_TIME0_HS_RX_SYNC(255) | 261 SUN6I_DPHY_RX_TIME0_HS_RX_CLK_MISS(255) | 262 SUN6I_DPHY_RX_TIME0_LP_RX(255)); 263 264 /* 265 * Formula from the Allwinner BSP, with hardcoded coefficients 266 * (probably internal divider/multiplier). 267 */ 268 rx_dly = 8 * (unsigned int)(dphy_clk_rate / (mipi_symbol_rate / 8)); 269 270 /* 271 * The Allwinner BSP has an alternative formula for LP_RX_ULPS_WP: 272 * lp_ulps_wp_cnt = lp_ulps_wp_ms * lp_clk / 1000 273 * but does not use it and hardcodes 255 instead. 274 */ 275 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME1_REG, 276 SUN6I_DPHY_RX_TIME1_RX_DLY(rx_dly) | 277 SUN6I_DPHY_RX_TIME1_LP_RX_ULPS_WP(255)); 278 279 /* HS_RX_ANA0 value is hardcoded in the Allwinner BSP. */ 280 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME2_REG, 281 SUN6I_DPHY_RX_TIME2_HS_RX_ANA0(4)); 282 283 /* 284 * Formula from the Allwinner BSP, with hardcoded coefficients 285 * (probably internal divider/multiplier). 286 */ 287 lprst_dly = 4 * (unsigned int)(dphy_clk_rate / (mipi_symbol_rate / 2)); 288 289 regmap_write(dphy->regs, SUN6I_DPHY_RX_TIME3_REG, 290 SUN6I_DPHY_RX_TIME3_LPRST_DLY(lprst_dly)); 291 292 /* Analog parameters are hardcoded in the Allwinner BSP. */ 293 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 294 SUN6I_DPHY_ANA0_REG_PWS | 295 SUN6I_DPHY_ANA0_REG_SLV(7) | 296 SUN6I_DPHY_ANA0_REG_SFB(2)); 297 298 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 299 SUN6I_DPHY_ANA1_REG_SVTT(4)); 300 301 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 302 SUN6I_DPHY_ANA4_REG_DMPLVC | 303 SUN6I_DPHY_ANA4_REG_DMPLVD(1)); 304 305 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 306 SUN6I_DPHY_ANA2_REG_ENIB); 307 308 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 309 SUN6I_DPHY_ANA3_EN_LDOR | 310 SUN6I_DPHY_ANA3_EN_LDOC | 311 SUN6I_DPHY_ANA3_EN_LDOD); 312 313 /* 314 * Delay comes from the Allwinner BSP, likely for internal regulator 315 * ramp-up. 316 */ 317 udelay(3); 318 319 value = SUN6I_DPHY_RX_CTL_EN_DBC | SUN6I_DPHY_RX_CTL_RX_CLK_FORCE; 320 321 /* 322 * Rx data lane force-enable bits are used as regular RX enable by the 323 * Allwinner BSP. 324 */ 325 if (dphy->config.lanes >= 1) 326 value |= SUN6I_DPHY_RX_CTL_RX_D0_FORCE; 327 if (dphy->config.lanes >= 2) 328 value |= SUN6I_DPHY_RX_CTL_RX_D1_FORCE; 329 if (dphy->config.lanes >= 3) 330 value |= SUN6I_DPHY_RX_CTL_RX_D2_FORCE; 331 if (dphy->config.lanes == 4) 332 value |= SUN6I_DPHY_RX_CTL_RX_D3_FORCE; 333 334 regmap_write(dphy->regs, SUN6I_DPHY_RX_CTL_REG, value); 335 336 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 337 SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | 338 SUN6I_DPHY_GCTL_EN); 339 340 return 0; 341 } 342 343 static int sun6i_dphy_power_on(struct phy *phy) 344 { 345 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 346 347 switch (dphy->direction) { 348 case SUN6I_DPHY_DIRECTION_TX: 349 return sun6i_dphy_tx_power_on(dphy); 350 case SUN6I_DPHY_DIRECTION_RX: 351 return sun6i_dphy_rx_power_on(dphy); 352 default: 353 return -EINVAL; 354 } 355 } 356 357 static int sun6i_dphy_power_off(struct phy *phy) 358 { 359 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 360 361 regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, 0); 362 363 regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, 0); 364 regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, 0); 365 regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, 0); 366 regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, 0); 367 regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, 0); 368 369 return 0; 370 } 371 372 static int sun6i_dphy_exit(struct phy *phy) 373 { 374 struct sun6i_dphy *dphy = phy_get_drvdata(phy); 375 376 clk_rate_exclusive_put(dphy->mod_clk); 377 clk_disable_unprepare(dphy->mod_clk); 378 reset_control_assert(dphy->reset); 379 380 return 0; 381 } 382 383 384 static const struct phy_ops sun6i_dphy_ops = { 385 .configure = sun6i_dphy_configure, 386 .power_on = sun6i_dphy_power_on, 387 .power_off = sun6i_dphy_power_off, 388 .init = sun6i_dphy_init, 389 .exit = sun6i_dphy_exit, 390 }; 391 392 static const struct regmap_config sun6i_dphy_regmap_config = { 393 .reg_bits = 32, 394 .val_bits = 32, 395 .reg_stride = 4, 396 .max_register = SUN6I_DPHY_DBG5_REG, 397 .name = "mipi-dphy", 398 }; 399 400 static int sun6i_dphy_probe(struct platform_device *pdev) 401 { 402 struct phy_provider *phy_provider; 403 struct sun6i_dphy *dphy; 404 const char *direction; 405 void __iomem *regs; 406 int ret; 407 408 dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); 409 if (!dphy) 410 return -ENOMEM; 411 412 regs = devm_platform_ioremap_resource(pdev, 0); 413 if (IS_ERR(regs)) { 414 dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); 415 return PTR_ERR(regs); 416 } 417 418 dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", 419 regs, &sun6i_dphy_regmap_config); 420 if (IS_ERR(dphy->regs)) { 421 dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); 422 return PTR_ERR(dphy->regs); 423 } 424 425 dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); 426 if (IS_ERR(dphy->reset)) { 427 dev_err(&pdev->dev, "Couldn't get our reset line\n"); 428 return PTR_ERR(dphy->reset); 429 } 430 431 dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); 432 if (IS_ERR(dphy->mod_clk)) { 433 dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); 434 return PTR_ERR(dphy->mod_clk); 435 } 436 437 dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); 438 if (IS_ERR(dphy->phy)) { 439 dev_err(&pdev->dev, "failed to create PHY\n"); 440 return PTR_ERR(dphy->phy); 441 } 442 443 dphy->direction = SUN6I_DPHY_DIRECTION_TX; 444 445 ret = of_property_read_string(pdev->dev.of_node, "allwinner,direction", 446 &direction); 447 448 if (!ret && !strncmp(direction, "rx", 2)) 449 dphy->direction = SUN6I_DPHY_DIRECTION_RX; 450 451 phy_set_drvdata(dphy->phy, dphy); 452 phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); 453 454 return PTR_ERR_OR_ZERO(phy_provider); 455 } 456 457 static const struct of_device_id sun6i_dphy_of_table[] = { 458 { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, 459 { } 460 }; 461 MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); 462 463 static struct platform_driver sun6i_dphy_platform_driver = { 464 .probe = sun6i_dphy_probe, 465 .driver = { 466 .name = "sun6i-mipi-dphy", 467 .of_match_table = sun6i_dphy_of_table, 468 }, 469 }; 470 module_platform_driver(sun6i_dphy_platform_driver); 471 472 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin>"); 473 MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); 474 MODULE_LICENSE("GPL"); 475