WM8960 driver with SSI2 on Kernel 4.1.15

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

WM8960 driver with SSI2 on Kernel 4.1.15

Jump to solution
9,482 Views
tootzoe
Contributor III

Hi all,

   did sombody have successfully driven wm8960 with ssi bus on kernel 4.1.15 ?   can you share your imx-wm8960.c and wm8960.c , wm8960.h  files?

I found there are wm8960 source code including in kernel 4.1.15 bsp, but this source code using sai bus while our customer board connect the wm8960 with ssi interface, so far our board can probe wm8960 sound card , but can't make a sound, there is no single on ssi bus at all, i try to copy wm8962 source as example and adapt to wm8960, but very soon i found it need to understand lots  registers  of wm8960 , it is need much more time.

 so i come here to ask the community  if somebody have any ideas? 

any comments is appreciated.

Best regards

  

toot_hzf

Labels (1)
1 Solution
4,835 Views
tootzoe
Contributor III

well,  this is the registers' configurations that make wm8960 work,  i used the driver code comes frome kernel-4.1.15, just change sai to ssi.

rtn: 0x 0 = 0x13f
rtn: 0x 1 = 0x13f
rtn: 0x 2 = 0x165
rtn: 0x 3 = 0x165
rtn: 0x 4 = 0x 5
rtn: 0x 5 = 0x 0
rtn: 0x 6 = 0x 0
rtn: 0x 7 = 0x 42
rtn: 0x 8 = 0x1c4
rtn: 0x 9 = 0x 0
rtn: 0x a = 0x d6
rtn: 0x b = 0x d6
rtn: 0x10 = 0x 0
rtn: 0x11 = 0x 7b
rtn: 0x12 = 0x100
rtn: 0x13 = 0x 32
rtn: 0x14 = 0x 0
rtn: 0x15 = 0x c3
rtn: 0x16 = 0x c3
rtn: 0x17 = 0x1c0
rtn: 0x18 = 0x 0
rtn: 0x19 = 0x fc
rtn: 0x1a = 0x1fb
rtn: 0x1b = 0x 0
rtn: 0x1c = 0x 8
rtn: 0x1d = 0x 0
rtn: 0x20 = 0x108
rtn: 0x21 = 0x108
rtn: 0x22 = 0x100
rtn: 0x25 = 0x100
rtn: 0x26 = 0x 0
rtn: 0x27 = 0x 0
rtn: 0x28 = 0x165
rtn: 0x29 = 0x165
rtn: 0x2a = 0x 40
rtn: 0x2b = 0x 0
rtn: 0x2c = 0x 0
rtn: 0x2d = 0x 50
rtn: 0x2e = 0x 50
rtn: 0x2f = 0x 3c
rtn: 0x30 = 0x 2
rtn: 0x31 = 0x f7
rtn: 0x33 = 0x 9b
rtn: 0x34 = 0x 37
rtn: 0x35 = 0x 86
rtn: 0x36 = 0x c2
rtn: 0x37 = 0x 27

View solution in original post

0 Kudos
17 Replies
4,835 Views
turkersezgin
Contributor III

Hello everyone,

I have a custom board with imx6q CPU.

It uses wm8960 codec but I haven't voice.

Kernel version is 4.1.15. What can I do for voice?

Do you have any solutions for wm8960?

Thanks.

0 Kudos
4,835 Views
caritzhu
Contributor III

Hi Huang,

I was just porting the wm8960 on Linux 4.1.15, and modify the driver just like what you show. But still not success.

Whether you could send me the imx-wm8960.c and wm8960.c to my email addreess(dald@163.com)?

I don't know how to send file as attachement on this community.

Thank you very much

Carit

0 Kudos
4,835 Views
tootzoe
Contributor III

Carit ,

 the major change of source code is posted above, if still your wm8960 not work,  i think you should check you hardware, measure signal with oscilloscope.

toot_hzf

best regards!

0 Kudos
4,835 Views
caritzhu
Contributor III

Hi Huang,

    Thank you for your reply!

    I think the hardware is ok, because I have check the oscilloscope, it works well with 24MHz, and the audio port from imx6q could output signal as well. In the other hand, Android 4.4.3 with 3.10.35 kernel version has sound on the same board. But the two driver codes are quite different.

    Maybe our wm8960 hardware design is not suitable for the new driver. hehe.wm8960.png

        I don't know wthether it could be the problem.

Thank you

Carit

0 Kudos
4,835 Views
yoshiharuseirik
Contributor I

Hi Huang

I am having the same problem.
If you do not mind, can you tell me the specific source code changes?

regards
0 Kudos
4,835 Views
tootzoe
Contributor III

hi , yoshiharu,

 are you a japaness?

yes, i can tell you what modifies i have done with wm8960.c and  imx-wm8960.c   this two files.

0 Kudos
4,835 Views
yoshiharuseirik
Contributor I

Hi Huang
 
Thank you for your reply.
 
 > are you a japaness?
 
Yes.
 
> yes, i can tell you what modifies i have done with wm8960.c and  imx-wm8960.c   this two files.
 
Thank you for providing the information.
 
My method modified only imx-wm8960.c and confirmed that it works with a combination of wm8960 and imx6(Quad)-SSI on my environment.
I used "aplay" and "arecord" to check the operation.
 
The rough change contents are as follows.
・base code is imx-wm 8962.c.  
・description part of FLL is changed to PLL(WM8960_SYSCLK_AUTO).
・description part of "ADC - MONOMIX" commented out.
・character string used in the device tree is set to imx-wm8960.c
・"imx_wm896x_rates" is set to imx-wm 8960.c
・Initial setting of register is described in function imx_wm8960_late_probe()
 
best regards!

0 Kudos
4,834 Views
tootzoe
Contributor III

hi , yoshiharu

please compare to my code:

device tree:

sound {
            compatible = "fsl,imx-audio-wm8960";
        model = "wm8960-audio";
        cpu-dai = <&ssi2>;
        audio-codec = <&codec>;
        codec-master;
        audio-routing =
            "Headphone Jack", "HP_L",
            "Headphone Jack", "HP_R",
            "Ext Spk", "SPK_LP",
            "Ext Spk", "SPK_LN",
            "Ext Spk", "SPK_RP",
            "Ext Spk", "SPK_RN",
            "LINPUT1", "Main MIC" ;

        mux-int-port = <2>;
        mux-ext-port = <3>;
        hp-det = <2 0>;
        hp-det-gpios = <&gpio3 0 1>;    
        mic-det-gpios = <&gpio3 1 1>;   
    };

codec: wm8960@1a {
    compatible = "wlf,wm8960";
    reg = <0x1a>;
    clocks = <&clks IMX6QDL_CLK_CKO>;
    clock-names = "mclk";
    DCVDD-supply = <&reg_audio>;
    DBVDD-supply = <&reg_audio>;
    AVDD-supply = <&reg_audio>;
    CPVDD-supply = <&reg_audio>;
    MICVDD-supply = <&reg_audio>;
    PLLVDD-supply = <&reg_audio>;
    SPKVDD1-supply = <&reg_audio>;
    SPKVDD2-supply = <&reg_audio>;
    wlf,shared-lrclk;
    /* wlf,capless */
    amic-mono;
};

modifications to imx-wm8960.c  ( please note that my code is base original imx-wm8960.c  , NOT imx-wm8962.c )

static int imx_wm8960_probe(struct platform_device *pdev)
{
        struct device_node *np = pdev->dev.of_node;
    struct device_node *cpu_np, *codec_np = NULL;
    struct device_node *gpr_np ;
    struct platform_device *cpu_pdev;
    struct imx_priv *priv = &card_priv;
    struct i2c_client *codec_dev;
    struct imx_wm8960_data *data;
    struct platform_device *asrc_pdev = NULL;
    struct device_node *asrc_np;
    u32 width;
    int int_port, ext_port ;
    int ret;

    priv->pdev = pdev;

#if 1
    ret = of_property_read_u32(np, "mux-int-port", &int_port);
    if (ret) {
        dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
        return ret;
    }
    ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
    if (ret) {
        dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
        return ret;
    }

    /*
     * The port numbering in the hardware manual starts at 1, while
     * the audmux API expects it starts at 0.
     */
    int_port--;
    ext_port--;
    ret = imx_audmux_v2_configure_port(int_port,
            IMX_AUDMUX_V2_PTCR_SYN |
            IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
            IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
            IMX_AUDMUX_V2_PTCR_TFSDIR |
            IMX_AUDMUX_V2_PTCR_TCLKDIR,
            IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
    if (ret) {
        dev_err(&pdev->dev, "audmux internal port setup failed\n");
        return ret;
    }
    imx_audmux_v2_configure_port(ext_port,
            IMX_AUDMUX_V2_PTCR_SYN,
            IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
    if (ret) {
        dev_err(&pdev->dev, "audmux external port setup failed\n");
        return ret;
    }
#endif

    cpu_np = of_parse_phandle(np, "cpu-dai", 0);
    if (!cpu_np) {
        dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
        ret = -EINVAL;
        goto fail;
    }

    codec_np = of_parse_phandle(np, "audio-codec", 0);
    if (!codec_np) {
        dev_err(&pdev->dev, "phandle missing or invalid\n");
        ret = -EINVAL;
        goto fail;
    }
#if 0
    cpu_pdev = of_find_device_by_node(cpu_np);
    if (!cpu_pdev) {
        dev_err(&pdev->dev, "failed to find SAI platform device\n");
        ret = -EINVAL;
        goto fail;
    }
#else
    cpu_pdev = of_find_device_by_node(cpu_np);
    if (!cpu_pdev) {
        dev_err(&pdev->dev, "failed to find SSI platform device\n");
        ret = -EINVAL;
        goto fail;
    }
#endif
    codec_dev = of_find_i2c_device_by_node(codec_np);
    if (!codec_dev || !codec_dev->dev.driver) {
        dev_err(&pdev->dev, "failed to find codec platform device\n");
        ret = -EINVAL;
        goto fail;
    }

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data) {
        ret = -ENOMEM;
        goto fail;
    }

    if (of_property_read_bool(np, "codec-master"))
        data->is_codec_master = true;

    data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
    if (IS_ERR(data->codec_clk)) {
        ret = PTR_ERR(data->codec_clk);
        dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
        goto fail;
    }

    gpr_np = of_parse_phandle(np, "gpr", 0);
        if (gpr_np) {
        data->gpr = syscon_node_to_regmap(gpr_np);
        if (IS_ERR(data->gpr)) {
            ret = PTR_ERR(data->gpr);
            dev_err(&pdev->dev, "failed to get gpr regmap\n");
            goto fail;
        }

        /* set SAI2_MCLK_DIR to enable codec MCLK for imx7d */
        regmap_update_bits(data->gpr, 4, 1<<20, 1<<20);
    }

    of_property_read_u32_array(np, "hp-det", data->hp_det, 2);

    asrc_np = of_parse_phandle(np, "asrc-controller", 0);
    if (asrc_np) {
        asrc_pdev = of_find_device_by_node(asrc_np);
        priv->asrc_pdev = asrc_pdev;
    }

    data->card.dai_link = imx_wm8960_dai;

......

......

till here, i think your wm8960 is woking now, when you play a WAV file with aplay,   SSI bus should have correct single,

but the sound is very very small,  even can't be hear,

and last , in wm8960.c  ,  replace wm8960's registers with the values i posted above, 

you can use a delay work-queen to do that.

best regards!

4,834 Views
kcy15
Contributor I

I was just porting the wm8960 on android6.01 (Linux 4.1.15), and modify the driver just like what you show. and modify  this .

pastedImage_2.png

The clock is normal output, but 8960 output 1.41 M, can't hear any sound, please help me to see what's the problem

/*
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/

#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/control.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>
#include <linux/mfd/syscon.h>
#include "../codecs/wm8960.h"
#include "fsl_sai.h"
#include "imx-audmux.h"
struct imx_wm8960_data {
struct snd_soc_card card;
struct clk *codec_clk;
unsigned int clk_frequency;
bool is_codec_master;
bool is_stream_in_use[2];
bool is_stream_opened[2];
struct regmap *gpr;
unsigned int hp_det[2];
u32 asrc_rate;
u32 asrc_format;
};

struct imx_priv {
enum of_gpio_flags hp_active_low;
enum of_gpio_flags mic_active_low;
bool is_headset_jack;
struct snd_kcontrol *headphone_kctl;
struct platform_device *pdev;
struct platform_device *asrc_pdev;
struct snd_card *snd_card;
};

static struct imx_priv card_priv;

static struct snd_soc_jack imx_hp_jack;
static struct snd_soc_jack_pin imx_hp_jack_pin = {
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
};
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
.name = "headphone detect",
.report = SND_JACK_HEADPHONE,
.debounce_time = 250,
.invert = 0,
};

static struct snd_soc_jack imx_mic_jack;
static struct snd_soc_jack_pin imx_mic_jack_pins = {
.pin = "Mic Jack",
.mask = SND_JACK_MICROPHONE,
};
static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
.name = "mic detect",
.report = SND_JACK_MICROPHONE,
.debounce_time = 250,
.invert = 0,
};

static int hp_jack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct snd_soc_jack *jack = data;
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
int hp_status, ret;

hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);

if (hp_status != priv->hp_active_low) {
snd_soc_dapm_disable_pin(dapm, "Ext Spk");
if (priv->is_headset_jack) {
snd_soc_dapm_enable_pin(dapm, "Mic Jack");
snd_soc_dapm_disable_pin(dapm, "Main MIC");
}
ret = imx_hp_jack_gpio.report;
snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
} else {
snd_soc_dapm_enable_pin(dapm, "Ext Spk");
if (priv->is_headset_jack) {
snd_soc_dapm_disable_pin(dapm, "Mic Jack");
snd_soc_dapm_enable_pin(dapm, "Main MIC");
}
ret = 0;
snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
}

return ret;
}

static int mic_jack_status_check(void *data)
{
struct imx_priv *priv = &card_priv;
struct snd_soc_jack *jack = data;
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
int mic_status, ret;

mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);

if (mic_status != priv->mic_active_low) {
snd_soc_dapm_disable_pin(dapm, "Main MIC");
ret = imx_mic_jack_gpio.report;
} else {
snd_soc_dapm_enable_pin(dapm, "Main MIC");
ret = 0;
}

return ret;
}

static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_MIC("Main MIC", NULL),
};

static int imx_wm8960_jack_init(struct snd_soc_card *card,
struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin,
struct snd_soc_jack_gpio *gpio)
{
int ret;

ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1);
if (ret) {
return ret;
}

ret = snd_soc_jack_add_gpios(jack, 1, gpio);
if (ret)
return ret;

return 0;
}

static ssize_t show_headphone(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &card_priv;
int hp_status;

/* Check if headphone is plugged in */
hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);

if (hp_status != priv->hp_active_low)
strcpy(buf, "Headphone\n");
else
strcpy(buf, "Speaker\n");

return strlen(buf);
}

static ssize_t show_micphone(struct device_driver *dev, char *buf)
{
struct imx_priv *priv = &card_priv;
int mic_status;

/* Check if headphone is plugged in */
mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);

if (mic_status != priv->mic_active_low)
strcpy(buf, "Mic Jack\n");
else
strcpy(buf, "Main MIC\n");

return strlen(buf);
}
static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
static DRIVER_ATTR(micphone, S_IRUGO | S_IWUSR, show_micphone, NULL);

static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_card *card = rtd->card;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct device *dev = card->dev;
unsigned int sample_rate = params_rate(params);
unsigned int pll_out;
unsigned int fmt;
int ret = 0;

data->is_stream_in_use[tx] = true;

if (data->is_stream_in_use[!tx])
return 0;

if (data->is_codec_master)
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM;
else
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS;

/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
if (ret) {
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
return ret;
}
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
if (ret) {
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
return ret;
}

if (!data->is_codec_master) {
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
if (ret) {
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
return ret;
}

ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
if (ret) {
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
return ret;
}
return 0;
} else {
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
if (ret) {
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
return ret;
}
}

data->clk_frequency = clk_get_rate(data->codec_clk);

/* Set codec pll */
if (params_width(params) == 24)
pll_out = sample_rate * 768;
else
pll_out = sample_rate * 512;

ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out);
if (ret)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0);

return ret;
}

static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_card *card = rtd->card;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
struct device *dev = card->dev;
int ret;

data->is_stream_in_use[tx] = false;

if (data->is_codec_master && !data->is_stream_in_use[!tx]) {
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF);
if (ret)
dev_warn(dev, "failed to set codec dai fmt: %d\n", ret);
}

return 0;
}

static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 };
static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = {
.count = ARRAY_SIZE(imx_wm8960_rates),
.list = imx_wm8960_rates,
};

static int imx_hifi_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_card *card = rtd->card;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
//struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
int ret = 0;

//data->is_stream_opened[tx] = true;
//if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
// data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
// data->is_stream_opened[tx] = false;
// return -EBUSY;
//}

if (!data->is_codec_master) {
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints);
if (ret)
return ret;
}

ret = clk_prepare_enable(data->codec_clk);
if (ret) {
dev_err(card->dev, "Failed to enable MCLK: %d\n", ret);
return ret;
}

return ret;
}

static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_card *card = rtd->card;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;

clk_disable_unprepare(data->codec_clk);

data->is_stream_opened[tx] = false;
}

static struct snd_soc_ops imx_hifi_ops = {
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
.startup = imx_hifi_startup,
.shutdown = imx_hifi_shutdown,
};

static int imx_wm8960_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);

/*
* codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
* clock for ADCs and DACs
*/
snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6);

/* GPIO1 used as headphone detect output */
snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 3<<4);

/* Enable headphone jack detect */
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<6, 1<<6);
snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
snd_soc_update_bits(codec, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
snd_soc_update_bits(codec, WM8960_ADDCTL1, 3, 3);

return 0;
}

static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_soc_card *card = rtd->card;
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
struct imx_priv *priv = &card_priv;
struct snd_interval *rate;
struct snd_mask *mask;

if (!priv->asrc_pdev)
return -EINVAL;

rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
rate->max = rate->min = data->asrc_rate;

mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
snd_mask_none(mask);
snd_mask_set(mask, data->asrc_format);

return 0;
}

static struct snd_soc_dai_link imx_wm8960_dai[] = {
{
.name = "HiFi",
.stream_name = "HiFi",
.codec_dai_name = "wm8960-hifi",
.ops = &imx_hifi_ops,
},
{
.name = "HiFi-ASRC-FE",
.stream_name = "HiFi-ASRC-FE",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dynamic = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
{
.name = "HiFi-ASRC-BE",
.stream_name = "HiFi-ASRC-BE",
.codec_dai_name = "wm8960-hifi",
.platform_name = "snd-soc-dummy",
.no_pcm = 1,
.ignore_pmdown_time = 1,
.dpcm_playback = 1,
.dpcm_capture = 1,
.ops = &imx_hifi_ops,
.be_hw_params_fixup = be_hw_params_fixup,
},
};

static int imx_wm8960_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *cpu_np, *codec_np = NULL;
struct device_node *gpr_np ;
struct platform_device *cpu_pdev;
struct imx_priv *priv = &card_priv;
struct i2c_client *codec_dev;
struct imx_wm8960_data *data;
struct platform_device *asrc_pdev = NULL;
struct device_node *asrc_np;
u32 width;
int int_port, ext_port ;
int ret;

priv->pdev = pdev;

#if 1
ret = of_property_read_u32(np, "mux-int-port", &int_port);
if (ret) {
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
return ret;
}
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
if (ret) {
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
return ret;
}

/*
* The port numbering in the hardware manual starts at 1, while
* the audmux API expects it starts at 0.
*/
int_port--;
ext_port--;
ret = imx_audmux_v2_configure_port(int_port,
IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
if (ret) {
dev_err(&pdev->dev, "audmux internal port setup failed\n");
return ret;
}
imx_audmux_v2_configure_port(ext_port,
IMX_AUDMUX_V2_PTCR_SYN,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
if (ret) {
dev_err(&pdev->dev, "audmux external port setup failed\n");
return ret;
}
#endif

cpu_np = of_parse_phandle(np, "cpu-dai", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}

codec_np = of_parse_phandle(np, "audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}

cpu_pdev = of_find_device_by_node(cpu_np);
if (!cpu_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto fail;
}
codec_dev = of_find_i2c_device_by_node(codec_np);
if (!codec_dev || !codec_dev->dev.driver) {
dev_err(&pdev->dev, "failed to find codec platform device\n");
ret = -EINVAL;
goto fail;
}

data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto fail;
}

if (of_property_read_bool(np, "codec-master"))
data->is_codec_master = true;

data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
if (IS_ERR(data->codec_clk)) {
ret = PTR_ERR(data->codec_clk);
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
goto fail;
}

gpr_np = of_parse_phandle(np, "gpr", 0);
if (gpr_np) {
data->gpr = syscon_node_to_regmap(gpr_np);
if (IS_ERR(data->gpr)) {
ret = PTR_ERR(data->gpr);
dev_err(&pdev->dev, "failed to get gpr regmap\n");
goto fail;
}

/* set SAI2_MCLK_DIR to enable codec MCLK for imx7d */
regmap_update_bits(data->gpr, 4, 1<<20, 1<<20);
}

of_property_read_u32_array(np, "hp-det", data->hp_det, 2);

asrc_np = of_parse_phandle(np, "asrc-controller", 0);
if (asrc_np) {
asrc_pdev = of_find_device_by_node(asrc_np);
priv->asrc_pdev = asrc_pdev;
}

data->card.dai_link = imx_wm8960_dai;
imx_wm8960_dai[0].codec_of_node = codec_np;
imx_wm8960_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
imx_wm8960_dai[0].platform_of_node = cpu_np;

if (!asrc_pdev) {
data->card.num_links = 1;
} else {
imx_wm8960_dai[1].cpu_of_node = asrc_np;
imx_wm8960_dai[1].platform_of_node = asrc_np;
imx_wm8960_dai[2].codec_of_node = codec_np;
imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
data->card.num_links = 3;

ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
&data->asrc_rate);
if (ret) {
dev_err(&pdev->dev, "failed to get output rate\n");
ret = -EINVAL;
goto fail;
}

ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
if (ret) {
dev_err(&pdev->dev, "failed to get output rate\n");
ret = -EINVAL;
goto fail;
}

if (width == 24)
data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
else
data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
}

data->card.dev = &pdev->dev;
data->card.owner = THIS_MODULE;
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
goto fail;
data->card.dapm_widgets = imx_wm8960_dapm_widgets;
data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);

ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
if (ret)
goto fail;

data->card.late_probe = imx_wm8960_late_probe;

platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto fail;
}

priv->snd_card = data->card.snd_card;

imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
"hp-det-gpios", 0, &priv->hp_active_low);

imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
"mic-det-gpios", 0, &priv->mic_active_low);

if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
gpio_is_valid(imx_mic_jack_gpio.gpio) &&
imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
priv->is_headset_jack = true;

if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
priv->headphone_kctl = snd_kctl_jack_new("Headphone", 0, NULL);
ret = snd_ctl_add(priv->snd_card, priv->headphone_kctl);
if (ret)
dev_warn(&pdev->dev, "failed to create headphone jack kctl\n");

if (priv->is_headset_jack) {
imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
}
imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
imx_hp_jack_gpio.data = &imx_hp_jack;
ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
&imx_hp_jack_pin, &imx_hp_jack_gpio);
if (ret) {
dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
goto out;
}

ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
if (ret)
dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
}

if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
if (!priv->is_headset_jack) {
imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
imx_mic_jack_gpio.data = &imx_mic_jack;
ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
&imx_mic_jack_pins, &imx_mic_jack_gpio);
if (ret) {
dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
goto out;
}
}
ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
if (ret)
dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
}

out:
ret = 0;
fail:
if (cpu_np)
of_node_put(cpu_np);
if (codec_np)
of_node_put(codec_np);

return ret;
}

static int imx_wm8960_remove(struct platform_device *pdev)
{
driver_remove_file(pdev->dev.driver, &driver_attr_micphone);
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);

return 0;
}

static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

static struct platform_driver imx_wm8960_driver = {
.driver = {
.name = "imx-wm8960",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx-wm8960");

 

0 Kudos
4,834 Views
majinchao
Contributor I

Thank you very much for your post, solved my problem, but my voice is also relatively small

0 Kudos
4,834 Views
caritzhu
Contributor III

Hello, I think you need to check whether your Headphone Jack Dectect function is due to the WM8960 or iMX6 CPU? The original code of WM8960 codec(in the imx-wm8960.c) is used GPIO1 as the headphone jack dectect. If your board is detected by CPU itself, you need to delete relevant code for it.

0 Kudos
4,834 Views
yoshiharuseirik
Contributor I

Hi Huang

Thank you for explaining the code.
your code is simple and nice.

>but the sound is very very small,  even can't be hear,

Depending on the hardware, in my environment, there is no problem with the volume.
wm8960 has several volume registers.
Please see Fig13 in the data sheet of wm8960.

only Left...
LOUT1VOL ->WM8960_LOUT1(0x02) -> Headphone
LOUT2VOL  -> LM8960_LOUT2(0x28) -> Speaker
ACGAIN/DCGAIN -> WM8960_CLASSD3(0x33) ->Speaker
(no used OUT3 on your devicetree)
LDACVOL -> WM8960_LDAC(0x0a) (It is not shown in Fig.)

These registers can be changed by alsamixer.

LOUT1VOL -> Simple mixer control 'Headphone',0
LOUT2VOL -> Simple mixer control 'Speaker',0
ACGAIN -> Simple mixer control 'Speaker AC',0
ACGAIN -> Simple mixer control 'Speaker DC',0
LDACVOL -> Simple mixer control 'Playback',0

best regards!

0 Kudos
4,834 Views
tootzoe
Contributor III

hi , yoshiharu,

 so the wm8960 now work for you?

best regards!

0 Kudos
4,833 Views
yoshiharuseirik
Contributor I

Hi Huang,

It works without problems.

best regards!

0 Kudos
4,833 Views
Bio_TICFSL
NXP TechSupport
NXP TechSupport

Hi Huang

Here are the code and example usage for ssi1:

https://community.nxp.com/docs/DOC-106295 

regards

0 Kudos
4,836 Views
tootzoe
Contributor III

well,  this is the registers' configurations that make wm8960 work,  i used the driver code comes frome kernel-4.1.15, just change sai to ssi.

rtn: 0x 0 = 0x13f
rtn: 0x 1 = 0x13f
rtn: 0x 2 = 0x165
rtn: 0x 3 = 0x165
rtn: 0x 4 = 0x 5
rtn: 0x 5 = 0x 0
rtn: 0x 6 = 0x 0
rtn: 0x 7 = 0x 42
rtn: 0x 8 = 0x1c4
rtn: 0x 9 = 0x 0
rtn: 0x a = 0x d6
rtn: 0x b = 0x d6
rtn: 0x10 = 0x 0
rtn: 0x11 = 0x 7b
rtn: 0x12 = 0x100
rtn: 0x13 = 0x 32
rtn: 0x14 = 0x 0
rtn: 0x15 = 0x c3
rtn: 0x16 = 0x c3
rtn: 0x17 = 0x1c0
rtn: 0x18 = 0x 0
rtn: 0x19 = 0x fc
rtn: 0x1a = 0x1fb
rtn: 0x1b = 0x 0
rtn: 0x1c = 0x 8
rtn: 0x1d = 0x 0
rtn: 0x20 = 0x108
rtn: 0x21 = 0x108
rtn: 0x22 = 0x100
rtn: 0x25 = 0x100
rtn: 0x26 = 0x 0
rtn: 0x27 = 0x 0
rtn: 0x28 = 0x165
rtn: 0x29 = 0x165
rtn: 0x2a = 0x 40
rtn: 0x2b = 0x 0
rtn: 0x2c = 0x 0
rtn: 0x2d = 0x 50
rtn: 0x2e = 0x 50
rtn: 0x2f = 0x 3c
rtn: 0x30 = 0x 2
rtn: 0x31 = 0x f7
rtn: 0x33 = 0x 9b
rtn: 0x34 = 0x 37
rtn: 0x35 = 0x 86
rtn: 0x36 = 0x c2
rtn: 0x37 = 0x 27

0 Kudos
4,833 Views
tootzoe
Contributor III

hi bio_ticfsl ,

 Thank You for you guide, i will try you code and then post feedback here, thanks again~

best regards!

toot_hzf

0 Kudos