diff options
Diffstat (limited to 'sound/soc/bcm')
-rw-r--r-- | sound/soc/bcm/bcm2835-i2s.c | 391 | ||||
-rw-r--r-- | sound/soc/bcm/cygnus-ssp.c | 237 |
2 files changed, 346 insertions, 282 deletions
diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c index 6ba20498202e..2e449d7173fc 100644 --- a/sound/soc/bcm/bcm2835-i2s.c +++ b/sound/soc/bcm/bcm2835-i2s.c @@ -31,6 +31,7 @@ * General Public License for more details. */ +#include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> @@ -99,6 +100,8 @@ #define BCM2835_I2S_CHWID(v) (v) #define BCM2835_I2S_CH1(v) ((v) << 16) #define BCM2835_I2S_CH2(v) (v) +#define BCM2835_I2S_CH1_POS(v) BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(v)) +#define BCM2835_I2S_CH2_POS(v) BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(v)) #define BCM2835_I2S_TX_PANIC(v) ((v) << 24) #define BCM2835_I2S_RX_PANIC(v) ((v) << 16) @@ -110,12 +113,19 @@ #define BCM2835_I2S_INT_RXR BIT(1) #define BCM2835_I2S_INT_TXW BIT(0) +/* Frame length register is 10 bit, maximum length 1024 */ +#define BCM2835_I2S_MAX_FRAME_LENGTH 1024 + /* General device struct */ struct bcm2835_i2s_dev { struct device *dev; struct snd_dmaengine_dai_dma_data dma_data[2]; unsigned int fmt; - unsigned int bclk_ratio; + unsigned int tdm_slots; + unsigned int rx_mask; + unsigned int tx_mask; + unsigned int slot_width; + unsigned int frame_length; struct regmap *i2s_regmap; struct clk *clk; @@ -225,19 +235,120 @@ static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) { struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); - dev->bclk_ratio = ratio; + + if (!ratio) { + dev->tdm_slots = 0; + return 0; + } + + if (ratio > BCM2835_I2S_MAX_FRAME_LENGTH) + return -EINVAL; + + dev->tdm_slots = 2; + dev->rx_mask = 0x03; + dev->tx_mask = 0x03; + dev->slot_width = ratio / 2; + dev->frame_length = ratio; + return 0; } +static int bcm2835_i2s_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int width) +{ + struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (slots) { + if (slots < 0 || width < 0) + return -EINVAL; + + /* Limit masks to available slots */ + rx_mask &= GENMASK(slots - 1, 0); + tx_mask &= GENMASK(slots - 1, 0); + + /* + * The driver is limited to 2-channel setups. + * Check that exactly 2 bits are set in the masks. + */ + if (hweight_long((unsigned long) rx_mask) != 2 + || hweight_long((unsigned long) tx_mask) != 2) + return -EINVAL; + + if (slots * width > BCM2835_I2S_MAX_FRAME_LENGTH) + return -EINVAL; + } + + dev->tdm_slots = slots; + + dev->rx_mask = rx_mask; + dev->tx_mask = tx_mask; + dev->slot_width = width; + dev->frame_length = slots * width; + + return 0; +} + +/* + * Convert logical slot number into physical slot number. + * + * If odd_offset is 0 sequential number is identical to logical number. + * This is used for DSP modes with slot numbering 0 1 2 3 ... + * + * Otherwise odd_offset defines the physical offset for odd numbered + * slots. This is used for I2S and left/right justified modes to + * translate from logical slot numbers 0 1 2 3 ... into physical slot + * numbers 0 2 ... 3 4 ... + */ +static int bcm2835_i2s_convert_slot(unsigned int slot, unsigned int odd_offset) +{ + if (!odd_offset) + return slot; + + if (slot & 1) + return (slot >> 1) + odd_offset; + + return slot >> 1; +} + +/* + * Calculate channel position from mask and slot width. + * + * Mask must contain exactly 2 set bits. + * Lowest set bit is channel 1 position, highest set bit channel 2. + * The constant offset is added to both channel positions. + * + * If odd_offset is > 0 slot positions are translated to + * I2S-style TDM slot numbering ( 0 2 ... 3 4 ...) with odd + * logical slot numbers starting at physical slot odd_offset. + */ +static void bcm2835_i2s_calc_channel_pos( + unsigned int *ch1_pos, unsigned int *ch2_pos, + unsigned int mask, unsigned int width, + unsigned int bit_offset, unsigned int odd_offset) +{ + *ch1_pos = bcm2835_i2s_convert_slot((ffs(mask) - 1), odd_offset) + * width + bit_offset; + *ch2_pos = bcm2835_i2s_convert_slot((fls(mask) - 1), odd_offset) + * width + bit_offset; +} + static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); - unsigned int sampling_rate = params_rate(params); - unsigned int data_length, data_delay, bclk_ratio; - unsigned int ch1pos, ch2pos, mode, format; + unsigned int data_length, data_delay, framesync_length; + unsigned int slots, slot_width, odd_slot_offset; + int frame_length, bclk_rate; + unsigned int rx_mask, tx_mask; + unsigned int rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos; + unsigned int mode, format; + bool bit_clock_master = false; + bool frame_sync_master = false; + bool frame_start_falling_edge = false; uint32_t csreg; + int ret = 0; /* * If a stream is already enabled, @@ -248,42 +359,70 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON)) return 0; - /* - * Adjust the data length according to the format. - * We prefill the half frame length with an integer - * divider of 2400 as explained at the clock settings. - * Maybe it is overwritten there, if the Integer mode - * does not apply. - */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - data_length = 16; - break; - case SNDRV_PCM_FORMAT_S24_LE: - data_length = 24; + data_length = params_width(params); + data_delay = 0; + odd_slot_offset = 0; + mode = 0; + + if (dev->tdm_slots) { + slots = dev->tdm_slots; + slot_width = dev->slot_width; + frame_length = dev->frame_length; + rx_mask = dev->rx_mask; + tx_mask = dev->tx_mask; + bclk_rate = dev->frame_length * params_rate(params); + } else { + slots = 2; + slot_width = params_width(params); + rx_mask = 0x03; + tx_mask = 0x03; + + frame_length = snd_soc_params_to_frame_size(params); + if (frame_length < 0) + return frame_length; + + bclk_rate = snd_soc_params_to_bclk(params); + if (bclk_rate < 0) + return bclk_rate; + } + + /* Check if data fits into slots */ + if (data_length > slot_width) + return -EINVAL; + + /* Check if CPU is bit clock master */ + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + bit_clock_master = true; break; - case SNDRV_PCM_FORMAT_S32_LE: - data_length = 32; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBM_CFM: + bit_clock_master = false; break; default: return -EINVAL; } - /* If bclk_ratio already set, use that one. */ - if (dev->bclk_ratio) - bclk_ratio = dev->bclk_ratio; - else - /* otherwise calculate a fitting block ratio */ - bclk_ratio = 2 * data_length; - - /* Clock should only be set up here if CPU is clock master */ + /* Check if CPU is frame sync master */ switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBM_CFS: + frame_sync_master = true; + break; case SND_SOC_DAIFMT_CBS_CFM: - clk_set_rate(dev->clk, sampling_rate * bclk_ratio); + case SND_SOC_DAIFMT_CBM_CFM: + frame_sync_master = false; break; default: - break; + return -EINVAL; + } + + /* Clock should only be set up here if CPU is clock master */ + if (bit_clock_master) { + ret = clk_set_rate(dev->clk, bclk_rate); + if (ret) + return ret; } /* Setup the frame format */ @@ -294,43 +433,94 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, format |= BCM2835_I2S_CHWID((data_length-8)&0xf); + /* CH2 format is the same as for CH1 */ + format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format); + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - data_delay = 1; - break; - default: + /* I2S mode needs an even number of slots */ + if (slots & 1) + return -EINVAL; + /* - * TODO - * Others are possible but are not implemented at the moment. + * Use I2S-style logical slot numbering: even slots + * are in first half of frame, odd slots in second half. */ - dev_err(dev->dev, "%s:bad format\n", __func__); - return -EINVAL; - } + odd_slot_offset = slots >> 1; - ch1pos = data_delay; - ch2pos = bclk_ratio / 2 + data_delay; + /* MSB starts one cycle after frame start */ + data_delay = 1; - switch (params_channels(params)) { - case 2: - format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format); - format |= BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(ch1pos)); - format |= BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(ch2pos)); + /* Setup frame sync signal for 50% duty cycle */ + framesync_length = frame_length / 2; + frame_start_falling_edge = true; + break; + case SND_SOC_DAIFMT_LEFT_J: + if (slots & 1) + return -EINVAL; + + odd_slot_offset = slots >> 1; + data_delay = 0; + framesync_length = frame_length / 2; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_RIGHT_J: + if (slots & 1) + return -EINVAL; + + /* Odd frame lengths aren't supported */ + if (frame_length & 1) + return -EINVAL; + + odd_slot_offset = slots >> 1; + data_delay = slot_width - data_length; + framesync_length = frame_length / 2; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_DSP_A: + data_delay = 1; + framesync_length = 1; + frame_start_falling_edge = false; + break; + case SND_SOC_DAIFMT_DSP_B: + data_delay = 0; + framesync_length = 1; + frame_start_falling_edge = false; break; default: return -EINVAL; } + bcm2835_i2s_calc_channel_pos(&rx_ch1_pos, &rx_ch2_pos, + rx_mask, slot_width, data_delay, odd_slot_offset); + bcm2835_i2s_calc_channel_pos(&tx_ch1_pos, &tx_ch2_pos, + tx_mask, slot_width, data_delay, odd_slot_offset); + + /* + * Transmitting data immediately after frame start, eg + * in left-justified or DSP mode A, only works stable + * if bcm2835 is the frame clock master. + */ + if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_master) + dev_warn(dev->dev, + "Unstable slave config detected, L/R may be swapped"); + /* * Set format for both streams. * We cannot set another frame length * (and therefore word length) anyway, * so the format will be the same. */ - regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, format); - regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, format); + regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, + format + | BCM2835_I2S_CH1_POS(rx_ch1_pos) + | BCM2835_I2S_CH2_POS(rx_ch2_pos)); + regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, + format + | BCM2835_I2S_CH1_POS(tx_ch1_pos) + | BCM2835_I2S_CH2_POS(tx_ch2_pos)); /* Setup the I2S mode */ - mode = 0; if (data_length <= 16) { /* @@ -342,65 +532,41 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP; } - mode |= BCM2835_I2S_FLEN(bclk_ratio - 1); - mode |= BCM2835_I2S_FSLEN(bclk_ratio / 2); + mode |= BCM2835_I2S_FLEN(frame_length - 1); + mode |= BCM2835_I2S_FSLEN(framesync_length); - /* Master or slave? */ - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - /* CPU is master */ - break; - case SND_SOC_DAIFMT_CBM_CFS: - /* - * CODEC is bit clock master - * CPU is frame master - */ + /* CLKM selects bcm2835 clock slave mode */ + if (!bit_clock_master) mode |= BCM2835_I2S_CLKM; - break; - case SND_SOC_DAIFMT_CBS_CFM: - /* - * CODEC is frame master - * CPU is bit clock master - */ + + /* FSM selects bcm2835 frame sync slave mode */ + if (!frame_sync_master) mode |= BCM2835_I2S_FSM; + + /* CLKI selects normal clocking mode, sampling on rising edge */ + switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + mode |= BCM2835_I2S_CLKI; break; - case SND_SOC_DAIFMT_CBM_CFM: - /* CODEC is master */ - mode |= BCM2835_I2S_CLKM; - mode |= BCM2835_I2S_FSM; + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: break; default: - dev_err(dev->dev, "%s:bad master\n", __func__); return -EINVAL; } - /* - * Invert clocks? - * - * The BCM approach seems to be inverted to the classical I2S approach. - */ + /* FSI selects frame start on falling edge */ switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: - /* None. Therefore, both for BCM */ - mode |= BCM2835_I2S_CLKI; - mode |= BCM2835_I2S_FSI; - break; - case SND_SOC_DAIFMT_IB_IF: - /* Both. Therefore, none for BCM */ + case SND_SOC_DAIFMT_IB_NF: + if (frame_start_falling_edge) + mode |= BCM2835_I2S_FSI; break; case SND_SOC_DAIFMT_NB_IF: - /* - * Invert only frame sync. Therefore, - * invert only bit clock for BCM - */ - mode |= BCM2835_I2S_CLKI; - break; - case SND_SOC_DAIFMT_IB_NF: - /* - * Invert only bit clock. Therefore, - * invert only frame sync for BCM - */ - mode |= BCM2835_I2S_FSI; + case SND_SOC_DAIFMT_IB_IF: + if (!frame_start_falling_edge) + mode |= BCM2835_I2S_FSI; break; default: return -EINVAL; @@ -423,7 +589,27 @@ static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream, /* Clear FIFOs */ bcm2835_i2s_clear_fifos(dev, true, true); - return 0; + dev_dbg(dev->dev, + "slots: %d width: %d rx mask: 0x%02x tx_mask: 0x%02x\n", + slots, slot_width, rx_mask, tx_mask); + + dev_dbg(dev->dev, "frame len: %d sync len: %d data len: %d\n", + frame_length, framesync_length, data_length); + + dev_dbg(dev->dev, "rx pos: %d,%d tx pos: %d,%d\n", + rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos); + + dev_dbg(dev->dev, "sampling rate: %d bclk rate: %d\n", + params_rate(params), bclk_rate); + + dev_dbg(dev->dev, "CLKM: %d CLKI: %d FSM: %d FSI: %d frame start: %s edge\n", + !!(mode & BCM2835_I2S_CLKM), + !!(mode & BCM2835_I2S_CLKI), + !!(mode & BCM2835_I2S_FSM), + !!(mode & BCM2835_I2S_FSI), + (mode & BCM2835_I2S_FSI) ? "falling" : "rising"); + + return ret; } static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream, @@ -559,6 +745,7 @@ static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = { .hw_params = bcm2835_i2s_hw_params, .set_fmt = bcm2835_i2s_set_dai_fmt, .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio, + .set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot, }; static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai) @@ -578,7 +765,9 @@ static struct snd_soc_dai_driver bcm2835_i2s_dai = { .playback = { .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE @@ -586,13 +775,16 @@ static struct snd_soc_dai_driver bcm2835_i2s_dai = { .capture = { .channels_min = 2, .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE }, .ops = &bcm2835_i2s_dai_ops, - .symmetric_rates = 1 + .symmetric_rates = 1, + .symmetric_samplebits = 1, }; static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg) @@ -699,9 +891,6 @@ static int bcm2835_i2s_probe(struct platform_device *pdev) dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].flags = SND_DMAENGINE_PCM_DAI_FLAG_PACK; - /* BCLK ratio - use default */ - dev->bclk_ratio = 0; - /* Store the pdev */ dev->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, dev); diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c index e710bb0c5637..15c438f0f22d 100644 --- a/sound/soc/bcm/cygnus-ssp.c +++ b/sound/soc/bcm/cygnus-ssp.c @@ -27,12 +27,6 @@ #define DEFAULT_VCO 1354750204 -#define CYGNUS_TDM_RATE \ - (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ - SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | \ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_48000) - #define CAPTURE_FCI_ID_BASE 0x180 #define CYGNUS_SSP_TRISTATE_MASK 0x001fff #define CYGNUS_PLLCLKSEL_MASK 0xf @@ -234,152 +228,20 @@ static const struct pll_macro_entry pll_predef_mclk[] = { {98304000, 2}, }; +#define CYGNUS_RATE_MIN 8000 +#define CYGNUS_RATE_MAX 384000 + /* List of valid frame sizes for tdm mode */ static const int ssp_valid_tdm_framesize[] = {32, 64, 128, 256, 512}; -/* - * Use this relationship to derive the sampling rate (lrclk) - * lrclk = (mclk) / ((2*mclk_to_sclk_ratio) * (32 * SCLK))). - * - * Use mclk and pll_ch from the table above - * - * Valid SCLK = 0/1/2/4/8/12 - * - * mclk_to_sclk_ratio = number of MCLK per SCLK. Division is twice the - * value programmed in this field. - * Valid mclk_to_sclk_ratio = 1 through to 15 - * - * eg: To set lrclk = 48khz, set mclk = 12288000, mclk_to_sclk_ratio = 2, - * SCLK = 64 - */ -struct _ssp_clk_coeff { - u32 mclk; - u32 sclk_rate; - u32 rate; - u32 mclk_rate; +static const unsigned int cygnus_rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 88200, 96000, 176400, 192000, 352800, 384000 }; -static const struct _ssp_clk_coeff ssp_clk_coeff[] = { - { 4096000, 32, 16000, 4}, - { 4096000, 32, 32000, 2}, - { 4096000, 64, 8000, 4}, - { 4096000, 64, 16000, 2}, - { 4096000, 64, 32000, 1}, - { 4096000, 128, 8000, 2}, - { 4096000, 128, 16000, 1}, - { 4096000, 256, 8000, 1}, - - { 6144000, 32, 16000, 6}, - { 6144000, 32, 32000, 3}, - { 6144000, 32, 48000, 2}, - { 6144000, 32, 96000, 1}, - { 6144000, 64, 8000, 6}, - { 6144000, 64, 16000, 3}, - { 6144000, 64, 48000, 1}, - { 6144000, 128, 8000, 3}, - - { 8192000, 32, 32000, 4}, - { 8192000, 64, 16000, 4}, - { 8192000, 64, 32000, 2}, - { 8192000, 128, 8000, 4}, - { 8192000, 128, 16000, 2}, - { 8192000, 128, 32000, 1}, - { 8192000, 256, 8000, 2}, - { 8192000, 256, 16000, 1}, - { 8192000, 512, 8000, 1}, - - {12288000, 32, 32000, 6}, - {12288000, 32, 48000, 4}, - {12288000, 32, 96000, 2}, - {12288000, 32, 192000, 1}, - {12288000, 64, 16000, 6}, - {12288000, 64, 32000, 3}, - {12288000, 64, 48000, 2}, - {12288000, 64, 96000, 1}, - {12288000, 128, 8000, 6}, - {12288000, 128, 16000, 3}, - {12288000, 128, 48000, 1}, - {12288000, 256, 8000, 3}, - - {16384000, 64, 32000, 4}, - {16384000, 128, 16000, 4}, - {16384000, 128, 32000, 2}, - {16384000, 256, 8000, 4}, - {16384000, 256, 16000, 2}, - {16384000, 256, 32000, 1}, - {16384000, 512, 8000, 2}, - {16384000, 512, 16000, 1}, - - {24576000, 32, 96000, 4}, - {24576000, 32, 192000, 2}, - {24576000, 64, 32000, 6}, - {24576000, 64, 48000, 4}, - {24576000, 64, 96000, 2}, - {24576000, 64, 192000, 1}, - {24576000, 128, 16000, 6}, - {24576000, 128, 32000, 3}, - {24576000, 128, 48000, 2}, - {24576000, 256, 8000, 6}, - {24576000, 256, 16000, 3}, - {24576000, 256, 48000, 1}, - {24576000, 512, 8000, 3}, - - {49152000, 32, 192000, 4}, - {49152000, 64, 96000, 4}, - {49152000, 64, 192000, 2}, - {49152000, 128, 32000, 6}, - {49152000, 128, 48000, 4}, - {49152000, 128, 96000, 2}, - {49152000, 128, 192000, 1}, - {49152000, 256, 16000, 6}, - {49152000, 256, 32000, 3}, - {49152000, 256, 48000, 2}, - {49152000, 256, 96000, 1}, - {49152000, 512, 8000, 6}, - {49152000, 512, 16000, 3}, - {49152000, 512, 48000, 1}, - - { 5644800, 32, 22050, 4}, - { 5644800, 32, 44100, 2}, - { 5644800, 32, 88200, 1}, - { 5644800, 64, 11025, 4}, - { 5644800, 64, 22050, 2}, - { 5644800, 64, 44100, 1}, - - {11289600, 32, 44100, 4}, - {11289600, 32, 88200, 2}, - {11289600, 32, 176400, 1}, - {11289600, 64, 22050, 4}, - {11289600, 64, 44100, 2}, - {11289600, 64, 88200, 1}, - {11289600, 128, 11025, 4}, - {11289600, 128, 22050, 2}, - {11289600, 128, 44100, 1}, - - {22579200, 32, 88200, 4}, - {22579200, 32, 176400, 2}, - {22579200, 64, 44100, 4}, - {22579200, 64, 88200, 2}, - {22579200, 64, 176400, 1}, - {22579200, 128, 22050, 4}, - {22579200, 128, 44100, 2}, - {22579200, 128, 88200, 1}, - {22579200, 256, 11025, 4}, - {22579200, 256, 22050, 2}, - {22579200, 256, 44100, 1}, - - {45158400, 32, 176400, 4}, - {45158400, 64, 88200, 4}, - {45158400, 64, 176400, 2}, - {45158400, 128, 44100, 4}, - {45158400, 128, 88200, 2}, - {45158400, 128, 176400, 1}, - {45158400, 256, 22050, 4}, - {45158400, 256, 44100, 2}, - {45158400, 256, 88200, 1}, - {45158400, 512, 11025, 4}, - {45158400, 512, 22050, 2}, - {45158400, 512, 44100, 1}, +static const struct snd_pcm_hw_constraint_list cygnus_rate_constraint = { + .count = ARRAY_SIZE(cygnus_rates), + .list = cygnus_rates, }; static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai) @@ -679,40 +541,55 @@ static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk, return p_entry->pll_ch_num; } -static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio, - struct cygnus_audio *cygaud) +static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio) { - u32 value, i = 0; + u32 value; u32 mask = 0xf; u32 sclk; - bool found = false; - const struct _ssp_clk_coeff *p_entry = NULL; + u32 mclk_rate; + unsigned int bit_rate; + unsigned int ratio; - for (i = 0; i < ARRAY_SIZE(ssp_clk_coeff); i++) { - p_entry = &ssp_clk_coeff[i]; - if ((p_entry->rate == aio->lrclk) && - (p_entry->sclk_rate == aio->bit_per_frame) && - (p_entry->mclk == aio->mclk)) { - found = true; - break; - } - } - if (!found) { + bit_rate = aio->bit_per_frame * aio->lrclk; + + /* + * Check if the bit clock can be generated from the given MCLK. + * MCLK must be a perfect multiple of bit clock and must be one of the + * following values... (2,4,6,8,10,12,14) + */ + if ((aio->mclk % bit_rate) != 0) + return -EINVAL; + + ratio = aio->mclk / bit_rate; + switch (ratio) { + case 2: + case 4: + case 6: + case 8: + case 10: + case 12: + case 14: + mclk_rate = ratio / 2; + break; + + default: dev_err(aio->cygaud->dev, - "No valid match found in ssp_clk_coeff array\n"); + "Invalid combination of MCLK and BCLK\n"); dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n", aio->lrclk, aio->bit_per_frame, aio->mclk); return -EINVAL; } - sclk = aio->bit_per_frame; - if (sclk == 512) - sclk = 0; - /* sclks_per_1fs_div = sclk cycles/32 */ - sclk /= 32; /* Set sclk rate */ switch (aio->port_type) { case PORT_TDM: + sclk = aio->bit_per_frame; + if (sclk == 512) + sclk = 0; + + /* sclks_per_1fs_div = sclk cycles/32 */ + sclk /= 32; + /* Set number of bitclks per frame */ value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32); @@ -731,7 +608,7 @@ static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio, /* Set MCLK_RATE ssp port (spdif and ssp are the same) */ value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT); - value |= (p_entry->mclk_rate << I2S_OUT_MCLKRATE_SHIFT); + value |= (mclk_rate << I2S_OUT_MCLKRATE_SHIFT); writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value); @@ -745,7 +622,6 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); - struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); int rate, bitres; u32 value; u32 mask = 0x1f; @@ -841,7 +717,7 @@ static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, aio->lrclk = rate; if (!aio->is_slave) - ret = cygnus_ssp_set_clocks(aio, cygaud); + ret = cygnus_ssp_set_clocks(aio); return ret; } @@ -888,6 +764,11 @@ static int cygnus_ssp_startup(struct snd_pcm_substream *substream, else aio->clk_trace.cap_en = true; + substream->runtime->hw.rate_min = CYGNUS_RATE_MIN; + substream->runtime->hw.rate_max = CYGNUS_RATE_MAX; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cygnus_rate_constraint); return 0; } @@ -1261,9 +1142,7 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { .playback = { \ .channels_min = 1, \ .channels_max = 16, \ - .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ - SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ - SNDRV_PCM_RATE_192000, \ + .rates = SNDRV_PCM_RATE_KNOT, \ .formats = SNDRV_PCM_FMTBIT_S8 | \ SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ @@ -1271,9 +1150,7 @@ static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { .capture = { \ .channels_min = 2, \ .channels_max = 16, \ - .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ - SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ - SNDRV_PCM_RATE_192000, \ + .rates = SNDRV_PCM_RATE_KNOT, \ .formats = SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S32_LE, \ }, \ @@ -1288,14 +1165,12 @@ static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = { INIT_CPU_DAI(2), }; -static struct snd_soc_dai_driver cygnus_spdif_dai_info = { +static const struct snd_soc_dai_driver cygnus_spdif_dai_info = { .name = "cygnus-spdif", .playback = { .channels_min = 2, .channels_max = 2, - .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | - SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | - SNDRV_PCM_RATE_192000, + .rates = SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, }, |