| /* |
| * sound/soc/amlogic/meson/meson.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| */ |
| #undef pr_fmt |
| #define pr_fmt(fmt) "snd_card_meson: " fmt |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/clk.h> |
| #include <linux/timer.h> |
| #include <linux/workqueue.h> |
| #include <linux/interrupt.h> |
| #include <linux/platform_device.h> |
| #include <linux/i2c.h> |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| /* #include <sound/soc-dapm.h> */ |
| #include <sound/jack.h> |
| |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| #include <linux/switch.h> |
| #include <linux/amlogic/jtag.h> |
| #endif |
| /* #include <linux/amlogic/saradc.h> */ |
| #include <linux/amlogic/iomap.h> |
| |
| #include "i2s.h" |
| #include "meson.h" |
| #include "audio_hw.h" |
| #include <linux/amlogic/media/sound/audin_regs.h> |
| #include <linux/of.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/amlogic/aml_gpio_consumer.h> |
| #include <linux/of_gpio.h> |
| #include <linux/io.h> |
| |
| |
| #define DRV_NAME "aml_meson_snd_card" |
| |
| #define I2S_PLAY_BUF_SIZE 512 |
| #define I2S_PLAY_BUF_EXT_SIZE 64 |
| |
| static void aml_i2s_play(struct aml_audio_private_data *p_aml_audio) |
| { |
| struct snd_soc_card *card = (struct snd_soc_card *)p_aml_audio->data; |
| size_t size = I2S_PLAY_BUF_SIZE+I2S_PLAY_BUF_EXT_SIZE; |
| |
| audio_util_set_i2s_format(AUDIO_ALGOUT_DAC_FORMAT_DSP); |
| #ifdef CONFIG_AMLOGIC_SND_SPLIT_MODE |
| audio_set_i2s_mode(AIU_I2S_MODE_PCM16, 2); |
| #else |
| audio_set_i2s_mode(AIU_I2S_MODE_PCM16); |
| #endif |
| p_aml_audio->area = dmam_alloc_coherent(card->dev, |
| size, &p_aml_audio->addr, GFP_KERNEL); |
| memset(p_aml_audio->area, 0, size); |
| audio_set_aiubuf(p_aml_audio->addr, I2S_PLAY_BUF_SIZE, |
| 2, SNDRV_PCM_FORMAT_S16_LE); |
| audio_out_i2s_enable(1); |
| } |
| |
| static void aml_i2s_stop(struct aml_audio_private_data *p_aml_audio) |
| { |
| struct snd_soc_card *card = (struct snd_soc_card *)p_aml_audio->data; |
| size_t size = I2S_PLAY_BUF_SIZE+I2S_PLAY_BUF_EXT_SIZE; |
| |
| audio_out_i2s_enable(0); |
| dmam_free_coherent(card->dev, size, p_aml_audio->area, |
| p_aml_audio->addr); |
| } |
| |
| static void aml_audio_start_timer(struct aml_audio_private_data *p_aml_audio, |
| unsigned long delay) |
| { |
| p_aml_audio->timer.expires = jiffies + delay; |
| p_aml_audio->timer.data = (unsigned long)p_aml_audio; |
| p_aml_audio->detect_flag = -1; |
| add_timer(&p_aml_audio->timer); |
| p_aml_audio->timer_en = 1; |
| } |
| |
| static void aml_audio_stop_timer(struct aml_audio_private_data *p_aml_audio) |
| { |
| del_timer_sync(&p_aml_audio->timer); |
| cancel_work_sync(&p_aml_audio->work); |
| p_aml_audio->timer_en = 0; |
| p_aml_audio->detect_flag = -1; |
| } |
| |
| static int hp_det_adc_value(struct aml_audio_private_data *p_aml_audio) |
| { |
| int ret, hp_value = 0; |
| int hp_val_sum = 0; |
| int loop_num = 0; |
| unsigned int mic_ret = 0; |
| |
| while (loop_num < 8) { |
| /* hp_value = get_adc_sample(p_aml_audio->hp_adc_ch); */ |
| if (hp_value < 0) { |
| pr_info("hp detect get error adc value!\n"); |
| return -1; /* continue; */ |
| } |
| hp_val_sum += hp_value; |
| loop_num++; |
| msleep_interruptible(15); |
| } |
| hp_val_sum = hp_val_sum >> 3; |
| /* pr_info("00000000000hp_val_sum = %hx\n",hp_val_sum); */ |
| if (hp_val_sum >= p_aml_audio->hp_val_h) { |
| ret = 0; |
| } else if ((hp_val_sum < (p_aml_audio->hp_val_l)) && hp_val_sum >= 0) { |
| ret = 1; |
| if (p_aml_audio->mic_det) { |
| if (hp_val_sum <= p_aml_audio->mic_val) { |
| mic_ret = 8; |
| ret |= mic_ret; |
| } |
| } |
| } else { |
| ret = 2; |
| if (p_aml_audio->mic_det) { |
| ret = 0; |
| mic_ret = 8; |
| ret |= mic_ret; |
| } |
| |
| } |
| |
| return ret; |
| } |
| |
| static int aml_audio_hp_detect(struct aml_audio_private_data *p_aml_audio) |
| { |
| int loop_num = 0; |
| int ret; |
| |
| p_aml_audio->hp_det_status = false; |
| /* mutex_lock(&p_aml_audio->lock); */ |
| |
| while (loop_num < 3) { |
| ret = hp_det_adc_value(p_aml_audio); |
| if (p_aml_audio->hp_last_state != ret) { |
| msleep_interruptible(50); |
| if (ret < 0) |
| ret = p_aml_audio->hp_last_state; |
| else |
| p_aml_audio->hp_last_state = ret; |
| } else |
| msleep_interruptible(50); |
| |
| loop_num = loop_num + 1; |
| } |
| |
| /* mutex_unlock(&p_aml_audio->lock); */ |
| |
| return ret; |
| } |
| |
| static void aml_asoc_work_func(struct work_struct *work) |
| { |
| struct aml_audio_private_data *p_aml_audio = NULL; |
| struct snd_soc_card *card = NULL; |
| int flag = -1; |
| int status = SND_JACK_HEADPHONE; |
| |
| p_aml_audio = container_of(work, struct aml_audio_private_data, work); |
| card = (struct snd_soc_card *)p_aml_audio->data; |
| |
| flag = aml_audio_hp_detect(p_aml_audio); |
| |
| if (p_aml_audio->detect_flag != flag) { |
| |
| p_aml_audio->detect_flag = flag; |
| |
| if (flag & 0x1) { |
| /* 1 :have mic ; 2 no mic */ |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| switch_set_state(&p_aml_audio->sdev, 2); |
| #endif |
| pr_info("aml aduio hp pluged 3 jack_type: %d\n", |
| SND_JACK_HEADPHONE); |
| snd_soc_jack_report(&p_aml_audio->jack, status, |
| SND_JACK_HEADPHONE); |
| |
| /* mic port detect */ |
| if (p_aml_audio->mic_det) { |
| if (flag & 0x8) { |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| switch_set_state(&p_aml_audio->mic_sdev, |
| 1); |
| #endif |
| pr_info("aml aduio mic pluged jack_type: %d\n", |
| SND_JACK_MICROPHONE); |
| snd_soc_jack_report(&p_aml_audio->jack, |
| status, SND_JACK_HEADPHONE); |
| } |
| } |
| |
| } else if (flag & 0x2) { |
| /* 1 :have mic ; 2 no mic */ |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| switch_set_state(&p_aml_audio->sdev, 1); |
| #endif |
| pr_info("aml aduio hp pluged 4 jack_type: %d\n", |
| SND_JACK_HEADSET); |
| snd_soc_jack_report(&p_aml_audio->jack, status, |
| SND_JACK_HEADPHONE); |
| } else { |
| pr_info("aml audio hp unpluged\n"); |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| switch_set_state(&p_aml_audio->sdev, 0); |
| #endif |
| snd_soc_jack_report(&p_aml_audio->jack, 0, |
| SND_JACK_HEADPHONE); |
| |
| /* mic port detect */ |
| if (p_aml_audio->mic_det) { |
| if (flag & 0x8) { |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| switch_set_state(&p_aml_audio->mic_sdev, |
| 1); |
| #endif |
| pr_info("aml aduio mic pluged jack_type: %d\n", |
| SND_JACK_MICROPHONE); |
| snd_soc_jack_report(&p_aml_audio->jack, |
| status, SND_JACK_HEADPHONE); |
| } |
| } |
| } |
| |
| } |
| p_aml_audio->hp_det_status = true; |
| } |
| |
| static void aml_asoc_timer_func(unsigned long data) |
| { |
| struct aml_audio_private_data *p_aml_audio = |
| (struct aml_audio_private_data *)data; |
| unsigned long delay = msecs_to_jiffies(150); |
| |
| if (p_aml_audio->hp_det_status && |
| !p_aml_audio->suspended) { |
| schedule_work(&p_aml_audio->work); |
| } |
| mod_timer(&p_aml_audio->timer, jiffies + delay); |
| } |
| |
| static struct aml_audio_private_data *p_audio; |
| static int aml_spk_enabled; |
| |
| static int aml_set_spk(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| aml_spk_enabled = ucontrol->value.integer.value[0]; |
| pr_info("aml_set_spk: aml_spk_enabled=%d\n", |
| aml_spk_enabled); |
| |
| msleep_interruptible(10); |
| if (!IS_ERR(p_audio->mute_desc)) |
| gpiod_direction_output(p_audio->mute_desc, aml_spk_enabled); |
| |
| if (aml_spk_enabled == 1) |
| msleep_interruptible(100); |
| |
| return 0; |
| } |
| |
| static int aml_get_spk(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = aml_spk_enabled; |
| return 0; |
| } |
| |
| static int aml_suspend_pre(struct snd_soc_card *card) |
| { |
| struct aml_audio_private_data *p_aml_audio; |
| struct pinctrl_state *state; |
| int val = 0; |
| |
| pr_info("enter %s\n", __func__); |
| p_aml_audio = snd_soc_card_get_drvdata(card); |
| if (!p_aml_audio->hp_disable) { |
| /* stop timer */ |
| mutex_lock(&p_aml_audio->lock); |
| p_aml_audio->suspended = true; |
| if (p_aml_audio->timer_en) |
| aml_audio_stop_timer(p_aml_audio); |
| |
| mutex_unlock(&p_aml_audio->lock); |
| } |
| |
| if (IS_ERR_OR_NULL(p_aml_audio->pin_ctl)) { |
| pr_info("no audio pin_ctrl to suspend\n"); |
| return 0; |
| } |
| |
| state = pinctrl_lookup_state(p_aml_audio->pin_ctl, "aml_snd_suspend"); |
| if (!IS_ERR(state)) { |
| pr_info("enter %s set pin_ctl suspend state\n", __func__); |
| pinctrl_select_state(p_aml_audio->pin_ctl, state); |
| } |
| |
| if (!IS_ERR(p_aml_audio->mute_desc)) { |
| val = p_aml_audio->mute_inv ? |
| GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH; |
| gpiod_direction_output(p_aml_audio->mute_desc, val); |
| }; |
| |
| return 0; |
| } |
| |
| static int aml_suspend_post(struct snd_soc_card *card) |
| { |
| pr_info("enter %s\n", __func__); |
| return 0; |
| } |
| |
| static int aml_resume_pre(struct snd_soc_card *card) |
| { |
| pr_info("enter %s\n", __func__); |
| |
| return 0; |
| } |
| |
| static int aml_resume_post(struct snd_soc_card *card) |
| { |
| struct aml_audio_private_data *p_aml_audio; |
| struct pinctrl_state *state; |
| int val = 0; |
| |
| pr_info("enter %s\n", __func__); |
| p_aml_audio = snd_soc_card_get_drvdata(card); |
| if (!p_aml_audio->hp_disable) { |
| mutex_lock(&p_aml_audio->lock); |
| p_aml_audio->suspended = false; |
| if (!p_aml_audio->timer_en) { |
| aml_audio_start_timer(p_aml_audio, |
| msecs_to_jiffies(100)); |
| } |
| mutex_unlock(&p_aml_audio->lock); |
| } |
| |
| if (IS_ERR_OR_NULL(p_aml_audio->pin_ctl)) { |
| pr_info("no audio pin_ctrl to resume\n"); |
| return 0; |
| } |
| |
| state = pinctrl_lookup_state(p_aml_audio->pin_ctl, "audio_i2s_pins"); |
| if (!IS_ERR(state)) { |
| pr_info("enter %s set pin_ctl working state\n", __func__); |
| pinctrl_select_state(p_aml_audio->pin_ctl, state); |
| } |
| |
| if (!IS_ERR(p_aml_audio->mute_desc)) { |
| if (p_aml_audio->sleep_time) |
| msleep(p_aml_audio->sleep_time); |
| val = p_aml_audio->mute_inv ? |
| GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; |
| gpiod_direction_output(p_aml_audio->mute_desc, val); |
| } |
| return 0; |
| } |
| |
| static int speaker_events(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| int val = 0; |
| |
| if (IS_ERR(p_audio->mute_desc)) { |
| pr_info("no mute_gpio setting"); |
| return 0; |
| } |
| switch (event) { |
| case SND_SOC_DAPM_POST_PMU: |
| pr_info("audio speaker on\n"); |
| val = p_audio->mute_inv ? |
| GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH; |
| gpiod_direction_output(p_audio->mute_desc, 1); |
| aml_spk_enabled = 1; |
| msleep(p_audio->sleep_time); |
| break; |
| case SND_SOC_DAPM_PRE_PMD: |
| pr_info("audio speaker off\n"); |
| val = p_audio->mute_inv ? |
| GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; |
| gpiod_direction_output(p_audio->mute_desc, val); |
| aml_spk_enabled = 0; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static const struct snd_soc_dapm_widget aml_asoc_dapm_widgets[] = { |
| SND_SOC_DAPM_SPK("Ext Spk", speaker_events), |
| SND_SOC_DAPM_HP("HP", NULL), |
| SND_SOC_DAPM_MIC("MAIN MIC", NULL), |
| SND_SOC_DAPM_MIC("HEADSET MIC", NULL), |
| }; |
| |
| static const struct snd_kcontrol_new aml_asoc_controls[] = { |
| SOC_DAPM_PIN_SWITCH("Ext Spk"), |
| }; |
| |
| static struct snd_soc_jack_pin jack_pins[] = { |
| { |
| .pin = "HP", |
| .mask = SND_JACK_HEADPHONE, |
| } |
| }; |
| |
| |
| static const struct snd_kcontrol_new aml_controls[] = { |
| SOC_SINGLE_BOOL_EXT("Ext Spk Switch", 0, |
| aml_get_spk, |
| aml_set_spk), |
| }; |
| |
| static int aml_asoc_init(struct snd_soc_pcm_runtime *rtd) |
| { |
| struct snd_soc_card *card = rtd->card; |
| struct snd_soc_codec *codec = rtd->codec; |
| struct snd_soc_dapm_context *dapm = &codec->component.dapm; |
| struct aml_audio_private_data *p_aml_audio; |
| int ret = 0; |
| int hp_paraments[5]; |
| |
| p_aml_audio = snd_soc_card_get_drvdata(card); |
| ret = snd_soc_add_card_controls(card, aml_controls, |
| ARRAY_SIZE(aml_controls)); |
| if (ret) |
| return ret; |
| /* Add specific widgets */ |
| snd_soc_dapm_new_controls(dapm, aml_asoc_dapm_widgets, |
| ARRAY_SIZE(aml_asoc_dapm_widgets)); |
| p_aml_audio->hp_disable = |
| of_property_read_bool(card->dev->of_node, "hp_disable"); |
| |
| pr_info("headphone detection disable=%d\n", p_aml_audio->hp_disable); |
| |
| if (!p_aml_audio->hp_disable) { |
| /* for report headphone to android */ |
| |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| p_aml_audio->sdev.name = "h2w"; |
| ret = switch_dev_register(&p_aml_audio->sdev); |
| if (ret < 0) { |
| pr_err("ASoC: register hp switch dev failed\n"); |
| return ret; |
| } |
| |
| |
| /* for micphone detect */ |
| p_aml_audio->mic_sdev.name = "mic_dev"; |
| ret = switch_dev_register(&p_aml_audio->mic_sdev); |
| if (ret < 0) { |
| pr_err("ASoC: register mic switch dev failed\n"); |
| return ret; |
| } |
| #endif |
| ret = snd_soc_card_jack_new(card, |
| "hp switch", SND_JACK_HEADPHONE, |
| &p_aml_audio->jack, jack_pins, ARRAY_SIZE(jack_pins)); |
| if (ret) { |
| pr_info("Failed to alloc resource for hp switch\n"); |
| return ret; |
| } |
| |
| p_aml_audio->hp_det_status = true; |
| p_aml_audio->mic_det = |
| of_property_read_bool(card->dev->of_node, "mic_det"); |
| |
| pr_info("entern %s : mic_det=%d\n", __func__, |
| p_aml_audio->mic_det); |
| ret = |
| of_property_read_u32_array(card->dev->of_node, |
| "hp_paraments", &hp_paraments[0], |
| 5); |
| if (ret) |
| pr_info("falied to get hp detect paraments\n"); |
| else { |
| /* hp adc value higher base, hp unplugged */ |
| p_aml_audio->hp_val_h = hp_paraments[0]; |
| /* hp adc value low base, 3 section hp plugged. */ |
| p_aml_audio->hp_val_l = hp_paraments[1]; |
| /* hp adc value mic detect value. */ |
| p_aml_audio->mic_val = hp_paraments[2]; |
| /* hp adc value test toerance */ |
| p_aml_audio->hp_detal = hp_paraments[3]; |
| /* get adc value from which adc port for hp detect */ |
| p_aml_audio->hp_adc_ch = hp_paraments[4]; |
| |
| pr_info("hp detect paraments: h=%d, l=%d,mic=%d,det=%d,ch=%d\n", |
| p_aml_audio->hp_val_h, p_aml_audio->hp_val_l, |
| p_aml_audio->mic_val, p_aml_audio->hp_detal, |
| p_aml_audio->hp_adc_ch); |
| } |
| |
| init_timer(&p_aml_audio->timer); |
| p_aml_audio->timer.function = aml_asoc_timer_func; |
| p_aml_audio->timer.data = (unsigned long)p_aml_audio; |
| p_aml_audio->data = (void *)card; |
| p_aml_audio->suspended = false; |
| |
| INIT_WORK(&p_aml_audio->work, aml_asoc_work_func); |
| mutex_init(&p_aml_audio->lock); |
| |
| mutex_lock(&p_aml_audio->lock); |
| if (!p_aml_audio->timer_en) { |
| aml_audio_start_timer(p_aml_audio, |
| msecs_to_jiffies(100)); |
| } |
| mutex_unlock(&p_aml_audio->lock); |
| } |
| |
| ret = |
| of_property_read_u32(card->dev->of_node, "sleep_time", |
| &p_aml_audio->sleep_time); |
| if (ret) |
| pr_info("no spk event delay time set\n"); |
| |
| return 0; |
| } |
| |
| static void aml_pinmux_init(struct snd_soc_card *card) |
| { |
| struct aml_audio_private_data *p_aml_audio; |
| int val; |
| |
| p_aml_audio = snd_soc_card_get_drvdata(card); |
| p_aml_audio->mute_desc = gpiod_get( |
| card->dev, "mute_gpio", GPIOD_OUT_LOW); |
| p_aml_audio->mute_inv = |
| of_property_read_bool(card->dev->of_node, "mute_inv"); |
| if (!IS_ERR(p_aml_audio->mute_desc)) { |
| if (p_aml_audio->sleep_time) |
| msleep(p_aml_audio->sleep_time); |
| val = p_aml_audio->mute_inv ? |
| GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; |
| gpiod_direction_output(p_aml_audio->mute_desc, val); |
| } |
| |
| #if 0 /*tmp_mask_for_kernel_4_4*/ |
| if (is_jtag_apao()) |
| return; |
| |
| val = aml_read_sec_reg(0xda004004); |
| pr_info("audio use jtag pinmux as i2s output, read val =%x\n", |
| aml_read_sec_reg(0xda004004)); |
| val = val & (~((1<<8) | (1<<1))); |
| aml_write_sec_reg(0xda004004, val); |
| #endif |
| |
| p_aml_audio->pin_ctl = devm_pinctrl_get_select( |
| card->dev, "audio_i2s"); |
| if (IS_ERR(p_aml_audio->pin_ctl)) { |
| pr_info("%s,aml_pinmux_init error!\n", __func__); |
| return; |
| } |
| } |
| |
| static int aml_card_dai_parse_of(struct device *dev, |
| struct snd_soc_dai_link *dai_link, |
| int (*init)(struct snd_soc_pcm_runtime *rtd), |
| struct device_node *cpu_node, |
| struct device_node *codec_node, |
| struct device_node *plat_node) |
| { |
| int ret; |
| |
| /* get cpu dai->name */ |
| ret = snd_soc_of_get_dai_name(cpu_node, &dai_link->cpu_dai_name); |
| if (ret < 0) |
| goto parse_error; |
| |
| ret = of_count_phandle_with_args(codec_node, "sound-dai", |
| "#sound-dai-cells"); |
| pr_info("%s codec_node count:%d\n", __func__, ret); |
| if (ret <= 1) { |
| ret = snd_soc_of_get_dai_name(codec_node, |
| &dai_link->codec_dai_name); |
| if (ret < 0) |
| goto parse_error; |
| |
| dai_link->name = dai_link->stream_name = |
| dai_link->cpu_dai_name; |
| dai_link->codec_of_node = |
| of_parse_phandle(codec_node, "sound-dai", 0); |
| dai_link->platform_of_node = plat_node; |
| dai_link->init = init; |
| } else { |
| ret = snd_soc_of_get_dai_link_codecs(dev, codec_node, dai_link); |
| if (ret < 0) { |
| pr_err("failed get_dai_link_codecs from codec_node\n"); |
| goto parse_error; |
| } |
| dai_link->init = init; |
| dai_link->platform_of_node = plat_node; |
| /*"multicodec";*/ |
| dai_link->name = dai_link->stream_name = dai_link->cpu_dai_name; |
| } |
| return 0; |
| |
| parse_error: |
| return ret; |
| } |
| |
| static int aml_card_dais_parse_of(struct snd_soc_card *card) |
| { |
| struct device_node *np = card->dev->of_node; |
| struct device_node *cpu_node, *codec_node, *plat_node; |
| struct device *dev = card->dev; |
| struct snd_soc_dai_link *dai_links; |
| int num_dai_links, cpu_num, codec_num, plat_num; |
| int i, ret; |
| int (*init)(struct snd_soc_pcm_runtime *rtd); |
| |
| ret = of_count_phandle_with_args(np, "cpu_list", NULL); |
| if (ret < 0) { |
| dev_err(dev, "AML sound card no cpu_list errno: %d\n", ret); |
| goto err; |
| } else { |
| cpu_num = ret; |
| } |
| |
| ret = of_count_phandle_with_args(np, "codec_list", NULL); |
| if (ret < 0) { |
| dev_err(dev, "AML sound card no codec_list errno: %d\n", ret); |
| goto err; |
| } else { |
| codec_num = ret; |
| } |
| |
| ret = of_count_phandle_with_args(np, "plat_list", NULL); |
| if (ret < 0) { |
| dev_err(dev, "AML sound card no plat_list errno: %d\n", ret); |
| goto err; |
| } else { |
| plat_num = ret; |
| } |
| |
| if ((cpu_num == codec_num) && (cpu_num == plat_num)) { |
| num_dai_links = cpu_num; |
| } else { |
| dev_err(dev, |
| "AML sound card cpu_dai num, codec_dai num, platform num don't match: %d\n", |
| ret); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| dai_links = |
| devm_kzalloc(dev, num_dai_links * sizeof(struct snd_soc_dai_link), |
| GFP_KERNEL); |
| if (!dai_links) { |
| dev_err(dev, "Can't allocate snd_soc_dai_links\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| card->dai_link = dai_links; |
| card->num_links = num_dai_links; |
| for (i = 0; i < num_dai_links; i++) { |
| init = NULL; |
| /* CPU sub-node */ |
| cpu_node = of_parse_phandle(np, "cpu_list", i); |
| if (!cpu_node) { |
| dev_err(dev, "parse aml sound card cpu list error\n"); |
| return -EINVAL; |
| } |
| /* CODEC sub-node */ |
| codec_node = of_parse_phandle(np, "codec_list", i); |
| if (!codec_node) { |
| dev_err(dev, "parse aml sound card codec list error\n"); |
| return ret; |
| } |
| /* Platform sub-node */ |
| plat_node = of_parse_phandle(np, "plat_list", i); |
| if (!plat_node) { |
| dev_err(dev, |
| "parse aml sound card platform list error\n"); |
| return ret; |
| } |
| if (i == 0) |
| init = aml_asoc_init; |
| |
| ret = |
| aml_card_dai_parse_of(dev, &dai_links[i], init, cpu_node, |
| codec_node, plat_node); |
| } |
| |
| err: |
| return ret; |
| } |
| |
| static void aml_pinmux_work_func(struct work_struct *pinmux_work) |
| { |
| struct aml_audio_private_data *p_aml_audio = NULL; |
| struct snd_soc_card *card = NULL; |
| |
| p_aml_audio = container_of(pinmux_work, |
| struct aml_audio_private_data, pinmux_work); |
| card = (struct snd_soc_card *)p_aml_audio->data; |
| |
| aml_pinmux_init(card); |
| } |
| |
| static int aml_audio_probe(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct device *dev = &pdev->dev; |
| struct snd_soc_card *card = NULL; |
| struct aml_audio_private_data *p_aml_audio; |
| int ret; |
| |
| p_aml_audio = |
| devm_kzalloc(dev, sizeof(struct aml_audio_private_data), |
| GFP_KERNEL); |
| if (!p_aml_audio) { |
| dev_err(&pdev->dev, "Can't allocate aml_audio_private_data\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| p_audio = p_aml_audio; |
| |
| card = devm_kzalloc(dev, sizeof(struct snd_soc_card), GFP_KERNEL); |
| if (!card) { |
| /*dev_err(dev, "Can't allocate snd_soc_card\n");*/ |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| snd_soc_card_set_drvdata(card, p_aml_audio); |
| card->dev = dev; |
| platform_set_drvdata(pdev, card); |
| ret = snd_soc_of_parse_card_name(card, "aml_sound_card,name"); |
| if (ret < 0) { |
| dev_err(dev, "no specific snd_soc_card name\n"); |
| goto err; |
| } |
| /* DAPM routes */ |
| if (of_property_read_bool(np, "aml,audio-routing")) { |
| ret = snd_soc_of_parse_audio_routing(card, "aml,audio-routing"); |
| if (ret < 0) { |
| dev_err(dev, "parse aml sound card routing error %d\n", |
| ret); |
| return ret; |
| } |
| } |
| ret = aml_card_dais_parse_of(card); |
| if (ret < 0) { |
| dev_err(dev, "parse aml sound card routing error %d\n", |
| ret); |
| goto err; |
| } |
| |
| card->suspend_pre = aml_suspend_pre, |
| card->suspend_post = aml_suspend_post, |
| card->resume_pre = aml_resume_pre, |
| card->resume_post = aml_resume_post, |
| ret = devm_snd_soc_register_card(&pdev->dev, card); |
| if (ret < 0) { |
| dev_err(dev, "register aml sound card error %d\n", ret); |
| goto err; |
| } |
| |
| p_aml_audio->data = (void *)card; |
| |
| ret = of_property_read_bool(np, "i2sclk_disable_startup"); |
| if (!ret) { |
| pr_info("enable i2sclk in startup\n"); |
| aml_i2s_play(p_aml_audio); |
| } |
| INIT_WORK(&p_aml_audio->pinmux_work, aml_pinmux_work_func); |
| schedule_work(&p_aml_audio->pinmux_work); |
| |
| /*aml_pinmux_init(card);*/ |
| return 0; |
| err: |
| dev_err(dev, "Can't probe snd_soc_card\n"); |
| return ret; |
| } |
| |
| static void aml_audio_shutdown(struct platform_device *pdev) |
| { |
| struct pinctrl_state *state; |
| struct snd_soc_card *card; |
| |
| card = platform_get_drvdata(pdev); |
| aml_suspend_pre(card); |
| |
| if (IS_ERR_OR_NULL(p_audio->pin_ctl)) { |
| pr_info("no audio pin_ctrl to shutdown\n"); |
| return; |
| } |
| |
| state = pinctrl_lookup_state(p_audio->pin_ctl, "aml_snd_suspend"); |
| if (!IS_ERR(state)) |
| pinctrl_select_state(p_audio->pin_ctl, state); |
| |
| aml_i2s_stop(p_audio); |
| } |
| |
| static const struct of_device_id amlogic_audio_of_match[] = { |
| {.compatible = "aml, meson-snd-card",}, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, amlogic_audio_dt_match); |
| |
| static struct platform_driver aml_audio_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = amlogic_audio_of_match, |
| .pm = &snd_soc_pm_ops, |
| }, |
| .probe = aml_audio_probe, |
| .shutdown = aml_audio_shutdown, |
| }; |
| |
| module_platform_driver(aml_audio_driver); |
| |
| MODULE_AUTHOR("AMLogic, Inc."); |
| MODULE_DESCRIPTION("AML_MESON audio machine Asoc driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:" DRV_NAME); |