diff options
author | Nicolin Chen <b42378@freescale.com> | 2013-08-23 07:31:52 -0400 |
---|---|---|
committer | Nitin Garg <nitin.garg@freescale.com> | 2014-04-16 09:01:31 -0400 |
commit | b34d7e9229f04a02592cf1424820ba8aef5a5f5c (patch) | |
tree | 4fcf7461b28963836575c28c358b482fb3f4722f | |
parent | 2df4851ae48d1b2d4e10b0f6aea0386937880af1 (diff) |
ENGR00276567-2 ASoC: fsl: Add I2S Master support for ssi
Add I2S master/PCM(DSP_A, DSP_B)/LEFT_J formats support for ssi,
also dropped the constrain of i2s-slave in probe().
Signed-off-by: Nicolin Chen <b42378@freescale.com>
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 219 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_ssi.h | 6 |
2 files changed, 215 insertions, 10 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index ad9875f7bb30..894e2109c8a0 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c | |||
@@ -138,6 +138,7 @@ struct fsl_ssi_private { | |||
138 | struct device_attribute dev_attr; | 138 | struct device_attribute dev_attr; |
139 | struct platform_device *pdev; | 139 | struct platform_device *pdev; |
140 | 140 | ||
141 | unsigned long sysrate; | ||
141 | bool new_binding; | 142 | bool new_binding; |
142 | bool ssi_on_imx; | 143 | bool ssi_on_imx; |
143 | bool use_dual_fifo; | 144 | bool use_dual_fifo; |
@@ -488,6 +489,8 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, | |||
488 | snd_pcm_format_width(params_format(hw_params)); | 489 | snd_pcm_format_width(params_format(hw_params)); |
489 | u32 wl = CCSR_SSI_SxCCR_WL(sample_size); | 490 | u32 wl = CCSR_SSI_SxCCR_WL(sample_size); |
490 | int enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN; | 491 | int enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN; |
492 | u32 dc = CCSR_SSI_SxCCR_DC(params_channels(hw_params)); | ||
493 | int ret; | ||
491 | 494 | ||
492 | /* | 495 | /* |
493 | * If we're in synchronous mode, and the SSI is already enabled, | 496 | * If we're in synchronous mode, and the SSI is already enabled, |
@@ -496,6 +499,14 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, | |||
496 | if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) | 499 | if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) |
497 | return 0; | 500 | return 0; |
498 | 501 | ||
502 | if (ssi_private->sysrate) { | ||
503 | ret = clk_set_rate(ssi_private->clk, ssi_private->sysrate); | ||
504 | if (ret) { | ||
505 | dev_err(cpu_dai->dev, "failed to set clock rate\n"); | ||
506 | return ret; | ||
507 | } | ||
508 | } | ||
509 | |||
499 | /* | 510 | /* |
500 | * FIXME: The documentation says that SxCCR[WL] should not be | 511 | * FIXME: The documentation says that SxCCR[WL] should not be |
501 | * modified while the SSI is enabled. The only time this can | 512 | * modified while the SSI is enabled. The only time this can |
@@ -508,10 +519,13 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, | |||
508 | 519 | ||
509 | /* In synchronous mode, the SSI uses STCCR for capture */ | 520 | /* In synchronous mode, the SSI uses STCCR for capture */ |
510 | if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || | 521 | if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || |
511 | ssi_private->cpu_dai_drv.symmetric_rates) | 522 | ssi_private->cpu_dai_drv.symmetric_rates) { |
512 | write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); | 523 | write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); |
513 | else | 524 | write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_DC_MASK, dc); |
525 | } else { | ||
514 | write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); | 526 | write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); |
527 | write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, dc); | ||
528 | } | ||
515 | 529 | ||
516 | return 0; | 530 | return 0; |
517 | } | 531 | } |
@@ -567,6 +581,194 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, | |||
567 | return 0; | 581 | return 0; |
568 | } | 582 | } |
569 | 583 | ||
584 | static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) | ||
585 | { | ||
586 | struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); | ||
587 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
588 | u32 strcr = 0, stcr, srcr, scr, mask; | ||
589 | |||
590 | scr = read_ssi(&ssi->scr) & ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_NET); | ||
591 | |||
592 | mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR | ||
593 | | CCSR_SSI_STCR_TSCKP | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TFSL | ||
594 | | CCSR_SSI_STCR_TEFS; | ||
595 | stcr = read_ssi(&ssi->stcr) & ~mask; | ||
596 | srcr = read_ssi(&ssi->srcr) & ~mask; | ||
597 | |||
598 | /* DAI format */ | ||
599 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
600 | case SND_SOC_DAIFMT_I2S: | ||
601 | scr |= CCSR_SSI_SCR_NET; | ||
602 | |||
603 | /* Pre-set SSI I2S mode */ | ||
604 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
605 | case SND_SOC_DAIFMT_CBS_CFS: | ||
606 | scr &= ~CCSR_SSI_SCR_I2S_MODE_MASK; | ||
607 | scr |= CCSR_SSI_SCR_I2S_MODE_MASTER; | ||
608 | break; | ||
609 | case SND_SOC_DAIFMT_CBM_CFM: | ||
610 | scr &= ~CCSR_SSI_SCR_I2S_MODE_MASK; | ||
611 | scr |= CCSR_SSI_SCR_I2S_MODE_SLAVE; | ||
612 | break; | ||
613 | default: | ||
614 | dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_MASTER: %d", | ||
615 | fmt & SND_SOC_DAIFMT_MASTER_MASK); | ||
616 | return -EINVAL; | ||
617 | } | ||
618 | |||
619 | /* Data on rising edge of bclk, frame low, 1clk before data */ | ||
620 | strcr |= CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TSCKP | ||
621 | | CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; | ||
622 | break; | ||
623 | case SND_SOC_DAIFMT_LEFT_J: | ||
624 | /* Data on rising edge of bclk, frame high */ | ||
625 | strcr |= CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TSCKP; | ||
626 | break; | ||
627 | case SND_SOC_DAIFMT_DSP_A: | ||
628 | /* Data on rising edge of bclk, frame high, 1clk before data */ | ||
629 | strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | ||
630 | | CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; | ||
631 | break; | ||
632 | case SND_SOC_DAIFMT_DSP_B: | ||
633 | /* Data on rising edge of bclk, frame high */ | ||
634 | strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | ||
635 | | CCSR_SSI_STCR_TXBIT0; | ||
636 | break; | ||
637 | default: | ||
638 | dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT: %d", | ||
639 | fmt & SND_SOC_DAIFMT_FORMAT_MASK); | ||
640 | return -EINVAL; | ||
641 | } | ||
642 | |||
643 | /* DAI clock inversion */ | ||
644 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
645 | case SND_SOC_DAIFMT_NB_NF: | ||
646 | /* Nothing to do for both normal cases */ | ||
647 | break; | ||
648 | case SND_SOC_DAIFMT_IB_NF: | ||
649 | /* Invert bit clock */ | ||
650 | strcr ^= CCSR_SSI_STCR_TSCKP; | ||
651 | break; | ||
652 | case SND_SOC_DAIFMT_NB_IF: | ||
653 | /* Invert frame clock */ | ||
654 | strcr ^= CCSR_SSI_STCR_TFSI; | ||
655 | break; | ||
656 | case SND_SOC_DAIFMT_IB_IF: | ||
657 | /* Invert both clocks */ | ||
658 | strcr ^= CCSR_SSI_STCR_TSCKP; | ||
659 | strcr ^= CCSR_SSI_STCR_TFSI; | ||
660 | break; | ||
661 | default: | ||
662 | dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_INV: %d", | ||
663 | fmt & SND_SOC_DAIFMT_INV_MASK); | ||
664 | return -EINVAL; | ||
665 | } | ||
666 | |||
667 | /* DAI clock master masks */ | ||
668 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
669 | case SND_SOC_DAIFMT_CBS_CFS: | ||
670 | scr |= CCSR_SSI_SCR_SYS_CLK_EN; | ||
671 | strcr |= CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR; | ||
672 | break; | ||
673 | case SND_SOC_DAIFMT_CBM_CFM: | ||
674 | scr &= ~CCSR_SSI_SCR_SYS_CLK_EN; | ||
675 | break; | ||
676 | default: | ||
677 | dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_MASTER: %d", | ||
678 | fmt & SND_SOC_DAIFMT_MASTER_MASK); | ||
679 | return -EINVAL; | ||
680 | } | ||
681 | |||
682 | stcr |= strcr; | ||
683 | srcr |= strcr; | ||
684 | |||
685 | if (ssi_private->cpu_dai_drv.symmetric_rates) { | ||
686 | scr |= CCSR_SSI_SCR_SYN; | ||
687 | |||
688 | /* Need to clear RXDIR when using SYNC mode */ | ||
689 | srcr &= ~CCSR_SSI_SRCR_RXDIR; | ||
690 | } | ||
691 | |||
692 | write_ssi(stcr, &ssi->stcr); | ||
693 | write_ssi(srcr, &ssi->srcr); | ||
694 | write_ssi(scr, &ssi->scr); | ||
695 | |||
696 | return 0; | ||
697 | } | ||
698 | |||
699 | static int fsl_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, | ||
700 | int clk_id, unsigned int freq, int dir) | ||
701 | { | ||
702 | struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); | ||
703 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
704 | int synchronous = ssi_private->cpu_dai_drv.symmetric_rates; | ||
705 | u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i; | ||
706 | unsigned long clkrate, sysrate = 0; | ||
707 | u64 sub, savesub = 100000; | ||
708 | |||
709 | /* | ||
710 | * It should be already enough to divide clock by setting pm. | ||
711 | * So we here keep psr and div2 to 0. | ||
712 | */ | ||
713 | psr = 0; | ||
714 | div2 = 0; | ||
715 | |||
716 | factor = (div2 + 1) * (7 * psr + 1) * 2; | ||
717 | |||
718 | for (i = 0; i < 255; i++) { | ||
719 | /* The bclk rate must be smaller than 1/5 sysclk rate */ | ||
720 | if (factor * (i + 1) < 5) | ||
721 | continue; | ||
722 | |||
723 | sysrate = freq * factor * (i + 2); | ||
724 | clkrate = clk_round_rate(ssi_private->clk, sysrate); | ||
725 | |||
726 | do_div(clkrate, factor); | ||
727 | afreq = (u32)clkrate / (i + 1); | ||
728 | |||
729 | if (freq == afreq) | ||
730 | sub = 0; | ||
731 | else if (freq / afreq == 1) | ||
732 | sub = freq - afreq; | ||
733 | else if (afreq / freq == 1) | ||
734 | sub = afreq - freq; | ||
735 | else | ||
736 | continue; | ||
737 | |||
738 | /* Calculate the fraction */ | ||
739 | sub *= 100000; | ||
740 | do_div(sub, freq); | ||
741 | |||
742 | if (sub < savesub) { | ||
743 | ssi_private->sysrate = sysrate; | ||
744 | savesub = sub; | ||
745 | pm = i; | ||
746 | } | ||
747 | |||
748 | /* We are lucky */ | ||
749 | if (savesub == 0) | ||
750 | break; | ||
751 | } | ||
752 | |||
753 | /* No proper pm found if it is still remaining the initial value */ | ||
754 | if (pm == 999) { | ||
755 | dev_err(cpu_dai->dev, "failed to handle the required sysclk\n"); | ||
756 | return -EINVAL; | ||
757 | } | ||
758 | |||
759 | stccr = CCSR_SSI_SxCCR_PM(pm + 1) | (div2 ? CCSR_SSI_SxCCR_DIV2 : 0) | ||
760 | | (psr ? CCSR_SSI_SxCCR_PSR : 0); | ||
761 | mask = CCSR_SSI_SxCCR_PM_MASK | CCSR_SSI_SxCCR_DIV2_MASK | ||
762 | | CCSR_SSI_SxCCR_PSR_MASK; | ||
763 | |||
764 | if (dir == SND_SOC_CLOCK_OUT || synchronous) | ||
765 | write_ssi_mask(&ssi->stccr, mask, stccr); | ||
766 | else | ||
767 | write_ssi_mask(&ssi->srccr, mask, stccr); | ||
768 | |||
769 | return 0; | ||
770 | } | ||
771 | |||
570 | /** | 772 | /** |
571 | * fsl_ssi_shutdown: shutdown the SSI | 773 | * fsl_ssi_shutdown: shutdown the SSI |
572 | * | 774 | * |
@@ -588,6 +790,8 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, | |||
588 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | 790 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; |
589 | 791 | ||
590 | write_ssi_mask(&ssi->sier, SIER_FLAGS, 0); | 792 | write_ssi_mask(&ssi->sier, SIER_FLAGS, 0); |
793 | |||
794 | ssi_private->sysrate = 0; | ||
591 | } | 795 | } |
592 | 796 | ||
593 | if (ssi_private->ssi_on_imx) { | 797 | if (ssi_private->ssi_on_imx) { |
@@ -613,6 +817,8 @@ static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) | |||
613 | static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { | 817 | static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { |
614 | .startup = fsl_ssi_startup, | 818 | .startup = fsl_ssi_startup, |
615 | .hw_params = fsl_ssi_hw_params, | 819 | .hw_params = fsl_ssi_hw_params, |
820 | .set_fmt = fsl_ssi_set_dai_fmt, | ||
821 | .set_sysclk = fsl_ssi_set_dai_sysclk, | ||
616 | .shutdown = fsl_ssi_shutdown, | 822 | .shutdown = fsl_ssi_shutdown, |
617 | .trigger = fsl_ssi_trigger, | 823 | .trigger = fsl_ssi_trigger, |
618 | }; | 824 | }; |
@@ -723,13 +929,6 @@ static int fsl_ssi_probe(struct platform_device *pdev) | |||
723 | if (!of_device_is_available(np)) | 929 | if (!of_device_is_available(np)) |
724 | return -ENODEV; | 930 | return -ENODEV; |
725 | 931 | ||
726 | /* We only support the SSI in "I2S Slave" mode */ | ||
727 | sprop = of_get_property(np, "fsl,mode", NULL); | ||
728 | if (!sprop || strcmp(sprop, "i2s-slave")) { | ||
729 | dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop); | ||
730 | return -ENODEV; | ||
731 | } | ||
732 | |||
733 | /* The DAI name is the last part of the full name of the node. */ | 932 | /* The DAI name is the last part of the full name of the node. */ |
734 | p = strrchr(np->full_name, '/') + 1; | 933 | p = strrchr(np->full_name, '/') + 1; |
735 | ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p), | 934 | ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p), |
@@ -817,6 +1016,8 @@ static int fsl_ssi_probe(struct platform_device *pdev) | |||
817 | ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0); | 1016 | ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0); |
818 | } | 1017 | } |
819 | 1018 | ||
1019 | ssi_private->sysrate = 0; | ||
1020 | |||
820 | /* Initialize the the device_attribute structure */ | 1021 | /* Initialize the the device_attribute structure */ |
821 | dev_attr = &ssi_private->dev_attr; | 1022 | dev_attr = &ssi_private->dev_attr; |
822 | sysfs_attr_init(&dev_attr->attr); | 1023 | sysfs_attr_init(&dev_attr->attr); |
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h index e6b9a69e2a68..5ff9b1d5270d 100644 --- a/sound/soc/fsl/fsl_ssi.h +++ b/sound/soc/fsl/fsl_ssi.h | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * Author: Timur Tabi <timur@freescale.com> | 4 | * Author: Timur Tabi <timur@freescale.com> |
5 | * | 5 | * |
6 | * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed | 6 | * Copyright 2007-2013 Freescale Semiconductor, Inc. This file is licensed |
7 | * under the terms of the GNU General Public License version 2. This | 7 | * under the terms of the GNU General Public License version 2. This |
8 | * program is licensed "as is" without any warranty of any kind, whether | 8 | * program is licensed "as is" without any warranty of any kind, whether |
9 | * express or implied. | 9 | * express or implied. |
@@ -125,7 +125,11 @@ struct ccsr_ssi { | |||
125 | #define CCSR_SSI_SRCR_REFS 0x00000001 | 125 | #define CCSR_SSI_SRCR_REFS 0x00000001 |
126 | 126 | ||
127 | /* STCCR and SRCCR */ | 127 | /* STCCR and SRCCR */ |
128 | #define CCSR_SSI_SxCCR_DIV2_SHIFT 18 | ||
129 | #define CCSR_SSI_SxCCR_DIV2_MASK 0x00040000 | ||
128 | #define CCSR_SSI_SxCCR_DIV2 0x00040000 | 130 | #define CCSR_SSI_SxCCR_DIV2 0x00040000 |
131 | #define CCSR_SSI_SxCCR_PSR_SHIFT 17 | ||
132 | #define CCSR_SSI_SxCCR_PSR_MASK 0x00020000 | ||
129 | #define CCSR_SSI_SxCCR_PSR 0x00020000 | 133 | #define CCSR_SSI_SxCCR_PSR 0x00020000 |
130 | #define CCSR_SSI_SxCCR_WL_SHIFT 13 | 134 | #define CCSR_SSI_SxCCR_WL_SHIFT 13 |
131 | #define CCSR_SSI_SxCCR_WL_MASK 0x0001E000 | 135 | #define CCSR_SSI_SxCCR_WL_MASK 0x0001E000 |