/* * sound\soc\sunxi\sun8iw11_codec.c * (C) Copyright 2014-2018 * Allwinner Technology Co., Ltd. * huangxin * wolfgang huang * * some simple description for this code * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_rw_func.h" #include "sun8iw11-codec.h" //#define SUNXI_DAPM_HPPA #undef SUNXI_DAPM_HPPA #define DRV_NAME "sunxi-internal-codec" struct codec_hw_config { u32 adcagc_cfg:1; u32 adcdrc_cfg:1; u32 dacdrc_cfg:1; u32 adchpf_cfg:1; u32 dachpf_cfg:1; }; struct sunxi_codec { struct device *dev; struct regmap *regmap; void __iomem *analogbase; struct clk *pllclk; struct clk *moduleclk; /* self user config params */ u32 headphonevol; u32 maingain; u32 spkervol; u32 pa_sleep_time; u32 spk_gpio; bool hp_dirused; bool spk_gpio_used; struct codec_hw_config hwconfig; }; struct sample_rate { unsigned int samplerate; unsigned int rate_bit; }; static const struct sample_rate sample_rate_conv[] = { {44100, 0}, {48000, 0}, {8000, 5}, {32000, 1}, {22050, 2}, {24000, 2}, {16000, 3}, {11025, 4}, {12000, 4}, {192000, 6}, {96000, 7}, }; static const DECLARE_TLV_DB_SCALE(digital_tlv, -7424, 116, 0); static const DECLARE_TLV_DB_SCALE(headphone_tlv, -6300, 100, 1); static const DECLARE_TLV_DB_SCALE(linein_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(fm_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(mic_gain_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(phoneout_tlv, -450, 150, 0); static const DECLARE_TLV_DB_SCALE(adc_gain_tlv, -450, 150, 0); static const unsigned int mic_boost_tlv[] = { TLV_DB_RANGE_HEAD(2), 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), }; static void adcagc_config(struct snd_soc_codec *codec) { } static void adcdrc_config(struct snd_soc_codec *codec) { } static void adchpf_config(struct snd_soc_codec *codec) { snd_soc_update_bits(codec, SUNXI_ADC_DRC_HHPFC, (0xFF<value.integer.value[0] = ((reg_val & (1<value.integer.value[0]) { case 0: case 1: snd_soc_update_bits(codec, SUNXI_DAC_DPC, (0x1<dapm); struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, SUNXI_PAEN_HP_CTR, (1<pa_sleep_time + 150); break; case SND_SOC_DAPM_PRE_PMD: snd_soc_update_bits(codec, SUNXI_PAEN_HP_CTR, (1<dapm); struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); switch (event) { case SND_SOC_DAPM_POST_PMU: if (sunxi_internal_codec->spk_gpio_used) { gpio_set_value(sunxi_internal_codec->spk_gpio, 1); /* time delay to wait spk pa work fine, general setting 50ms */ mdelay(50); } break; case SND_SOC_DAPM_PRE_PMD: if (sunxi_internal_codec->spk_gpio_used) gpio_set_value(sunxi_internal_codec->spk_gpio, 0); break; default: break; } return 0; } static int sunxi_playback_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, SUNXI_DAC_DPC, (0x1<dapm); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_soc_update_bits(codec, SUNXI_ADC_FIFO_CTR, (0x1<headphonevol<spkervol<hp_dirused) { snd_soc_update_bits(codec, SUNXI_PAEN_HP_CTR, (0x3<hwconfig.adcagc_cfg) adcagc_config(codec); if (sunxi_internal_codec->hwconfig.adcdrc_cfg) adcdrc_config(codec); if (sunxi_internal_codec->hwconfig.adchpf_cfg) adchpf_config(codec); if (sunxi_internal_codec->hwconfig.dacdrc_cfg) dacdrc_config(codec); if (sunxi_internal_codec->hwconfig.dachpf_cfg) dachpf_config(codec); } static int sunxi_codec_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; int i = 0; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (3<stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (3<stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (0x7< 48000) return -EINVAL; snd_soc_update_bits(codec, SUNXI_ADC_FIFO_CTR, (0x7<stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (1<stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (1<pllclk, freq)) { dev_err(sunxi_internal_codec->dev, "set pllclk rate failed\n"); return -EINVAL; } return 0; } static void sunxi_codec_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (sunxi_internal_codec->hwconfig.dacdrc_cfg) dacdrc_enable(codec, 0); if (sunxi_internal_codec->hwconfig.dachpf_cfg) dachpf_enable(codec, 0); } else { if (sunxi_internal_codec->hwconfig.adcagc_cfg) adcagc_enable(codec, 0); if (sunxi_internal_codec->hwconfig.adcdrc_cfg) adcdrc_enable(codec, 0); if (sunxi_internal_codec->hwconfig.adchpf_cfg) adchpf_enable(codec, 0); } } static int sunxi_codec_digital_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; if (mute) { snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << LHPPAMUTE, 0x0 << LHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << RHPPAMUTE, 0x0 << RHPPAMUTE); } else { snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << LHPPAMUTE, 0x1 << LHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << RHPPAMUTE, 0x1 << RHPPAMUTE); } return 0; } static int sunxi_codec_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; sunxi_codec_init(codec); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (1<codec; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << LHPPAMUTE, 0x1 << LHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << RHPPAMUTE, 0x1 << RHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (1<stream == SNDRV_PCM_STREAM_PLAYBACK) { /* Fix pop: * when disable the DAC_DRQ_EN, * it will occuse to pop or click sound. */ snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << LHPPAMUTE, 0x0 << LHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_PA_SRC, 0x1 << RHPPAMUTE, 0x0 << RHPPAMUTE); snd_soc_update_bits(codec, SUNXI_DAC_FIFO_CTR, (1<codec; #ifndef SUNXI_DAPM_HPPA snd_soc_update_bits(codec, SUNXI_PAEN_HP_CTR, (1<component.dapm); snd_soc_add_codec_controls(codec, sunxi_codec_controls, ARRAY_SIZE(sunxi_codec_controls)); snd_soc_dapm_new_controls(dapm, sunxi_codec_dapm_widgets, ARRAY_SIZE(sunxi_codec_dapm_widgets)); snd_soc_dapm_add_routes(dapm, sunxi_codec_dapm_routes, ARRAY_SIZE(sunxi_codec_dapm_routes)); sunxi_codec_init(codec); return 0; } static int sunxi_codec_remove(struct snd_soc_codec *codec) { #ifndef SUNXI_DAPM_HPPA snd_soc_update_bits(codec, SUNXI_PAEN_HP_CTR, (1<spk_gpio_used) sunxi_gpio_iodisable(sunxi_internal_codec->spk_gpio); clk_disable_unprepare(sunxi_internal_codec->moduleclk); clk_disable_unprepare(sunxi_internal_codec->pllclk); pr_debug("End %s\n", __func__); return 0; } static int sunxi_codec_resume(struct snd_soc_codec *codec) { struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); pr_debug("Enter %s\n", __func__); if (clk_prepare_enable(sunxi_internal_codec->pllclk)) { dev_err(sunxi_internal_codec->dev, "enable pllclk failed, resume exit\n"); return -EBUSY; } if (clk_prepare_enable(sunxi_internal_codec->moduleclk)) { dev_err(sunxi_internal_codec->dev, "enable moduleclk failed, resume exit\n"); clk_disable_unprepare(sunxi_internal_codec->pllclk); return -EBUSY; } if (sunxi_internal_codec->spk_gpio_used) { gpio_direction_output(sunxi_internal_codec->spk_gpio, 1); gpio_set_value(sunxi_internal_codec->spk_gpio, 0); } sunxi_codec_init(codec); pr_debug("End %s\n", __func__); return 0; } static unsigned int sunxi_codec_read(struct snd_soc_codec *codec, unsigned int reg) { struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); unsigned int reg_val; if (reg >= SUNXI_PR_CFG) { /* Analog part */ reg = reg - SUNXI_PR_CFG; return read_prcm_wvalue(reg, sunxi_internal_codec->analogbase); } else { regmap_read(sunxi_internal_codec->regmap, reg, ®_val); } return reg_val; } static int sunxi_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { struct sunxi_codec *sunxi_internal_codec = snd_soc_codec_get_drvdata(codec); if (reg >= SUNXI_PR_CFG) { /* Analog part */ reg = reg - SUNXI_PR_CFG; write_prcm_wvalue(reg, val, sunxi_internal_codec->analogbase); } else { regmap_write(sunxi_internal_codec->regmap, reg, val); } return 0; }; static struct snd_soc_codec_driver soc_codec_dev_sunxi = { .probe = sunxi_codec_probe, .remove = sunxi_codec_remove, .suspend = sunxi_codec_suspend, .resume = sunxi_codec_resume, .read = sunxi_codec_read, .write = sunxi_codec_write, .ignore_pmdown_time = 1, }; struct label { const char *name; int value; }; #define LABEL(constant) { #constant, constant } #define LABEL_END { NULL, -1 } static struct label reg_labels[] = { LABEL(SUNXI_DAC_DPC), LABEL(SUNXI_DAC_FIFO_CTR), LABEL(SUNXI_DAC_FIFO_STA), LABEL(SUNXI_ADC_FIFO_CTR), LABEL(SUNXI_ADC_FIFO_STA), LABEL(SUNXI_ADC_RXDATA), LABEL(SUNXI_DAC_TXDATA), LABEL(SUNXI_DAC_CNT), LABEL(SUNXI_ADC_CNT), LABEL(SUNXI_DAC_DG), LABEL(SUNXI_ADC_DG), LABEL(SUNXI_HMIC_CTRL), LABEL(SUNXI_HMIC_DATA), LABEL(SUNXI_HP_VOLC), LABEL(SUNXI_LOMIX_SRC), LABEL(SUNXI_ROMIX_SRC), LABEL(SUNXI_DAC_PA_SRC), LABEL(SUNXI_LINEIN_GCTR), LABEL(SUNXI_FM_GCTR), LABEL(SUNXI_MICIN_GCTR), LABEL(SUNXI_PAEN_HP_CTR), LABEL(SUNXI_PHONEOUT_CTR), LABEL(SUNXI_MIC2G_LINEEN_CTR), LABEL(SUNXI_MIC1G_MICBIAS_CTR), LABEL(SUNXI_LADCMIX_SRC), LABEL(SUNXI_RADCMIX_SRC), LABEL(SUNXI_PA_POP_CTR), LABEL(SUNXI_ADC_AP_EN), LABEL(SUNXI_ADDA_APT0), LABEL(SUNXI_ADDA_APT1), LABEL(SUNXI_ADDA_APT2), LABEL(SUNXI_CHOP_CAL_CTR), LABEL(SUNXI_BIAS_DA16_CAL_CTR), LABEL(SUNXI_DA16_CALI_DATA), LABEL(SUNXI_BIAS_CALI_DATA), LABEL(SUNXI_BIAS_CALI_SET), LABEL_END, }; static ssize_t show_audio_reg(struct device *dev, struct device_attribute *attr, char *buf) { struct sunxi_codec *sunxi_internal_codec = dev_get_drvdata(dev); int count = 0, i = 0; int reg_group = 1; int reg_offset; unsigned int reg_val; count += sprintf(buf, "dump audio reg:\n"); while (reg_labels[i].name != NULL) { if (reg_labels[i].value == SUNXI_PR_CFG) reg_group++; if (reg_group == 1) { regmap_read(sunxi_internal_codec->regmap, reg_labels[i].value, ®_val); count += sprintf(buf + count, "%s 0x%x: 0x%08x\n", reg_labels[i].name, (reg_labels[i].value), reg_val); } else if (reg_group == 2) { reg_offset = reg_labels[i].value - SUNXI_PR_CFG; reg_val = read_prcm_wvalue(reg_offset, sunxi_internal_codec->analogbase); count += sprintf(buf + count, "%s 0x%x: 0x%x\n", reg_labels[i].name, reg_labels[i].value, reg_val); } i++; } return count; } /* ex: *param 1: 0 read;1 write *param 2: 1 digital reg; 2 analog reg *param 3: reg value; *param 4: write value; read: echo 0,1,0x00> audio_reg echo 0,2,0x00> audio_reg write: echo 1,1,0x00,0xa > audio_reg echo 1,2,0x00,0xff > audio_reg */ static ssize_t store_audio_reg(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; int rw_flag; unsigned int input_reg_val = 0; int input_reg_group = 0; unsigned int input_reg_offset = 0; struct sunxi_codec *sunxi_internal_codec = dev_get_drvdata(dev); ret = sscanf(buf, "%d,%d,0x%x,0x%x", &rw_flag, &input_reg_group, &input_reg_offset, &input_reg_val); dev_info(dev, "ret:%d, reg_group:%d, reg_offset:%d, reg_val:0x%x\n", ret, input_reg_group, input_reg_offset, input_reg_val); if (!(input_reg_group == 1 || input_reg_group == 2)) { pr_err("not exist reg group\n"); ret = count; goto out; } if (!(rw_flag == 1 || rw_flag == 0)) { pr_err("not rw_flag\n"); ret = count; goto out; } if (input_reg_group == 1) { if (rw_flag) { regmap_write(sunxi_internal_codec->regmap, input_reg_offset, input_reg_val); } else { regmap_read(sunxi_internal_codec->regmap, input_reg_offset, &input_reg_val); dev_info(dev, "\n\n Reg[0x%x] : 0x%08x\n\n", input_reg_offset, input_reg_val); } } else if (input_reg_group == 2) { if (rw_flag) { write_prcm_wvalue(input_reg_offset, input_reg_val & 0xff, sunxi_internal_codec->analogbase); } else { input_reg_val = read_prcm_wvalue(input_reg_offset, sunxi_internal_codec->analogbase); dev_info(dev, "\n\n Reg[0x%02x] : 0x%02x\n\n", input_reg_offset, input_reg_val); } } ret = count; out: return ret; } static DEVICE_ATTR(audio_reg, 0644, show_audio_reg, store_audio_reg); static struct attribute *audio_debug_attrs[] = { &dev_attr_audio_reg.attr, NULL, }; static struct attribute_group audio_debug_attr_group = { .name = "audio_reg_debug", .attrs = audio_debug_attrs, }; static const struct regmap_config sunxi_codec_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = SUNXI_ADC_DAP_CTR, .cache_type = REGCACHE_NONE, }; static int sunxi_internal_codec_probe(struct platform_device *pdev) { struct sunxi_codec *sunxi_internal_codec; struct device_node *np = pdev->dev.of_node; void __iomem *sunxi_digibase; int ret; unsigned int temp_val; sunxi_internal_codec = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_codec), GFP_KERNEL); if (!sunxi_internal_codec) { dev_err(&pdev->dev, "Can't allocate sunxi codec memory\n"); ret = -ENOMEM; goto err_node_put; } dev_set_drvdata(&pdev->dev, sunxi_internal_codec); sunxi_internal_codec->dev = &pdev->dev; sunxi_internal_codec->pllclk = of_clk_get(np, 0); sunxi_internal_codec->moduleclk = of_clk_get(np, 1); if (IS_ERR_OR_NULL(sunxi_internal_codec->pllclk)) { dev_err(&pdev->dev, "pllclk not exist or invaild\n"); ret = PTR_ERR(sunxi_internal_codec->pllclk); goto err_devm_kfree; } else { if (IS_ERR_OR_NULL(sunxi_internal_codec->moduleclk)) { dev_err(&pdev->dev, "moduleclk not exist or invaild\n"); ret = PTR_ERR(sunxi_internal_codec->moduleclk); goto err_devm_kfree; } else { if (clk_set_parent(sunxi_internal_codec->moduleclk, sunxi_internal_codec->pllclk)) { dev_err(&pdev->dev, "set parent of moduleclk to pllclk failed\n"); ret = -EBUSY; goto err_devm_kfree; } if (clk_prepare_enable(sunxi_internal_codec->pllclk)) { dev_err(&pdev->dev, "pllclk enable failed\n"); ret = -EBUSY; goto err_devm_kfree; } if (clk_prepare_enable(sunxi_internal_codec->moduleclk)) { dev_err(&pdev->dev, "moduleclk enable failed\n"); ret = -EBUSY; goto err_pllclk_put; } } } sunxi_digibase = of_iomap(np, 0); if (sunxi_digibase == NULL) { dev_err(&pdev->dev, "digital register iomap failed\n"); ret = -EINVAL; goto err_moduleclk_put; } /* Analog register part, not using regmap */ sunxi_internal_codec->analogbase = of_iomap(np, 1); if (sunxi_internal_codec->analogbase == NULL) { dev_err(&pdev->dev, "analog register iomap failed\n"); ret = -EINVAL; goto err_digi_iounmap; } sunxi_internal_codec->regmap = devm_regmap_init_mmio(&pdev->dev, sunxi_digibase, &sunxi_codec_regmap_config); if (IS_ERR(sunxi_internal_codec->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); ret = PTR_ERR(sunxi_internal_codec->regmap); goto err_analog_iounmap; } ret = of_property_read_u32(np, "headphonevol", &temp_val); if (ret < 0) { dev_warn(&pdev->dev, "headphone volume get failed\n"); sunxi_internal_codec->headphonevol = 0; } else { sunxi_internal_codec->headphonevol = temp_val; } ret = of_property_read_u32(np, "spkervol", &temp_val); if (ret < 0) { dev_warn(&pdev->dev, "speaker volume get failed\n"); sunxi_internal_codec->spkervol = 0; } else { sunxi_internal_codec->spkervol = temp_val; } ret = of_property_read_u32(np, "maingain", &temp_val); if (ret < 0) { dev_warn(&pdev->dev, "main gain get failed\n"); sunxi_internal_codec->maingain = 0; } else { sunxi_internal_codec->maingain = temp_val; } ret = of_property_read_u32(np, "hp_dirused", &temp_val); if (ret < 0) { dev_warn(&pdev->dev, "hp_dirused get failed\n"); sunxi_internal_codec->hp_dirused = 0; } else { sunxi_internal_codec->hp_dirused = temp_val; } ret = of_property_read_u32(np, "pa_sleep_time", &temp_val); if (ret < 0) { dev_warn(&pdev->dev, "pa_sleep_time get failed\n"); sunxi_internal_codec->pa_sleep_time = 350; } else { sunxi_internal_codec->pa_sleep_time = temp_val; } pr_debug("headphonevol:%d, spkervol:%d, maingain:%d, pa_sleep_time:%d\n", sunxi_internal_codec->headphonevol, sunxi_internal_codec->spkervol, sunxi_internal_codec->maingain, sunxi_internal_codec->pa_sleep_time ); ret = of_property_read_u32(np, "adcagc_cfg", &temp_val); if (ret < 0) { pr_err("[audio-codec]adcagc_cfg configurations missing or invalid.\n"); ret = -EINVAL; } else { sunxi_internal_codec->hwconfig.adcagc_cfg = temp_val; } ret = of_property_read_u32(np, "adcdrc_cfg", &temp_val); if (ret < 0) { pr_err("[audio-codec]adcdrc_cfg configurations missing or invalid.\n"); ret = -EINVAL; } else { sunxi_internal_codec->hwconfig.adcdrc_cfg = temp_val; } ret = of_property_read_u32(np, "adchpf_cfg", &temp_val); if (ret < 0) { pr_err("[audio-codec]adchpf_cfg configurations missing or invalid.\n"); ret = -EINVAL; } else { sunxi_internal_codec->hwconfig.adchpf_cfg = temp_val; } ret = of_property_read_u32(np, "dacdrc_cfg", &temp_val); if (ret < 0) { pr_err("[audio-codec]dacdrc_cfg configurations missing or invalid.\n"); ret = -EINVAL; } else { sunxi_internal_codec->hwconfig.dacdrc_cfg = temp_val; } ret = of_property_read_u32(np, "dachpf_cfg", &temp_val); if (ret < 0) { pr_err("[audio-codec]dachpf_cfg configurations missing or invalid.\n"); ret = -EINVAL; } else { sunxi_internal_codec->hwconfig.dachpf_cfg = temp_val; } ret = of_get_named_gpio(np, "gpio-spk", 0); if (ret >= 0) { sunxi_internal_codec->spk_gpio_used = 1; sunxi_internal_codec->spk_gpio = ret; if (!gpio_is_valid(sunxi_internal_codec->spk_gpio)) { dev_err(&pdev->dev, "gpio-spk is valid\n"); ret = -EINVAL; goto err_analog_iounmap; } else { ret = devm_gpio_request(&pdev->dev, sunxi_internal_codec->spk_gpio, "SPK"); if (ret) { dev_err(&pdev->dev, "failed to request gpio-spk gpio\n"); ret = -EBUSY; goto err_analog_iounmap; } else { gpio_direction_output(sunxi_internal_codec->spk_gpio, 1); gpio_set_value(sunxi_internal_codec->spk_gpio, 0); } } } else { sunxi_internal_codec->spk_gpio_used = 0; } ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sunxi, sunxi_codec_dai, ARRAY_SIZE(sunxi_codec_dai)); if (ret < 0) { dev_err(&pdev->dev, "register codec failed\n"); goto err_analog_iounmap; } ret = sysfs_create_group(&pdev->dev.kobj, &audio_debug_attr_group); if (ret) { dev_warn(&pdev->dev, "failed to create attr group\n"); } return 0; err_analog_iounmap: iounmap(sunxi_internal_codec->analogbase); err_digi_iounmap: iounmap(sunxi_digibase); err_moduleclk_put: clk_disable_unprepare(sunxi_internal_codec->moduleclk); err_pllclk_put: clk_disable_unprepare(sunxi_internal_codec->pllclk); err_devm_kfree: devm_kfree(&pdev->dev, sunxi_internal_codec); err_node_put: of_node_put(np); return ret; } static int __exit sunxi_internal_codec_remove(struct platform_device *pdev) { struct sunxi_codec *sunxi_internal_codec = dev_get_drvdata(&pdev->dev); snd_soc_unregister_codec(&pdev->dev); clk_put(sunxi_internal_codec->moduleclk); clk_put(sunxi_internal_codec->pllclk); devm_kfree(&pdev->dev, sunxi_internal_codec); return 0; } static const struct of_device_id sunxi_internal_codec_of_match[] = { { .compatible = "allwinner,sunxi-internal-codec", }, {}, }; static struct platform_driver sunxi_internal_codec_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = sunxi_internal_codec_of_match, }, .probe = sunxi_internal_codec_probe, .remove = __exit_p(sunxi_internal_codec_remove), }; module_platform_driver(sunxi_internal_codec_driver); MODULE_DESCRIPTION("SUNXI Codec ASoC driver"); MODULE_AUTHOR("wolfgang huang "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:sunxi-internal-codec");