1/* 2 * Freescale eSDHC controller driver. 3 * 4 * Copyright (c) 2007, 2010, 2012 Freescale Semiconductor, Inc. 5 * Copyright (c) 2009 MontaVista Software, Inc. 6 * 7 * Authors: Xiaobo Xie <X.Xie@freescale.com> 8 * Anton Vorontsov <avorontsov@ru.mvista.com> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or (at 13 * your option) any later version. 14 */ 15 16#include <linux/err.h> 17#include <linux/io.h> 18#include <linux/of.h> 19#include <linux/delay.h> 20#include <linux/module.h> 21#include <linux/mmc/host.h> 22#include "sdhci-pltfm.h" 23#include "sdhci-esdhc.h" 24 25#define VENDOR_V_22 0x12 26#define VENDOR_V_23 0x13 27 28struct sdhci_esdhc { 29 u8 vendor_ver; 30 u8 spec_ver; 31}; 32 33/** 34 * esdhc_read*_fixup - Fixup the value read from incompatible eSDHC register 35 * to make it compatible with SD spec. 36 * 37 * @host: pointer to sdhci_host 38 * @spec_reg: SD spec register address 39 * @value: 32bit eSDHC register value on spec_reg address 40 * 41 * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC 42 * registers are 32 bits. There are differences in register size, register 43 * address, register function, bit position and function between eSDHC spec 44 * and SD spec. 45 * 46 * Return a fixed up register value 47 */ 48static u32 esdhc_readl_fixup(struct sdhci_host *host, 49 int spec_reg, u32 value) 50{ 51 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 52 struct sdhci_esdhc *esdhc = pltfm_host->priv; 53 u32 ret; 54 55 /* 56 * The bit of ADMA flag in eSDHC is not compatible with standard 57 * SDHC register, so set fake flag SDHCI_CAN_DO_ADMA2 when ADMA is 58 * supported by eSDHC. 59 * And for many FSL eSDHC controller, the reset value of field 60 * SDHCI_CAN_DO_ADMA1 is 1, but some of them can't support ADMA, 61 * only these vendor version is greater than 2.2/0x12 support ADMA. 62 */ 63 if ((spec_reg == SDHCI_CAPABILITIES) && (value & SDHCI_CAN_DO_ADMA1)) { 64 if (esdhc->vendor_ver > VENDOR_V_22) { 65 ret = value | SDHCI_CAN_DO_ADMA2; 66 return ret; 67 } 68 } 69 ret = value; 70 return ret; 71} 72 73static u16 esdhc_readw_fixup(struct sdhci_host *host, 74 int spec_reg, u32 value) 75{ 76 u16 ret; 77 int shift = (spec_reg & 0x2) * 8; 78 79 if (spec_reg == SDHCI_HOST_VERSION) 80 ret = value & 0xffff; 81 else 82 ret = (value >> shift) & 0xffff; 83 return ret; 84} 85 86static u8 esdhc_readb_fixup(struct sdhci_host *host, 87 int spec_reg, u32 value) 88{ 89 u8 ret; 90 u8 dma_bits; 91 int shift = (spec_reg & 0x3) * 8; 92 93 ret = (value >> shift) & 0xff; 94 95 /* 96 * "DMA select" locates at offset 0x28 in SD specification, but on 97 * P5020 or P3041, it locates at 0x29. 98 */ 99 if (spec_reg == SDHCI_HOST_CONTROL) { 100 /* DMA select is 22,23 bits in Protocol Control Register */ 101 dma_bits = (value >> 5) & SDHCI_CTRL_DMA_MASK; 102 /* fixup the result */ 103 ret &= ~SDHCI_CTRL_DMA_MASK; 104 ret |= dma_bits; 105 } 106 return ret; 107} 108 109/** 110 * esdhc_write*_fixup - Fixup the SD spec register value so that it could be 111 * written into eSDHC register. 112 * 113 * @host: pointer to sdhci_host 114 * @spec_reg: SD spec register address 115 * @value: 8/16/32bit SD spec register value that would be written 116 * @old_value: 32bit eSDHC register value on spec_reg address 117 * 118 * In SD spec, there are 8/16/32/64 bits registers, while all of eSDHC 119 * registers are 32 bits. There are differences in register size, register 120 * address, register function, bit position and function between eSDHC spec 121 * and SD spec. 122 * 123 * Return a fixed up register value 124 */ 125static u32 esdhc_writel_fixup(struct sdhci_host *host, 126 int spec_reg, u32 value, u32 old_value) 127{ 128 u32 ret; 129 130 /* 131 * Enabling IRQSTATEN[BGESEN] is just to set IRQSTAT[BGE] 132 * when SYSCTL[RSTD] is set for some special operations. 133 * No any impact on other operation. 134 */ 135 if (spec_reg == SDHCI_INT_ENABLE) 136 ret = value | SDHCI_INT_BLK_GAP; 137 else 138 ret = value; 139 140 return ret; 141} 142 143static u32 esdhc_writew_fixup(struct sdhci_host *host, 144 int spec_reg, u16 value, u32 old_value) 145{ 146 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 147 int shift = (spec_reg & 0x2) * 8; 148 u32 ret; 149 150 switch (spec_reg) { 151 case SDHCI_TRANSFER_MODE: 152 /* 153 * Postpone this write, we must do it together with a 154 * command write that is down below. Return old value. 155 */ 156 pltfm_host->xfer_mode_shadow = value; 157 return old_value; 158 case SDHCI_COMMAND: 159 ret = (value << 16) | pltfm_host->xfer_mode_shadow; 160 return ret; 161 } 162 163 ret = old_value & (~(0xffff << shift)); 164 ret |= (value << shift); 165 166 if (spec_reg == SDHCI_BLOCK_SIZE) { 167 /* 168 * Two last DMA bits are reserved, and first one is used for 169 * non-standard blksz of 4096 bytes that we don't support 170 * yet. So clear the DMA boundary bits. 171 */ 172 ret &= (~SDHCI_MAKE_BLKSZ(0x7, 0)); 173 } 174 return ret; 175} 176 177static u32 esdhc_writeb_fixup(struct sdhci_host *host, 178 int spec_reg, u8 value, u32 old_value) 179{ 180 u32 ret; 181 u32 dma_bits; 182 u8 tmp; 183 int shift = (spec_reg & 0x3) * 8; 184 185 /* 186 * eSDHC doesn't have a standard power control register, so we do 187 * nothing here to avoid incorrect operation. 188 */ 189 if (spec_reg == SDHCI_POWER_CONTROL) 190 return old_value; 191 /* 192 * "DMA select" location is offset 0x28 in SD specification, but on 193 * P5020 or P3041, it's located at 0x29. 194 */ 195 if (spec_reg == SDHCI_HOST_CONTROL) { 196 /* 197 * If host control register is not standard, exit 198 * this function 199 */ 200 if (host->quirks2 & SDHCI_QUIRK2_BROKEN_HOST_CONTROL) 201 return old_value; 202 203 /* DMA select is 22,23 bits in Protocol Control Register */ 204 dma_bits = (value & SDHCI_CTRL_DMA_MASK) << 5; 205 ret = (old_value & (~(SDHCI_CTRL_DMA_MASK << 5))) | dma_bits; 206 tmp = (value & (~SDHCI_CTRL_DMA_MASK)) | 207 (old_value & SDHCI_CTRL_DMA_MASK); 208 ret = (ret & (~0xff)) | tmp; 209 210 /* Prevent SDHCI core from writing reserved bits (e.g. HISPD) */ 211 ret &= ~ESDHC_HOST_CONTROL_RES; 212 return ret; 213 } 214 215 ret = (old_value & (~(0xff << shift))) | (value << shift); 216 return ret; 217} 218 219static u32 esdhc_be_readl(struct sdhci_host *host, int reg) 220{ 221 u32 ret; 222 u32 value; 223 224 value = ioread32be(host->ioaddr + reg); 225 ret = esdhc_readl_fixup(host, reg, value); 226 227 return ret; 228} 229 230static u32 esdhc_le_readl(struct sdhci_host *host, int reg) 231{ 232 u32 ret; 233 u32 value; 234 235 value = ioread32(host->ioaddr + reg); 236 ret = esdhc_readl_fixup(host, reg, value); 237 238 return ret; 239} 240 241static u16 esdhc_be_readw(struct sdhci_host *host, int reg) 242{ 243 u16 ret; 244 u32 value; 245 int base = reg & ~0x3; 246 247 value = ioread32be(host->ioaddr + base); 248 ret = esdhc_readw_fixup(host, reg, value); 249 return ret; 250} 251 252static u16 esdhc_le_readw(struct sdhci_host *host, int reg) 253{ 254 u16 ret; 255 u32 value; 256 int base = reg & ~0x3; 257 258 value = ioread32(host->ioaddr + base); 259 ret = esdhc_readw_fixup(host, reg, value); 260 return ret; 261} 262 263static u8 esdhc_be_readb(struct sdhci_host *host, int reg) 264{ 265 u8 ret; 266 u32 value; 267 int base = reg & ~0x3; 268 269 value = ioread32be(host->ioaddr + base); 270 ret = esdhc_readb_fixup(host, reg, value); 271 return ret; 272} 273 274static u8 esdhc_le_readb(struct sdhci_host *host, int reg) 275{ 276 u8 ret; 277 u32 value; 278 int base = reg & ~0x3; 279 280 value = ioread32(host->ioaddr + base); 281 ret = esdhc_readb_fixup(host, reg, value); 282 return ret; 283} 284 285static void esdhc_be_writel(struct sdhci_host *host, u32 val, int reg) 286{ 287 u32 value; 288 289 value = esdhc_writel_fixup(host, reg, val, 0); 290 iowrite32be(value, host->ioaddr + reg); 291} 292 293static void esdhc_le_writel(struct sdhci_host *host, u32 val, int reg) 294{ 295 u32 value; 296 297 value = esdhc_writel_fixup(host, reg, val, 0); 298 iowrite32(value, host->ioaddr + reg); 299} 300 301static void esdhc_be_writew(struct sdhci_host *host, u16 val, int reg) 302{ 303 int base = reg & ~0x3; 304 u32 value; 305 u32 ret; 306 307 value = ioread32be(host->ioaddr + base); 308 ret = esdhc_writew_fixup(host, reg, val, value); 309 if (reg != SDHCI_TRANSFER_MODE) 310 iowrite32be(ret, host->ioaddr + base); 311} 312 313static void esdhc_le_writew(struct sdhci_host *host, u16 val, int reg) 314{ 315 int base = reg & ~0x3; 316 u32 value; 317 u32 ret; 318 319 value = ioread32(host->ioaddr + base); 320 ret = esdhc_writew_fixup(host, reg, val, value); 321 if (reg != SDHCI_TRANSFER_MODE) 322 iowrite32(ret, host->ioaddr + base); 323} 324 325static void esdhc_be_writeb(struct sdhci_host *host, u8 val, int reg) 326{ 327 int base = reg & ~0x3; 328 u32 value; 329 u32 ret; 330 331 value = ioread32be(host->ioaddr + base); 332 ret = esdhc_writeb_fixup(host, reg, val, value); 333 iowrite32be(ret, host->ioaddr + base); 334} 335 336static void esdhc_le_writeb(struct sdhci_host *host, u8 val, int reg) 337{ 338 int base = reg & ~0x3; 339 u32 value; 340 u32 ret; 341 342 value = ioread32(host->ioaddr + base); 343 ret = esdhc_writeb_fixup(host, reg, val, value); 344 iowrite32(ret, host->ioaddr + base); 345} 346 347/* 348 * For Abort or Suspend after Stop at Block Gap, ignore the ADMA 349 * error(IRQSTAT[ADMAE]) if both Transfer Complete(IRQSTAT[TC]) 350 * and Block Gap Event(IRQSTAT[BGE]) are also set. 351 * For Continue, apply soft reset for data(SYSCTL[RSTD]); 352 * and re-issue the entire read transaction from beginning. 353 */ 354static void esdhc_of_adma_workaround(struct sdhci_host *host, u32 intmask) 355{ 356 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 357 struct sdhci_esdhc *esdhc = pltfm_host->priv; 358 bool applicable; 359 dma_addr_t dmastart; 360 dma_addr_t dmanow; 361 362 applicable = (intmask & SDHCI_INT_DATA_END) && 363 (intmask & SDHCI_INT_BLK_GAP) && 364 (esdhc->vendor_ver == VENDOR_V_23); 365 if (!applicable) 366 return; 367 368 host->data->error = 0; 369 dmastart = sg_dma_address(host->data->sg); 370 dmanow = dmastart + host->data->bytes_xfered; 371 /* 372 * Force update to the next DMA block boundary. 373 */ 374 dmanow = (dmanow & ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) + 375 SDHCI_DEFAULT_BOUNDARY_SIZE; 376 host->data->bytes_xfered = dmanow - dmastart; 377 sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS); 378} 379 380static int esdhc_of_enable_dma(struct sdhci_host *host) 381{ 382 u32 value; 383 384 value = sdhci_readl(host, ESDHC_DMA_SYSCTL); 385 value |= ESDHC_DMA_SNOOP; 386 sdhci_writel(host, value, ESDHC_DMA_SYSCTL); 387 return 0; 388} 389 390static unsigned int esdhc_of_get_max_clock(struct sdhci_host *host) 391{ 392 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 393 394 return pltfm_host->clock; 395} 396 397static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) 398{ 399 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 400 401 return pltfm_host->clock / 256 / 16; 402} 403 404static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) 405{ 406 struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 407 struct sdhci_esdhc *esdhc = pltfm_host->priv; 408 int pre_div = 1; 409 int div = 1; 410 u32 temp; 411 412 host->mmc->actual_clock = 0; 413 414 if (clock == 0) 415 return; 416 417 /* Workaround to start pre_div at 2 for VNN < VENDOR_V_23 */ 418 if (esdhc->vendor_ver < VENDOR_V_23) 419 pre_div = 2; 420 421 /* Workaround to reduce the clock frequency for p1010 esdhc */ 422 if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) { 423 if (clock > 20000000) 424 clock -= 5000000; 425 if (clock > 40000000) 426 clock -= 5000000; 427 } 428 429 temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); 430 temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN 431 | ESDHC_CLOCK_MASK); 432 sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); 433 434 while (host->max_clk / pre_div / 16 > clock && pre_div < 256) 435 pre_div *= 2; 436 437 while (host->max_clk / pre_div / div > clock && div < 16) 438 div++; 439 440 dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n", 441 clock, host->max_clk / pre_div / div); 442 host->mmc->actual_clock = host->max_clk / pre_div / div; 443 pre_div >>= 1; 444 div--; 445 446 temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL); 447 temp |= (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN 448 | (div << ESDHC_DIVIDER_SHIFT) 449 | (pre_div << ESDHC_PREDIV_SHIFT)); 450 sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL); 451 mdelay(1); 452} 453 454static void esdhc_pltfm_set_bus_width(struct sdhci_host *host, int width) 455{ 456 u32 ctrl; 457 458 ctrl = sdhci_readl(host, ESDHC_PROCTL); 459 ctrl &= (~ESDHC_CTRL_BUSWIDTH_MASK); 460 switch (width) { 461 case MMC_BUS_WIDTH_8: 462 ctrl |= ESDHC_CTRL_8BITBUS; 463 break; 464 465 case MMC_BUS_WIDTH_4: 466 ctrl |= ESDHC_CTRL_4BITBUS; 467 break; 468 469 default: 470 break; 471 } 472 473 sdhci_writel(host, ctrl, ESDHC_PROCTL); 474} 475 476static void esdhc_reset(struct sdhci_host *host, u8 mask) 477{ 478 sdhci_reset(host, mask); 479 480 sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); 481 sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); 482} 483 484#ifdef CONFIG_PM 485static u32 esdhc_proctl; 486static int esdhc_of_suspend(struct device *dev) 487{ 488 struct sdhci_host *host = dev_get_drvdata(dev); 489 490 esdhc_proctl = sdhci_readl(host, SDHCI_HOST_CONTROL); 491 492 return sdhci_suspend_host(host); 493} 494 495static int esdhc_of_resume(struct device *dev) 496{ 497 struct sdhci_host *host = dev_get_drvdata(dev); 498 int ret = sdhci_resume_host(host); 499 500 if (ret == 0) { 501 /* Isn't this already done by sdhci_resume_host() ? --rmk */ 502 esdhc_of_enable_dma(host); 503 sdhci_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); 504 } 505 return ret; 506} 507 508static const struct dev_pm_ops esdhc_pmops = { 509 .suspend = esdhc_of_suspend, 510 .resume = esdhc_of_resume, 511}; 512#define ESDHC_PMOPS (&esdhc_pmops) 513#else 514#define ESDHC_PMOPS NULL 515#endif 516 517static const struct sdhci_ops sdhci_esdhc_be_ops = { 518 .read_l = esdhc_be_readl, 519 .read_w = esdhc_be_readw, 520 .read_b = esdhc_be_readb, 521 .write_l = esdhc_be_writel, 522 .write_w = esdhc_be_writew, 523 .write_b = esdhc_be_writeb, 524 .set_clock = esdhc_of_set_clock, 525 .enable_dma = esdhc_of_enable_dma, 526 .get_max_clock = esdhc_of_get_max_clock, 527 .get_min_clock = esdhc_of_get_min_clock, 528 .adma_workaround = esdhc_of_adma_workaround, 529 .set_bus_width = esdhc_pltfm_set_bus_width, 530 .reset = esdhc_reset, 531 .set_uhs_signaling = sdhci_set_uhs_signaling, 532}; 533 534static const struct sdhci_ops sdhci_esdhc_le_ops = { 535 .read_l = esdhc_le_readl, 536 .read_w = esdhc_le_readw, 537 .read_b = esdhc_le_readb, 538 .write_l = esdhc_le_writel, 539 .write_w = esdhc_le_writew, 540 .write_b = esdhc_le_writeb, 541 .set_clock = esdhc_of_set_clock, 542 .enable_dma = esdhc_of_enable_dma, 543 .get_max_clock = esdhc_of_get_max_clock, 544 .get_min_clock = esdhc_of_get_min_clock, 545 .adma_workaround = esdhc_of_adma_workaround, 546 .set_bus_width = esdhc_pltfm_set_bus_width, 547 .reset = esdhc_reset, 548 .set_uhs_signaling = sdhci_set_uhs_signaling, 549}; 550 551static const struct sdhci_pltfm_data sdhci_esdhc_be_pdata = { 552 .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION 553 | SDHCI_QUIRK_NO_CARD_NO_RESET 554 | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, 555 .ops = &sdhci_esdhc_be_ops, 556}; 557 558static const struct sdhci_pltfm_data sdhci_esdhc_le_pdata = { 559 .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION 560 | SDHCI_QUIRK_NO_CARD_NO_RESET 561 | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, 562 .ops = &sdhci_esdhc_le_ops, 563}; 564 565static void esdhc_init(struct platform_device *pdev, struct sdhci_host *host) 566{ 567 struct sdhci_pltfm_host *pltfm_host; 568 struct sdhci_esdhc *esdhc; 569 u16 host_ver; 570 571 pltfm_host = sdhci_priv(host); 572 esdhc = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_esdhc), 573 GFP_KERNEL); 574 575 host_ver = sdhci_readw(host, SDHCI_HOST_VERSION); 576 esdhc->vendor_ver = (host_ver & SDHCI_VENDOR_VER_MASK) >> 577 SDHCI_VENDOR_VER_SHIFT; 578 esdhc->spec_ver = host_ver & SDHCI_SPEC_VER_MASK; 579 580 pltfm_host->priv = esdhc; 581} 582 583static int sdhci_esdhc_probe(struct platform_device *pdev) 584{ 585 struct sdhci_host *host; 586 struct device_node *np; 587 int ret; 588 589 np = pdev->dev.of_node; 590 591 if (of_get_property(np, "little-endian", NULL)) 592 host = sdhci_pltfm_init(pdev, &sdhci_esdhc_le_pdata, 0); 593 else 594 host = sdhci_pltfm_init(pdev, &sdhci_esdhc_be_pdata, 0); 595 596 if (IS_ERR(host)) 597 return PTR_ERR(host); 598 599 esdhc_init(pdev, host); 600 601 sdhci_get_of_property(pdev); 602 603 if (of_device_is_compatible(np, "fsl,p5040-esdhc") || 604 of_device_is_compatible(np, "fsl,p5020-esdhc") || 605 of_device_is_compatible(np, "fsl,p4080-esdhc") || 606 of_device_is_compatible(np, "fsl,p1020-esdhc") || 607 of_device_is_compatible(np, "fsl,t1040-esdhc") || 608 of_device_is_compatible(np, "fsl,ls1021a-esdhc")) 609 host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; 610 611 if (of_device_is_compatible(np, "fsl,ls1021a-esdhc")) 612 host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; 613 614 if (of_device_is_compatible(np, "fsl,p2020-esdhc")) { 615 /* 616 * Freescale messed up with P2020 as it has a non-standard 617 * host control register 618 */ 619 host->quirks2 |= SDHCI_QUIRK2_BROKEN_HOST_CONTROL; 620 } 621 622 /* call to generic mmc_of_parse to support additional capabilities */ 623 ret = mmc_of_parse(host->mmc); 624 if (ret) 625 goto err; 626 627 mmc_of_parse_voltage(np, &host->ocr_mask); 628 629 ret = sdhci_add_host(host); 630 if (ret) 631 goto err; 632 633 return 0; 634 err: 635 sdhci_pltfm_free(pdev); 636 return ret; 637} 638 639static const struct of_device_id sdhci_esdhc_of_match[] = { 640 { .compatible = "fsl,mpc8379-esdhc" }, 641 { .compatible = "fsl,mpc8536-esdhc" }, 642 { .compatible = "fsl,esdhc" }, 643 { } 644}; 645MODULE_DEVICE_TABLE(of, sdhci_esdhc_of_match); 646 647static struct platform_driver sdhci_esdhc_driver = { 648 .driver = { 649 .name = "sdhci-esdhc", 650 .of_match_table = sdhci_esdhc_of_match, 651 .pm = ESDHC_PMOPS, 652 }, 653 .probe = sdhci_esdhc_probe, 654 .remove = sdhci_pltfm_unregister, 655}; 656 657module_platform_driver(sdhci_esdhc_driver); 658 659MODULE_DESCRIPTION("SDHCI OF driver for Freescale MPC eSDHC"); 660MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, " 661 "Anton Vorontsov <avorontsov@ru.mvista.com>"); 662MODULE_LICENSE("GPL v2"); 663