diff options
Diffstat (limited to 'drivers/mfd/sm501.c')
-rw-r--r-- | drivers/mfd/sm501.c | 163 |
1 files changed, 128 insertions, 35 deletions
diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 4de8d467762a..13bac53db69a 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c | |||
@@ -48,6 +48,7 @@ struct sm501_devdata { | |||
48 | unsigned int pdev_id; | 48 | unsigned int pdev_id; |
49 | unsigned int irq; | 49 | unsigned int irq; |
50 | void __iomem *regs; | 50 | void __iomem *regs; |
51 | unsigned int rev; | ||
51 | }; | 52 | }; |
52 | 53 | ||
53 | #define MHZ (1000 * 1000) | 54 | #define MHZ (1000 * 1000) |
@@ -417,46 +418,108 @@ struct sm501_clock { | |||
417 | unsigned long mclk; | 418 | unsigned long mclk; |
418 | int divider; | 419 | int divider; |
419 | int shift; | 420 | int shift; |
421 | unsigned int m, n, k; | ||
420 | }; | 422 | }; |
421 | 423 | ||
424 | /* sm501_calc_clock | ||
425 | * | ||
426 | * Calculates the nearest discrete clock frequency that | ||
427 | * can be achieved with the specified input clock. | ||
428 | * the maximum divisor is 3 or 5 | ||
429 | */ | ||
430 | |||
431 | static int sm501_calc_clock(unsigned long freq, | ||
432 | struct sm501_clock *clock, | ||
433 | int max_div, | ||
434 | unsigned long mclk, | ||
435 | long *best_diff) | ||
436 | { | ||
437 | int ret = 0; | ||
438 | int divider; | ||
439 | int shift; | ||
440 | long diff; | ||
441 | |||
442 | /* try dividers 1 and 3 for CRT and for panel, | ||
443 | try divider 5 for panel only.*/ | ||
444 | |||
445 | for (divider = 1; divider <= max_div; divider += 2) { | ||
446 | /* try all 8 shift values.*/ | ||
447 | for (shift = 0; shift < 8; shift++) { | ||
448 | /* Calculate difference to requested clock */ | ||
449 | diff = sm501fb_round_div(mclk, divider << shift) - freq; | ||
450 | if (diff < 0) | ||
451 | diff = -diff; | ||
452 | |||
453 | /* If it is less than the current, use it */ | ||
454 | if (diff < *best_diff) { | ||
455 | *best_diff = diff; | ||
456 | |||
457 | clock->mclk = mclk; | ||
458 | clock->divider = divider; | ||
459 | clock->shift = shift; | ||
460 | ret = 1; | ||
461 | } | ||
462 | } | ||
463 | } | ||
464 | |||
465 | return ret; | ||
466 | } | ||
467 | |||
468 | /* sm501_calc_pll | ||
469 | * | ||
470 | * Calculates the nearest discrete clock frequency that can be | ||
471 | * achieved using the programmable PLL. | ||
472 | * the maximum divisor is 3 or 5 | ||
473 | */ | ||
474 | |||
475 | static unsigned long sm501_calc_pll(unsigned long freq, | ||
476 | struct sm501_clock *clock, | ||
477 | int max_div) | ||
478 | { | ||
479 | unsigned long mclk; | ||
480 | unsigned int m, n, k; | ||
481 | long best_diff = 999999999; | ||
482 | |||
483 | /* | ||
484 | * The SM502 datasheet doesn't specify the min/max values for M and N. | ||
485 | * N = 1 at least doesn't work in practice. | ||
486 | */ | ||
487 | for (m = 2; m <= 255; m++) { | ||
488 | for (n = 2; n <= 127; n++) { | ||
489 | for (k = 0; k <= 1; k++) { | ||
490 | mclk = (24000000UL * m / n) >> k; | ||
491 | |||
492 | if (sm501_calc_clock(freq, clock, max_div, | ||
493 | mclk, &best_diff)) { | ||
494 | clock->m = m; | ||
495 | clock->n = n; | ||
496 | clock->k = k; | ||
497 | } | ||
498 | } | ||
499 | } | ||
500 | } | ||
501 | |||
502 | /* Return best clock. */ | ||
503 | return clock->mclk / (clock->divider << clock->shift); | ||
504 | } | ||
505 | |||
422 | /* sm501_select_clock | 506 | /* sm501_select_clock |
423 | * | 507 | * |
424 | * selects nearest discrete clock frequency the SM501 can achive | 508 | * Calculates the nearest discrete clock frequency that can be |
509 | * achieved using the 288MHz and 336MHz PLLs. | ||
425 | * the maximum divisor is 3 or 5 | 510 | * the maximum divisor is 3 or 5 |
426 | */ | 511 | */ |
512 | |||
427 | static unsigned long sm501_select_clock(unsigned long freq, | 513 | static unsigned long sm501_select_clock(unsigned long freq, |
428 | struct sm501_clock *clock, | 514 | struct sm501_clock *clock, |
429 | int max_div) | 515 | int max_div) |
430 | { | 516 | { |
431 | unsigned long mclk; | 517 | unsigned long mclk; |
432 | int divider; | ||
433 | int shift; | ||
434 | long diff; | ||
435 | long best_diff = 999999999; | 518 | long best_diff = 999999999; |
436 | 519 | ||
437 | /* Try 288MHz and 336MHz clocks. */ | 520 | /* Try 288MHz and 336MHz clocks. */ |
438 | for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) { | 521 | for (mclk = 288000000; mclk <= 336000000; mclk += 48000000) { |
439 | /* try dividers 1 and 3 for CRT and for panel, | 522 | sm501_calc_clock(freq, clock, max_div, mclk, &best_diff); |
440 | try divider 5 for panel only.*/ | ||
441 | |||
442 | for (divider = 1; divider <= max_div; divider += 2) { | ||
443 | /* try all 8 shift values.*/ | ||
444 | for (shift = 0; shift < 8; shift++) { | ||
445 | /* Calculate difference to requested clock */ | ||
446 | diff = sm501fb_round_div(mclk, divider << shift) - freq; | ||
447 | if (diff < 0) | ||
448 | diff = -diff; | ||
449 | |||
450 | /* If it is less than the current, use it */ | ||
451 | if (diff < best_diff) { | ||
452 | best_diff = diff; | ||
453 | |||
454 | clock->mclk = mclk; | ||
455 | clock->divider = divider; | ||
456 | clock->shift = shift; | ||
457 | } | ||
458 | } | ||
459 | } | ||
460 | } | 523 | } |
461 | 524 | ||
462 | /* Return best clock. */ | 525 | /* Return best clock. */ |
@@ -478,6 +541,7 @@ unsigned long sm501_set_clock(struct device *dev, | |||
478 | unsigned long gate = readl(sm->regs + SM501_CURRENT_GATE); | 541 | unsigned long gate = readl(sm->regs + SM501_CURRENT_GATE); |
479 | unsigned long clock = readl(sm->regs + SM501_CURRENT_CLOCK); | 542 | unsigned long clock = readl(sm->regs + SM501_CURRENT_CLOCK); |
480 | unsigned char reg; | 543 | unsigned char reg; |
544 | unsigned int pll_reg = 0; | ||
481 | unsigned long sm501_freq; /* the actual frequency acheived */ | 545 | unsigned long sm501_freq; /* the actual frequency acheived */ |
482 | 546 | ||
483 | struct sm501_clock to; | 547 | struct sm501_clock to; |
@@ -492,14 +556,28 @@ unsigned long sm501_set_clock(struct device *dev, | |||
492 | * requested frequency the value must be multiplied by | 556 | * requested frequency the value must be multiplied by |
493 | * 2. This clock also has an additional pre divisor */ | 557 | * 2. This clock also has an additional pre divisor */ |
494 | 558 | ||
495 | sm501_freq = (sm501_select_clock(2 * req_freq, &to, 5) / 2); | 559 | if (sm->rev >= 0xC0) { |
496 | reg=to.shift & 0x07;/* bottom 3 bits are shift */ | 560 | /* SM502 -> use the programmable PLL */ |
497 | if (to.divider == 3) | 561 | sm501_freq = (sm501_calc_pll(2 * req_freq, |
498 | reg |= 0x08; /* /3 divider required */ | 562 | &to, 5) / 2); |
499 | else if (to.divider == 5) | 563 | reg = to.shift & 0x07;/* bottom 3 bits are shift */ |
500 | reg |= 0x10; /* /5 divider required */ | 564 | if (to.divider == 3) |
501 | if (to.mclk != 288000000) | 565 | reg |= 0x08; /* /3 divider required */ |
502 | reg |= 0x20; /* which mclk pll is source */ | 566 | else if (to.divider == 5) |
567 | reg |= 0x10; /* /5 divider required */ | ||
568 | reg |= 0x40; /* select the programmable PLL */ | ||
569 | pll_reg = 0x20000 | (to.k << 15) | (to.n << 8) | to.m; | ||
570 | } else { | ||
571 | sm501_freq = (sm501_select_clock(2 * req_freq, | ||
572 | &to, 5) / 2); | ||
573 | reg = to.shift & 0x07;/* bottom 3 bits are shift */ | ||
574 | if (to.divider == 3) | ||
575 | reg |= 0x08; /* /3 divider required */ | ||
576 | else if (to.divider == 5) | ||
577 | reg |= 0x10; /* /5 divider required */ | ||
578 | if (to.mclk != 288000000) | ||
579 | reg |= 0x20; /* which mclk pll is source */ | ||
580 | } | ||
503 | break; | 581 | break; |
504 | 582 | ||
505 | case SM501_CLOCK_V2XCLK: | 583 | case SM501_CLOCK_V2XCLK: |
@@ -560,6 +638,10 @@ unsigned long sm501_set_clock(struct device *dev, | |||
560 | } | 638 | } |
561 | 639 | ||
562 | writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); | 640 | writel(mode, sm->regs + SM501_POWER_MODE_CONTROL); |
641 | |||
642 | if (pll_reg) | ||
643 | writel(pll_reg, sm->regs + SM501_PROGRAMMABLE_PLL_CONTROL); | ||
644 | |||
563 | sm501_sync_regs(sm); | 645 | sm501_sync_regs(sm); |
564 | 646 | ||
565 | dev_info(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", | 647 | dev_info(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", |
@@ -580,15 +662,24 @@ EXPORT_SYMBOL_GPL(sm501_set_clock); | |||
580 | * finds the closest available frequency for a given clock | 662 | * finds the closest available frequency for a given clock |
581 | */ | 663 | */ |
582 | 664 | ||
583 | unsigned long sm501_find_clock(int clksrc, | 665 | unsigned long sm501_find_clock(struct device *dev, |
666 | int clksrc, | ||
584 | unsigned long req_freq) | 667 | unsigned long req_freq) |
585 | { | 668 | { |
669 | struct sm501_devdata *sm = dev_get_drvdata(dev); | ||
586 | unsigned long sm501_freq; /* the frequency achiveable by the 501 */ | 670 | unsigned long sm501_freq; /* the frequency achiveable by the 501 */ |
587 | struct sm501_clock to; | 671 | struct sm501_clock to; |
588 | 672 | ||
589 | switch (clksrc) { | 673 | switch (clksrc) { |
590 | case SM501_CLOCK_P2XCLK: | 674 | case SM501_CLOCK_P2XCLK: |
591 | sm501_freq = (sm501_select_clock(2 * req_freq, &to, 5) / 2); | 675 | if (sm->rev >= 0xC0) { |
676 | /* SM502 -> use the programmable PLL */ | ||
677 | sm501_freq = (sm501_calc_pll(2 * req_freq, | ||
678 | &to, 5) / 2); | ||
679 | } else { | ||
680 | sm501_freq = (sm501_select_clock(2 * req_freq, | ||
681 | &to, 5) / 2); | ||
682 | } | ||
592 | break; | 683 | break; |
593 | 684 | ||
594 | case SM501_CLOCK_V2XCLK: | 685 | case SM501_CLOCK_V2XCLK: |
@@ -895,6 +986,8 @@ static int sm501_init_dev(struct sm501_devdata *sm) | |||
895 | dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n", | 986 | dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n", |
896 | sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq); | 987 | sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq); |
897 | 988 | ||
989 | sm->rev = devid & SM501_DEVICEID_REVMASK; | ||
990 | |||
898 | sm501_dump_gate(sm); | 991 | sm501_dump_gate(sm); |
899 | 992 | ||
900 | ret = device_create_file(sm->dev, &dev_attr_dbg_regs); | 993 | ret = device_create_file(sm->dev, &dev_attr_dbg_regs); |