Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ~$ cat ~/src/kernel/linux-sunxi/sound/soc/sunxi/sunxi-i2s.c
- /*
- * sunxi-i2s.c
- *
- * (c) 2015 Andrea Venturi <[email protected]>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- */
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/io.h>
- #include <linux/slab.h>
- #include <linux/mbus.h>
- #include <linux/delay.h>
- #include <linux/clk.h>
- #include <sound/pcm.h>
- #include <sound/pcm_params.h>
- #include <sound/soc.h>
- //#include <linux/platform_data/asoc-sunxi.h>
- #include <linux/of.h>
- #include <linux/of_platform.h>
- #include <linux/of_address.h>
- #include <sound/dmaengine_pcm.h>
- #include "sunxi.h"
- #define DRV_NAME "sunxi-i2s"
- #define SUNXI_I2S_FORMATS \
- (SNDRV_PCM_FMTBIT_S16_LE | \
- SNDRV_PCM_FMTBIT_S24_LE | \
- SNDRV_PCM_FMTBIT_S32_LE)
- #define SUNXI_SPDIF_FORMATS \
- (SNDRV_PCM_FMTBIT_S16_LE | \
- SNDRV_PCM_FMTBIT_S24_LE)
- // for suspend/resume feature
- static int regsave[8];
- // TODO: Initialize structure on probe, after register default configuration
- //static struct sunxi_i2s_info sunxi_iis;
- static struct sunxi_i2s_info sunxi_iis = {
- .slave = 0,
- .samp_fs = 48000,
- .samp_res = 24,
- .samp_format = 0,
- .ws_size = 32,
- .mclk_rate = 512,
- .lrc_pol = 0,
- .bclk_pol = 0,
- .pcm_datamode = 0,
- .pcm_sw = 0,
- .pcm_sync_period = 0,
- .pcm_sync_type = 0,
- .pcm_start_slot = 0,
- .pcm_lsb_first = 0,
- .pcm_ch_num = 1,
- };
- typedef struct __BCLK_SET_INF
- {
- __u8 bitpersamp; // bits per sample - Word Sizes
- __u8 clk_div; // clock division
- __u16 mult_fs; // multiplay of sample rate
- } __bclk_set_inf;
- typedef struct __MCLK_SET_INF
- {
- __u32 samp_rate; // sample rate
- __u16 mult_fs; // multiply of sample rate
- __u8 clk_div; // mpll division
- __u32 mclk; // select mpll, 24.576MHz/22.5792Mhz
- } __mclk_set_inf;
- static __bclk_set_inf BCLK_INF[] =
- {
- // 16bits per sample
- {16, 4, 128}, {16, 6, 192}, {16, 8, 256},
- {16, 12, 384}, {16, 16, 512},
- //24 bits per sample
- {24, 4, 192}, {24, 8, 384}, {24, 16, 768},
- //32 bits per sample
- {32, 2, 128}, {32, 4, 256}, {32, 6, 384},
- {32, 8, 512}, {32, 12, 768},
- //end flag
- {0xff, 0, 0},
- };
- static __mclk_set_inf MCLK_INF[] =
- {
- // 8k bitrate
- { 8000, 128, 24, 24576000}, { 8000, 192, 16, 24576000}, { 8000, 256, 12, 24576000},
- { 8000, 384, 8, 24576000}, { 8000, 512, 6, 24576000}, { 8000, 768, 4, 24576000},
- // 16k bitrate
- { 16000, 128, 12, 24576000}, { 16000, 192, 8, 24576000}, { 16000, 256, 6, 24576000},
- { 16000, 384, 4, 24576000}, { 16000, 768, 2, 24576000},
- // 32k bitrate
- { 32000, 128, 6, 24576000}, { 32000, 192, 4, 24576000}, { 32000, 384, 2, 24576000},
- { 32000, 768, 1, 24576000},
- // 64k bitrate
- { 64000, 192, 2, 24576000}, { 64000, 384, 1, 24576000},
- //128k bitrate
- {128000, 192, 1, 24576000},
- // 12k bitrate
- { 12000, 128, 16, 24576000}, { 12000, 256, 8, 24576000}, { 12000, 512, 4, 24576000},
- // 24k bitrate
- { 24000, 128, 8, 24576000}, { 24000, 256, 4, 24576000}, { 24000, 512, 2, 24576000},
- // 48K bitrate
- { 48000, 128, 4, 24576000}, { 48000, 256, 2, 24576000}, { 48000, 512, 1, 24576000},
- // 96k bitrate
- { 96000, 128 , 2, 24576000}, { 96000, 256, 1, 24576000},
- //192k bitrate
- {192000, 128, 1, 24576000},
- //11.025k bitrate
- { 11025, 128, 16, 22579200}, { 11205, 256, 8, 22579200}, { 11205, 512, 4, 22579200},
- //22.05k bitrate
- { 22050, 128, 8, 22579200}, { 22050, 256, 4, 22579200},
- { 22050, 512, 2, 22579200},
- //44.1k bitrate
- { 44100, 128, 4, 22579200}, { 44100, 256, 2, 22579200}, { 44100, 512, 1, 22579200},
- //88.2k bitrate
- { 88200, 128, 2, 22579200}, { 88200, 256, 1, 22579200},
- //176.4k bitrate
- {176400, 128, 1, 22579200},
- //end flag 0xffffffff
- {0xffffffff, 0, 0, 24576000},
- };
- /*
- * TODO: Function description.
- */
- //static s32 get_clock_divder(u32 sample_rate, u32 sample_width, u32 * mclk_div, u32* mpll, u32* bclk_div, u32* mult_fs)
- static s32 sunxi_i2s_divisor_values(u32 * mclk_div, u32* bclk_div, u32* mclk)
- {
- u32 i, j, ret = -EINVAL;
- printk("[I2S]Entered %s\n", __func__);
- for(i=0; i< ARRAY_SIZE(MCLK_INF); i++) {
- if((MCLK_INF[i].samp_rate == sunxi_iis.samp_fs) && ((MCLK_INF[i].mult_fs == 256) || (MCLK_INF[i].mult_fs == 128))) {
- for(j=0; j<ARRAY_SIZE(BCLK_INF); j++) {
- if((BCLK_INF[j].bitpersamp == sunxi_iis.ws_size) && (BCLK_INF[j].mult_fs == MCLK_INF[i].mult_fs)) {
- //set mclk and bclk division
- *mclk_div = MCLK_INF[i].clk_div;
- *mclk = MCLK_INF[i].mclk;
- *bclk_div = BCLK_INF[j].clk_div;
- sunxi_iis.mclk_rate = MCLK_INF[i].mult_fs;
- ret = 0;
- break;
- }
- }
- }
- else if(MCLK_INF[i].samp_rate == 0xffffffff)
- break;
- }
- return ret;
- }
- static int sunxi_i2s_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
- {
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sunxi_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- printk("[I2S]Entered %s\n", __func__);
- clk_prepare_enable(priv->clk_apb);
- clk_prepare_enable(priv->clk_module);
- return 0;
- }
- static void sunxi_i2s_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
- {
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sunxi_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- printk("[I2S]Entered %s\n", __func__);
- clk_disable_unprepare(priv->clk_module);
- clk_disable_unprepare(priv->clk_apb);
- }
- static void sunxi_i2s_capture_start(struct sunxi_priv *priv)
- {
- printk("[I2S]Entered %s\n", __func__);
- /* flush RXFIFO */
- regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FRX, 0x1 << SUNXI_DA_FCTL_FRX);
- /* clear RX counter */
- regmap_update_bits(priv->regmap, SUNXI_DA_RXCNT, 0xffff << SUNXI_DA_RXCNT_RX_CNT, 0x0 << SUNXI_DA_RXCNT_RX_CNT);
- /* enable DA_CTL RXEN */
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_RXEN, 0x1 << SUNXI_DA_CTL_RXEN);
- /* enable DA_INT RX_DRQ */
- regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_RX_DRQ, 0x1 << SUNXI_DA_INT_RX_DRQ);
- }
- static void sunxi_i2s_capture_stop(struct sunxi_priv *priv)
- {
- unsigned int rx_counter;
- /* disable DA RX_DRQ */
- regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_RX_DRQ, 0x0 << SUNXI_DA_INT_RX_DRQ);
- /* FIXME clear RX counter not doing, want to check */
- // regmap_update_bits(priv->regmap, SUNXI_DA_RXCNT, 0xffff << SUNXI_DA_RXCNT_RX_CNT, 0x0 << SUNXI_DA_RXCNT_RX_CNT);
- regmap_read(priv->regmap, SUNXI_DA_RXCNT, &rx_counter);
- printk("DEB: stop I2S rec: sample counter: %x\n", rx_counter);
- /* disable DA_CTL RXEN */
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_RXEN, 0x0 << SUNXI_DA_CTL_RXEN);
- /* flush RXFIFO */
- regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FRX, 0x0 << SUNXI_DA_FCTL_FRX);
- }
- static void sunxi_i2s_play_start(struct sunxi_priv *priv)
- {
- printk("[I2S]Entered %s\n", __func__);
- /* flush TX FIFO */
- regmap_update_bits(priv->regmap, SUNXI_DA_FCTL, 0x1 << SUNXI_DA_FCTL_FTX, 0x1 << SUNXI_DA_FCTL_FTX);
- /* clear TX counter */
- regmap_update_bits(priv->regmap, SUNXI_DA_TXCNT, 0xffff << SUNXI_DA_TXCNT_TX_CNT, 0x0 << SUNXI_DA_TXCNT_TX_CNT);
- /* enable DA_CTL TXEN */
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_TXEN, 0x1 << SUNXI_DA_CTL_TXEN);
- /* enable DA TX_DRQ */
- regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_TX_DRQ, 0x1 << SUNXI_DA_INT_TX_DRQ);
- }
- static void sunxi_i2s_play_stop(struct sunxi_priv *priv)
- {
- unsigned int tx_counter;
- /* TODO: see if we need to drive PA GPIO low */
- /* disable DA_CTL TXEN */
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_TXEN, 0x0 << SUNXI_DA_CTL_TXEN);
- /* disable DA TX_DRQ */
- regmap_update_bits(priv->regmap, SUNXI_DA_INT, 0x1 << SUNXI_DA_INT_TX_DRQ, 0x0 << SUNXI_DA_INT_TX_DRQ);
- regmap_read(priv->regmap, SUNXI_DA_TXCNT, &tx_counter);
- printk("DEB %s: sample counter: %x\n", __func__, tx_counter);
- }
- static int sunxi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
- {
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sunxi_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- printk("DEB: %s, copy from same fnt on i2s 3.4 legacy sunxi-i2s.c\n", __func__);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- sunxi_i2s_capture_start(priv);
- else
- sunxi_i2s_play_start(priv);
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- sunxi_i2s_capture_stop(priv);
- else
- sunxi_i2s_play_stop(priv);
- break;
- default:
- return -EINVAL;
- }
- return 0;
- }
- static int sunxi_i2s_init(struct sunxi_priv *priv)
- {
- //unsigned long value;
- //unsigned int reg_data;
- printk("[I2S]Entered %s\n", __func__);
- /*
- * was used for parsing FEX file, and, is suffesful:
- * - setting slave or master
- * - requesting GPIO of I2S ctrl
- * - registering platform driver
- */
- return 0;
- }
- /*
- * TODO: Function Description
- * Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
- * Function called internally. The Machine Driver doesn't need to call this function because it is called whenever sunxi_i2s_set_clkdiv is called.
- * The master clock in Allwinner SoM depends on the sampling frequency.
- */
- static int sunxi_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir)
- {
- //u32 reg_val;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- printk("[I2S]Entered %s\n", __func__);
- if(!sunxi_iis.slave)
- {
- switch(clk_id)
- {
- case SUNXI_SET_MCLK: // Set the master clock frequency.
- // TODO - Check if the master clock is needed when slave mode is selected.
- if (clk_set_rate(priv->clk_pll2, freq))
- {
- pr_err("Try to set the i2s_pll2clk failed!\n");
- return -EINVAL;
- }
- break;
- case SUNXI_MCLKO_EN: // Enables the master clock output
- if(dir == 1) // Enable
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x1 << SUNXI_DA_CLKD_MCLKO_EN);
- if(dir == 0) // Disable
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x0 << SUNXI_DA_CLKD_MCLKO_EN);
- break;
- }
- }
- return 0;
- }
- /*
- * TODO: Function Description
- * Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
- */
- static int sunxi_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int value)
- {
- u32 reg_bk, ret;
- u32 mclk = 0;
- u32 mclk_div = 0;
- u32 bclk_div = 0;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- // Here i should know the sample rate and the FS multiple.
- printk("[I2S]Entered %s\n", __func__);
- switch (div_id) {
- case SUNXI_DIV_MCLK: // Sets MCLKDIV
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0xf << SUNXI_DA_CLKD_MCLKDIV, (value & 0xf) << SUNXI_DA_CLKD_MCLKDIV);
- break;
- case SUNXI_DIV_BCLK: // Sets BCLKDIV
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x7 << SUNXI_DA_CLKD_BCLKDIV, (value & 0x7) << SUNXI_DA_CLKD_BCLKDIV);
- break;
- case SUNXI_SAMPLING_FREQ:
- if(!sunxi_iis.slave)
- {
- reg_bk = sunxi_iis.samp_fs;
- sunxi_iis.samp_fs = (u32)value;
- ret = sunxi_i2s_divisor_values(&mclk_div, &bclk_div, &mclk); // Get the register values
- if(ret != 0)
- {
- printk("[I2S]Sampling rate frequency not supported.");
- sunxi_iis.samp_fs = reg_bk;
- return ret;
- }
- else
- {
- sunxi_iis.samp_fs = (u32)value;
- sunxi_i2s_set_sysclk(cpu_dai, SUNXI_SET_MCLK, mclk, 0); // Set the master clock.
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, (0xf << SUNXI_DA_CLKD_MCLKDIV)|(0x7 << SUNXI_DA_CLKD_BCLKDIV),
- ((mclk_div & 0xf) << SUNXI_DA_CLKD_MCLKDIV)|((bclk_div & 0x7) << SUNXI_DA_CLKD_BCLKDIV));
- }
- }
- else
- sunxi_iis.samp_fs = (u32)value;
- break;
- }
- return 0;
- }
- /*
- * TODO: Function description.
- * TODO: Refactor function because the configuration is with wrong scheme. Use a 4bit mask with the configuration option and then the value?
- * TODO: Include TX and RX FIFO trigger levels.
- * Saved in snd_soc_dai_ops sunxi_iis_dai_ops.
- * Configure:
- * - Master/Slave.
- * - I2S/PCM mode.
- * - Signal Inversion.
- * - Word Select Size.
- * - PCM Registers.
- */
- static int sunxi_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
- {
- u32 reg_val1;
- u32 reg_val2;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- printk("[I2S]Entered %s, FMT: %x\n", __func__, fmt);
- // Master/Slave Definition
- //reg_val1 = readl(sunxi_iis.regs + SUNXI_IISCTL);
- regmap_read(priv->regmap, SUNXI_DA_CTL, ®_val1 );
- printk("[I2S] %s: reg DA_CTL: 0x%0x\n", __func__, reg_val1);
- switch(fmt & SND_SOC_DAIFMT_MASTER_MASK){
- case SND_SOC_DAIFMT_CBS_CFS: // clk & frm slave
- reg_val1 &= ~SUNXI_DA_CTL_MS; // 0: I2S Master!
- printk("[I2S] %s, Master, so codec Slave.\n", __func__);
- break;
- case SND_SOC_DAIFMT_CBM_CFM: // clk & frm master
- reg_val1 |= SUNXI_DA_CTL_MS; // 1: I2S Slave!
- printk("[I2S] %s, Slave, so codec Master.\n", __func__);
- break;
- default:
- printk("[I2S] %s: Master-Slave Select unknown mode: (fmt=%x)\n", __func__, fmt);
- return -EINVAL;
- }
- regmap_write(priv->regmap, SUNXI_DA_CTL, reg_val1 );
- //writel(reg_val1, sunxi_iis.regs + SUNXI_IISCTL);
- // I2S or PCM mode.
- regmap_read(priv->regmap, SUNXI_DA_CTL, ®_val1 );
- regmap_read(priv->regmap, SUNXI_DA_FAT0, ®_val2 );
- printk("[I2S] %s: reg DA_FAT0: 0x%0x\n", __func__, reg_val2);
- //reg_val1 = readl(sunxi_iis.regs + SUNXI_IISCTL);
- //reg_val2 = readl(sunxi_iis.regs + SUNXI_IISFAT0); // Register Name in User Manual V1.2: DA_FAT0 - Digital Audio Format Register 0
- switch(fmt & SND_SOC_DAIFMT_FORMAT_MASK)
- {
- case SND_SOC_DAIFMT_I2S: /* I2S mode */
- reg_val1 &= ~SUNXI_DA_CTL_PCM;
- reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
- reg_val2 |= SUNXI_DA_FAT0_FMT_STD; //
- printk("[I2S]sunxi_i2s_set_fmt: Set I2S mode\n");
- sunxi_iis.samp_format = SND_SOC_DAIFMT_I2S;
- break;
- case SND_SOC_DAIFMT_RIGHT_J: /* Right Justified mode */
- reg_val1 &= ~SUNXI_DA_CTL_PCM;
- reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
- reg_val2 |= SUNXI_DA_FAT0_FMT_RIGHT; //
- printk("[I2S]sunxi_i2s_set_fmt: Set Right Justified mode\n");
- sunxi_iis.samp_format = SND_SOC_DAIFMT_RIGHT_J;
- break;
- case SND_SOC_DAIFMT_LEFT_J: /* Left Justified mode */
- reg_val1 &= ~SUNXI_DA_CTL_PCM;
- reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
- reg_val2 |= SUNXI_DA_FAT0_FMT_LEFT; //
- printk("[I2S]sunxi_i2s_set_fmt: Set Left Justified mode\n");
- sunxi_iis.samp_format = SND_SOC_DAIFMT_LEFT_J;
- break;
- case SND_SOC_DAIFMT_DSP_A: /* L data msb after FRM LRC */
- reg_val1 &= ~SUNXI_DA_CTL_PCM;
- reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
- reg_val2 |= SUNXI_DA_FAT0_FMT_LEFT; //
- sunxi_iis.samp_format = SND_SOC_DAIFMT_DSP_A;
- printk("[I2S]sunxi_i2s_set_fmt: Set L data msb after FRM LRC mode\n");
- break;
- case SND_SOC_DAIFMT_DSP_B: /* L data msb during FRM LRC */
- reg_val1 |= SUNXI_DA_CTL_PCM;
- reg_val2 &= ~SUNXI_DA_FAT0_FMT(3); // Clear FMT (Bit 1:0)
- reg_val2 |= SUNXI_DA_FAT0_LRCP;
- sunxi_iis.samp_format = SND_SOC_DAIFMT_DSP_B;
- printk("[I2S]sunxi_i2s_set_fmt: Set L data msb during FRM LRC mode\n");
- break;
- default:
- printk("[I2S]sunxi_i2s_set_fmt: Unknown mode\n");
- return -EINVAL;
- }
- regmap_write(priv->regmap, SUNXI_DA_CTL, reg_val1 );
- regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val2 );
- //writel(reg_val1, sunxi_iis.regs + SUNXI_IISCTL);
- //writel(reg_val2, sunxi_iis.regs + SUNXI_IISFAT0);
- // Word select Size
- regmap_read(priv->regmap, SUNXI_DA_FAT0, ®_val1 );
- //reg_val1 = readl(sunxi_iis.regs + SUNXI_IISFAT0);
- switch(fmt & SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_MASK) // TODO: Refactor, wrong configuration scheme.
- {
- case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_16BCLK:
- reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
- reg_val1 |= SUNXI_DA_FAT0_WSS_16;
- sunxi_iis.ws_size = 16;
- printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 16.\n");
- break;
- case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_20BCLK:
- reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
- reg_val1 |= SUNXI_DA_FAT0_WSS_20;
- sunxi_iis.ws_size = 20;
- printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 20.\n");
- break;
- case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_24BCLK:
- reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
- reg_val1 |= SUNXI_DA_FAT0_WSS_24;
- sunxi_iis.ws_size = 24;
- printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 24.\n");
- break;
- case SND_SOC_DAIFMT_SUNXI_IISFAT0_WSS_32BCLK:
- reg_val1 &= ~SUNXI_DA_FAT0_WSS_32; /* clear word select size */
- reg_val1 |= SUNXI_DA_FAT0_WSS_32;
- sunxi_iis.ws_size = 32;
- printk("[I2S]sunxi_i2s_set_fmt: Set word select size = 32.\n");
- break;
- default:
- printk("[I2S]sunxi_i2s_set_fmt: Unknown mode.\n");
- break;
- }
- regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val1 );
- //writel(reg_val1, sunxi_iis.regs + SUNXI_IISFAT0);
- // Signal Inversion
- regmap_read(priv->regmap, SUNXI_DA_FAT0, ®_val1 );
- //reg_val1 = readl(sunxi_iis.regs + SUNXI_IISFAT0);
- switch(fmt & SND_SOC_DAIFMT_INV_MASK)
- {
- case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */
- reg_val1 &= ~SUNXI_DA_FAT0_LRCP;
- reg_val1 &= ~SUNXI_DA_FAT0_BCP;
- sunxi_iis.bclk_pol = 0;
- sunxi_iis.lrc_pol = 0;
- printk("[I2S]sunxi_i2s_set_fmt: Normal bit clock + frame\n");
- break;
- case SND_SOC_DAIFMT_NB_IF: /* normal bclk + inverted frame */
- reg_val1 |= SUNXI_DA_FAT0_LRCP;
- reg_val1 &= ~SUNXI_DA_FAT0_BCP;
- sunxi_iis.bclk_pol = 0;
- sunxi_iis.lrc_pol = 1;
- printk("[I2S]sunxi_i2s_set_fmt: Normal bclk + inverted frame\n");
- break;
- case SND_SOC_DAIFMT_IB_NF: /* inverted bclk + normal frame */
- reg_val1 &= ~SUNXI_DA_FAT0_LRCP;
- reg_val1 |= SUNXI_DA_FAT0_BCP;
- sunxi_iis.bclk_pol = 1;
- sunxi_iis.lrc_pol = 0;
- printk("[I2S]sunxi_i2s_set_fmt: Inverted bclk + normal frame\n");
- break;
- case SND_SOC_DAIFMT_IB_IF: /* inverted bclk + frame */
- reg_val1 |= SUNXI_DA_FAT0_LRCP;;
- reg_val1 |= SUNXI_DA_FAT0_BCP;
- sunxi_iis.bclk_pol = 1;
- sunxi_iis.lrc_pol = 1;
- printk("[I2S]sunxi_i2s_set_fmt: Inverted bclk + frame\n");
- break;
- default:
- printk("[I2S]sunxi_i2s_set_fmt: Unknown mode\n");
- return -EINVAL;
- }
- regmap_write(priv->regmap, SUNXI_DA_FAT0, reg_val1 );
- //writel(reg_val1, sunxi_iis.regs + SUNXI_IISFAT0);
- // sunxi_i2s_printk_register_values();
- return 0;
- }
- static int sunxi_i2s_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
- {
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct sunxi_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- //int is_mono = !!(params_channels(params) == 1);
- int is_24bit = !!(hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32);
- unsigned int rate = params_rate(params);
- unsigned int hwrate;
- printk("[I2S]Entered %s\n", __func__);
- switch (rate) {
- case 176400:
- case 88200:
- case 44100:
- case 33075:
- case 22050:
- case 14700:
- case 11025:
- case 7350:
- default:
- clk_set_rate(priv->clk_module, 22579200);
- break;
- case 192000:
- case 96000:
- case 48000:
- case 32000:
- case 24000:
- case 16000:
- case 12000:
- case 8000:
- clk_set_rate(priv->clk_module, 24576000);
- break;
- }
- switch (rate) {
- case 192000:
- case 176400:
- hwrate = 6;
- break;
- case 96000:
- case 88200:
- hwrate = 7;
- break;
- default:
- case 48000:
- case 44100:
- hwrate = 0;
- break;
- case 32000:
- case 33075:
- hwrate = 1;
- break;
- case 24000:
- case 22050:
- hwrate = 2;
- break;
- case 16000:
- case 14700:
- hwrate = 3;
- break;
- case 12000:
- case 11025:
- hwrate = 4;
- break;
- case 8000:
- case 7350:
- hwrate = 5;
- break;
- }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- printk("[I2S]sunxi_i2s_hw_params: SNDRV_PCM_STREAM_PLAYBACK.\n");
- switch (params_channels(params)) { // Enables the outputs and sets the map of the samples, on crescent order.
- // FIXME: always 2 channels, for draft
- default:
- printk("[I2S] %s: channels selected different then 2 but not implemented\n", __func__);
- case 2:
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_SDO0_EN|SUNXI_DA_CTL_SDO1_EN|SUNXI_DA_CTL_SDO2_EN|SUNXI_DA_CTL_SDO3_EN, SUNXI_DA_CTL_SDO0_EN);
- regmap_update_bits(priv->regmap, SUNXI_DA_TXCHSEL, 7, SUNXI_DA_TXCHSEL_CHNUM(2)); /* mask 3 lsbs */
- regmap_update_bits(priv->regmap, SUNXI_DA_TXCHMAP, 0x3f, SUNXI_DA_TXCHMAP_TX_CH(1)|SUNXI_DA_TXCHMAP_TX_CH(2)); //FIXME: ugly masks!
- //reg_val1 |= SUNXI_IISCTL_SDO0EN;
- //reg_val2 |= SUNXI_TXCHSEL_CHNUM(2); // TX Channel Select 2-ch.
- //reg_val3 |= ((0x0 << 0) | (0x1 << 4)); // TX Channel0 Mapping 1st sample, TX Channel1 Mapping 2nd sample.
- printk("[I2S]sunxi_i2s_hw_params: SDO0 enabled, 2 channels selected.\n");
- break;
- }
- if (is_24bit) // FIXME need supporting also 20 bit properly, here it's two bytes only
- priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- else
- priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- } else {
- printk("[I2S]sunxi_i2s_hw_params: SNDRV_PCM_STREAM_CAPTURE.\n");
- switch (params_channels(params)) { // Enables the outputs and sets the map of the samples, on crescent order.
- // FIXME: always 2 channels, for draft
- default:
- printk("[I2S]sunxi_i2s_hw_params: channels selected different then 2 but...\n");
- case 2:
- regmap_update_bits(priv->regmap, SUNXI_DA_RXCHSEL, 7, SUNXI_DA_RXCHSEL_CHNUM(2)); /* mask 3 lsbs */
- regmap_update_bits(priv->regmap, SUNXI_DA_RXCHMAP, 0x3f, SUNXI_DA_RXCHMAP_RX_CH(1)|SUNXI_DA_RXCHMAP_RX_CH(2)); //FIXME: ugly masks!
- printk("[I2S]sunxi_i2s_hw_params: SDO0 enabled, 2 channels selected.\n");
- break;
- }
- if (is_24bit) // FIXME need supporting also 20 bit properly, here it's two bytes only
- priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
- else
- priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- }
- // Sample Rate.
- if(sunxi_iis.slave == 0) // Only master has to configure the clock registers for sample rate setting.
- {
- sunxi_iis.samp_fs = params_rate(params);
- sunxi_i2s_set_clkdiv(dai, SUNXI_SAMPLING_FREQ, sunxi_iis.samp_fs);
- }
- return 0;
- }
- /*
- * TODO: Function Description.
- * Saved in snd_soc_dai_driver sunxi_iis_dai.
- */
- static int sunxi_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
- {
- //u32 reg_val;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- printk("[I2S]Entered %s\n", __func__);
- // I2S Default Register Configuration
- sunxi_iis.slave = 0, // put as default Master
- sunxi_iis.samp_fs = 48000,
- sunxi_iis.samp_res = 24,
- sunxi_iis.samp_format = SND_SOC_DAIFMT_I2S,
- sunxi_iis.ws_size = 32,
- sunxi_iis.mclk_rate = 512,
- sunxi_iis.lrc_pol = 0,
- sunxi_iis.bclk_pol = 0,
- sunxi_iis.pcm_datamode = 0,
- sunxi_iis.pcm_sw = 0,
- sunxi_iis.pcm_sync_period = 0,
- sunxi_iis.pcm_sync_type = 0,
- sunxi_iis.pcm_start_slot = 0,
- sunxi_iis.pcm_lsb_first = 0,
- sunxi_iis.pcm_ch_num = 2,
- // Digital Audio Register Default Values
- // DIGITAL AUDIO CONTROL REGISTER DEF
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, SUNXI_DA_CTL_GEN, SUNXI_DA_CTL_GEN);
- //reg_val = SUNXI_IISCTL_MS | SUNXI_IISCTL_GEN;
- //writel(reg_val, sunxi_iis.regs + SUNXI_IISCTL);
- // DIGITAL AUDIO FORMAT REGISTER 0
- regmap_write(priv->regmap, SUNXI_DA_FAT0, SUNXI_DA_FAT0_FMT_STD|SUNXI_DA_FAT0_SR_24|SUNXI_DA_FAT0_WSS_32);
- //reg_val = SUNXI_IISFAT0_FMT_I2S | SUNXI_IISFAT0_SR_24BIT | SUNXI_IISFAT0_WSS_32BCLK;
- //writel(reg_val, sunxi_iis.regs + SUNXI_IISFAT0);
- // FIFO control register. TODO: Understand how to optimize this parameter.
- //reg_val = (1<<0); // Expanding received sample sign bit at MSB of DA_RXFIFO register. TODO: Check if this configuration works.
- //reg_val |= (1<<2); // Valid data at the LSB of TXFIFO register
- //reg_val |= SUNXI_IISFCTL_RXTL(0xf); //RX FIFO trigger level - try to make it multiple of 8 to enable DMA burst of 8.
- //reg_val |= SUNXI_IISFCTL_TXTL(0x40); //TX FIFO empty trigger level - try to make it multiple of 8 to enable DMA burst of 8
- //writel(reg_val, sunxi_iis.regs + SUNXI_IISFCTL);
- regmap_write(priv->regmap, SUNXI_DA_FCTL, (1<<SUNXI_DA_FCTL_RXOM)|(1<<SUNXI_DA_FCTL_TXIM)|(0x0f<<SUNXI_DA_FCTL_RXTL)|(0x40<<SUNXI_DA_FCTL_TXTL));
- //enable MCLK output
- //reg_val = readl(sunxi_iis.regs + SUNXI_IISCLKD);
- //reg_val |= SUNXI_IISCLKD_MCLKOEN;
- //writel(reg_val, sunxi_iis.regs + SUNXI_IISCLKD);
- regmap_update_bits(priv->regmap, SUNXI_DA_CLKD, 0x1 << SUNXI_DA_CLKD_MCLKO_EN, 0x1 << SUNXI_DA_CLKD_MCLKO_EN);
- printk("[IIS-0] sunxi_i2s_set_clkdiv: enable MCLK\n");
- printk("[I2S]I2S default register configuration complete.\n");
- return 0;
- }
- /*
- * TODO: Function Description.
- * Saved in snd_soc_dai_driver sunxi_iis_dai.
- */
- static int sunxi_i2s_dai_remove(struct snd_soc_dai *dai)
- {
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(dai);
- printk("[I2S]Entered %s\n", __func__);
- // DIGITAL AUDIO CONTROL REGISTER
- regmap_write(priv->regmap, SUNXI_DA_CTL, 0);
- //writel(0, sunxi_iis.regs + SUNXI_IISCTL);
- return 0;
- }
- /*
- * TODO: Function description.
- */
- static void iisregsave(struct sunxi_priv *priv)
- {
- printk("[I2S]Entered %s\n", __func__);
- regmap_read(priv->regmap, SUNXI_DA_CTL, ®save[0]);
- regmap_read(priv->regmap, SUNXI_DA_FAT0, ®save[1]);
- regmap_read(priv->regmap, SUNXI_DA_FAT1, ®save[2]);
- regmap_read(priv->regmap, SUNXI_DA_FCTL, ®save[3]); //| (0x3<<24); // TODO: Bit 24- FRX - Write ‘1’ to flush RX FIFO, self clear to ‘0’. Really needed?
- regmap_read(priv->regmap, SUNXI_DA_INT, ®save[4]);
- regmap_read(priv->regmap, SUNXI_DA_CLKD, ®save[5]);
- regmap_read(priv->regmap, SUNXI_DA_TXCHSEL, ®save[6]);
- regmap_read(priv->regmap, SUNXI_DA_TXCHMAP, ®save[7]);
- }
- /*
- * TODO: Function description.
- */
- static void iisregrestore(struct sunxi_priv *priv)
- {
- printk("[I2S]Entered %s\n", __func__);
- regmap_write(priv->regmap, SUNXI_DA_CTL, regsave[0]);
- regmap_write(priv->regmap, SUNXI_DA_FAT0, regsave[1]);
- regmap_write(priv->regmap, SUNXI_DA_FAT1, regsave[2]);
- regmap_write(priv->regmap, SUNXI_DA_FCTL, regsave[3]);
- regmap_write(priv->regmap, SUNXI_DA_INT, regsave[4]);
- regmap_write(priv->regmap, SUNXI_DA_CLKD, regsave[5]);
- regmap_write(priv->regmap, SUNXI_DA_TXCHSEL, regsave[6]);
- regmap_write(priv->regmap, SUNXI_DA_TXCHMAP, regsave[7]);
- }
- /*
- * TODO: Function Description.
- * Saved in snd_soc_dai_driver sunxi_iis_dai.
- */
- static int sunxi_i2s_suspend(struct snd_soc_dai *cpu_dai)
- {
- //u32 reg_val;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- printk("[I2S]Entered %s\n", __func__);
- //Global Disable Digital Audio Interface
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_GEN, 0x0 << SUNXI_DA_CTL_GEN);
- //reg_val = readl(sunxi_iis.regs + SUNXI_IISCTL);
- //reg_val &= ~SUNXI_IISCTL_GEN;
- //writel(reg_val, sunxi_iis.regs + SUNXI_IISCTL);
- iisregsave(priv);
- if(!sunxi_iis.slave) {
- //release the module clock, only for master mode
- clk_disable(priv->clk_module);
- }
- clk_disable(priv->clk_apb);
- //printk("[I2S]PLL2 0x01c20008 = %#x\n", *(volatile int*)0xF1C20008);
- // printk("[I2S]SPECIAL CLK 0x01c20068 = %#x, line= %d\n", *(volatile int*)0xF1C20068, __LINE__);
- // printk("[I2S]SPECIAL CLK 0x01c200B8 = %#x, line = %d\n", *(volatile int*)0xF1C200B8, __LINE__);
- // TODO: Understand this printk!
- return 0;
- }
- /*
- * TODO: Function Description.
- * Saved in snd_soc_dai_driver sunxi_iis_dai.
- */
- static int sunxi_i2s_resume(struct snd_soc_dai *cpu_dai)
- {
- //u32 reg_val;
- struct sunxi_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
- printk("[I2S]Entered %s\n", __func__);
- //enable the module clock
- clk_enable(priv->clk_apb);
- if(!sunxi_iis.slave) {
- //enable the module clock
- clk_enable(priv->clk_module);
- }
- iisregrestore(priv);
- //Global Enable Digital Audio Interface
- regmap_update_bits(priv->regmap, SUNXI_DA_CTL, 0x1 << SUNXI_DA_CTL_GEN, 0x1 << SUNXI_DA_CTL_GEN);
- return 0;
- }
- static const struct regmap_config sunxi_i2s_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = SUNXI_DA_RXCHMAP,
- };
- static const struct snd_soc_component_driver sunxi_i2s_component = {
- .name = DRV_NAME,
- };
- static struct snd_soc_dai_ops sunxi_i2s_dai_ops = {
- .startup = sunxi_i2s_startup,
- .shutdown = sunxi_i2s_shutdown,
- .set_sysclk = sunxi_i2s_set_sysclk,
- .set_clkdiv = sunxi_i2s_set_clkdiv,
- .set_fmt = sunxi_i2s_set_fmt,
- .hw_params = sunxi_i2s_hw_params,
- .trigger = sunxi_i2s_trigger,
- };
- static struct snd_soc_dai_driver sunxi_i2s_dai[1] = {
- {
- .name = "sunxi-i2s-snd-soc-dai-driver",
- .probe = sunxi_i2s_dai_probe,
- //.remove = sunxi_i2s_dai_remove,
- //.suspend = sunxi_i2s_suspend,
- //.resume = sunxi_i2s_resume,
- .ops = &sunxi_i2s_dai_ops,
- .capture = {
- .stream_name = "pcm0c",
- // TODO: Support SNDRV_PCM_FMTBIT_S20_3LE and SNDRV_PCM_FMTBIT_S24_3LE.
- .formats = SUNXI_I2S_CAPTURE_FORMATS,
- .rates = SUNXI_I2S_RATES,
- .rate_min = SNDRV_PCM_RATE_8000,
- .rate_max = SNDRV_PCM_RATE_192000,
- .channels_min = 1,
- .channels_max = 2,
- },
- .playback = {
- .stream_name = "pcm0p",
- // TODO: Support SNDRV_PCM_FMTBIT_S20_3LE and SNDRV_PCM_FMTBIT_S24_3LE. Implies in changing the word select size in *_set_fmt.
- .formats = SUNXI_I2S_PLAYBACK_FORMATS,
- .rates = SUNXI_I2S_RATES,
- .rate_min = SNDRV_PCM_RATE_8000,
- .rate_max = SNDRV_PCM_RATE_192000,
- .channels_min = 1,
- .channels_max = 2,
- },
- .symmetric_rates = 1,
- },
- };
- struct snd_soc_platform_driver sunxi_soc_platform = {
- // this is from kirkwood where it was used for DMA ops, but here we use dmaengine..
- //.ops = &sunxi_dma_ops,
- //.pcm_new = kirkwood_dma_new,
- //.pcm_free = kirkwood_dma_free_dma_buffers,
- };
- #ifdef CONFIG_OF
- static const struct of_device_id sunxi_i2s_of_match[] = {
- { .compatible = "allwinner,sun7i-a20-i2s" },
- { }
- };
- MODULE_DEVICE_TABLE(of, sunxi_i2s_of_match);
- #endif
- static int sunxi_digitalaudio_probe(struct platform_device *pdev)
- {
- struct device_node *np = pdev->dev.of_node;
- struct snd_soc_dai_driver *soc_dai = sunxi_i2s_dai;
- struct sunxi_priv *priv;
- const struct of_device_id *of_id;
- struct device *dev = &pdev->dev;
- struct resource *res;
- void __iomem *base;
- // WRONG struct snd_soc_card *card = &sunxi_dev;
- int ret;
- printk("[I2S]Entered %s\n", __func__);
- //return -ENODEV; // AV test to check if driver is spitted out
- if (!of_device_is_available(np))
- return -ENODEV;
- of_id = of_match_device(sunxi_i2s_of_match, dev);
- if (!of_id)
- return -EINVAL;
- priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- printk("[AV]alloc ok %s\n", __func__);
- dev_set_drvdata(&pdev->dev, priv);
- dev_err(dev, "%s, [AV]dev err set priv into platform\n", __func__);
- priv->revision = (enum sunxi_soc_family)of_id->data;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(base))
- return PTR_ERR(base);
- priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
- &sunxi_i2s_regmap_config);
- if (IS_ERR(priv->regmap))
- return PTR_ERR(priv->regmap);
- /* Get the clocks from the DT */
- priv->clk_apb = devm_clk_get(dev, "apb");
- if (IS_ERR(priv->clk_apb)) {
- dev_err(dev, "failed to get apb clock\n");
- return PTR_ERR(priv->clk_apb);
- }
- priv->clk_module = devm_clk_get(dev, "iis");
- if (IS_ERR(priv->clk_module)) {
- dev_err(dev, "failed to get i2s clock\n");
- return PTR_ERR(priv->clk_module);
- }
- /* Enable the clock on a basic rate */
- ret = clk_set_rate(priv->clk_module, 24576000);
- if (ret) {
- dev_err(dev, "failed to set i2s base clock rate\n");
- return ret;
- }
- /* Enable the bus clock */
- if (clk_prepare_enable(priv->clk_apb)) {
- dev_err(dev, "failed to enable apb clock\n");
- clk_disable_unprepare(priv->clk_module);
- return -EINVAL;
- }
- dev_info(dev, "[AV] set clock and rate on i2s, %s\n", __func__);
- /* DMA configuration for TX FIFO */
- priv->playback_dma_data.addr = res->start + SUNXI_DA_TXFIFO;
- priv->playback_dma_data.maxburst = 4;
- priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- /* DMA configuration for RX FIFO */
- priv->capture_dma_data.addr = res->start + SUNXI_DA_RXFIFO;
- priv->capture_dma_data.maxburst = 4;
- priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
- if (ret) {
- dev_err(&pdev->dev, "snd_soc_register_dmaengine failed (%d)\n", ret);
- goto err_clk_disable;
- }
- ret = devm_snd_soc_register_component(&pdev->dev, &sunxi_i2s_component, soc_dai, 1);
- if (ret) {
- dev_err(&pdev->dev, "snd_soc_register_component failed (%d)\n", ret);
- goto err_clk_disable;
- }
- ret = snd_soc_register_platform(&pdev->dev, &sunxi_soc_platform);
- if (ret) {
- dev_err(&pdev->dev, "snd_soc_register_platform failed\n");
- goto err_platform;
- }
- sunxi_i2s_init(priv);
- return 0;
- err_platform:
- dev_info(&pdev->dev, "AV snd_soc_register_platform failed\n");
- snd_soc_unregister_component(&pdev->dev);
- err_clk_disable:
- dev_info(&pdev->dev, "AV snd_soc_register_* failed\n");
- if (!IS_ERR(priv->clk_module))
- clk_disable_unprepare(priv->clk_module);
- clk_disable_unprepare(priv->clk_apb);
- return ret;
- }
- static int sunxi_digitalaudio_remove(struct platform_device *pdev)
- {
- struct sunxi_priv *priv = dev_get_drvdata(&pdev->dev);
- snd_soc_unregister_platform(&pdev->dev);
- snd_soc_unregister_component(&pdev->dev);
- if (!IS_ERR(priv->clk_apb))
- clk_disable_unprepare(priv->clk_apb);
- if (!IS_ERR(priv->clk_module))
- clk_disable_unprepare(priv->clk_module);
- return 0;
- }
- static struct platform_driver sunxi_i2s_driver = {
- .probe = sunxi_digitalaudio_probe,
- .remove = sunxi_digitalaudio_remove,
- .driver = {
- .name = DRV_NAME,
- .of_match_table = of_match_ptr(sunxi_i2s_of_match),
- },
- };
- module_platform_driver(sunxi_i2s_driver);
- /* Module information */
- MODULE_DEVICE_TABLE(of, sunxi_i2s_of_match); // autoload from: https://lwn.net/Articles/448502/
- MODULE_DESCRIPTION("Sunxi I2S ASoC Interface");
- MODULE_LICENSE("GPL");
- MODULE_ALIAS("platform:sunxi-i2s");
- buildroot@DISTROBOX:~$
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement