diff -drupN a/sound/soc/codecs/acx00.c b/sound/soc/codecs/acx00.c --- a/sound/soc/codecs/acx00.c 1970-01-01 03:00:00.000000000 +0300 +++ b/sound/soc/codecs/acx00.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,1307 @@ +/* + * acx00.c -- ACX00 ALSA Soc Audio Codec driver + * + * (C) Copyright 2010-2016 Allwinnertech Technology., Ltd. + * + * Author: Wolfgang Huang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acx00.h" + + +#define ACX00_DEF_VOL 0x9F9F +#undef ACX00_DAPM_LINEOUT + +struct acx00_priv { + struct acx00 *acx00; /* parent mfd device struct */ + struct snd_soc_codec *codec; + struct clk *clk; + unsigned int sample_rate; + unsigned int fmt; + unsigned int enable; + unsigned int spk_gpio; + bool spk_gpio_used; + struct mutex mutex; + struct delayed_work spk_work; + struct delayed_work resume_work; +}; + +struct sample_rate { + unsigned int samplerate; + unsigned int rate_bit; +}; + +static const struct sample_rate sample_rate_conv[] = { + {44100, 7}, + {48000, 8}, + {8000, 0}, + {32000, 6}, + {22050, 4}, + {24000, 5}, + {16000, 3}, + {11025, 1}, + {12000, 2}, + {192000, 10}, + {96000, 9}, +}; + +void __iomem *io_stat_addr; + +static const DECLARE_TLV_DB_SCALE(i2s_mixer_adc_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(i2s_mixer_dac_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(dac_mixer_adc_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(dac_mixer_dac_tlv, -600, 600, 1); +static const DECLARE_TLV_DB_SCALE(line_out_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(mic_out_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(phoneout_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(adc_input_tlv, -450, 150, 0); +static const DECLARE_TLV_DB_SCALE(lineout_tlv, -4800, 150, 1); +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 const struct snd_kcontrol_new acx00_codec_controls[] = { + SOC_DOUBLE_TLV("I2S Mixer ADC Volume", AC_I2S_MIXER_GAIN, + I2S_MIXERL_GAIN_ADC, I2S_MIXERR_GAIN_ADC, + 0x1, 0, i2s_mixer_adc_tlv), + SOC_DOUBLE_TLV("I2S Mixer DAC Volume", AC_I2S_MIXER_GAIN, + I2S_MIXERR_GAIN_DAC, I2S_MIXERR_GAIN_DAC, + 0x1, 0, i2s_mixer_dac_tlv), + SOC_DOUBLE_TLV("DAC Mixer ADC Volume", AC_DAC_MIXER_GAIN, + DAC_MIXERL_GAIN_ADC, DAC_MIXERR_GAIN_ADC, + 0x1, 0, dac_mixer_adc_tlv), + SOC_DOUBLE_TLV("DAC Mxier DAC Volume", AC_DAC_MIXER_GAIN, + DAC_MIXERL_GAIN_DAC, DAC_MIXERR_GAIN_DAC, + 0x1, 0, dac_mixer_dac_tlv), + SOC_SINGLE_TLV("Line Out Mixer Volume", AC_OUT_MIXER_CTL, + OUT_MIXER_LINE_VOL, 0x7, 0, line_out_tlv), + SOC_DOUBLE_TLV("MIC Out Mixer Volume", AC_OUT_MIXER_CTL, + OUT_MIXER_MIC1_VOL, OUT_MIXER_MIC2_VOL, + 0x7, 0, mic_out_tlv), + SOC_SINGLE_TLV("ADC Input Volume", AC_ADC_MIC_CTL, + ADC_GAIN, 0x07, 0, adc_input_tlv), + SOC_SINGLE_TLV("LINEOUT Volume", AC_LINEOUT_CTL, + LINEOUT_VOL, 0x1f, 0, lineout_tlv), + SOC_SINGLE_TLV("MIC1 Boost Volume", AC_ADC_MIC_CTL, + MIC1_BOOST, 0x07, 0, mic_boost_tlv), + SOC_SINGLE_TLV("MIC2 Boost Volume", AC_ADC_MIC_CTL, + MIC2_BOOST, 0x07, 0, mic_boost_tlv), +}; + +/* Enable I2S & DAC clk, then enable the DAC digital part */ +static int acx00_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, AC_SYS_CLK_CTL, + (0x1<dapm); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, AC_SYS_CLK_CTL, + (0x1<spk_gpio, 1); +} + +static int acx00_lineout_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); + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!priv->enable) { + snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, + (1<codec, AC_LINEOUT_CTL, + (1<enable = 1; + } +#ifdef ACX00_DAPM_LINEOUT + snd_soc_update_bits(codec, AC_LINEOUT_CTL, + (1<spk_gpio_used) { + if (spk_delay == 0) { + gpio_set_value(priv->spk_gpio, 1); + /* + * time delay to wait spk pa work fine, + * general setting 50ms + */ + mdelay(50); + } else + schedule_delayed_work(&priv->spk_work, + msecs_to_jiffies(spk_delay)); + } + break; + case SND_SOC_DAPM_PRE_PMD: + mdelay(50); + if (priv->spk_gpio_used) { + gpio_set_value(priv->spk_gpio, 0); + msleep(50); + } +#ifdef ACX00_DAPM_LINEOUT + snd_soc_update_bits(codec, AC_LINEOUT_CTL, + (1<acx00, 0x0010, 0x03); + acx00_reg_write(priv->acx00, 0x0012, 0x01); + /* The bit3 need to setup to 1 for bias current. */ + snd_soc_update_bits(codec, AC_MICBIAS_CTL, + (0x1 << ADDA_BIAS_CUR), (0x1 << ADDA_BIAS_CUR)); + + /* enable the output & global enable bit */ + snd_soc_update_bits(codec, AC_I2S_CTL, + (1<spk_gpio_used) { + snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, + (1<codec, AC_LINEOUT_CTL, + (1<enable = 1; + } +#ifndef ACX00_DAPM_LINEOUT + snd_soc_update_bits(codec, AC_LINEOUT_CTL, (1<codec; + int i; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + snd_soc_update_bits(codec, AC_I2S_FMT0, + (7<dev, "unrecognized format support\n"); + break; + } + for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++) { + if (sample_rate_conv[i].samplerate == params_rate(params)) { + snd_soc_update_bits(codec, AC_SYS_SR_CTL, + (SYS_SR_MASK<codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + /* codec clk & FRM master */ + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, AC_I2S_CLK, + (0x1<dev, "format setting failed\n"); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + snd_soc_update_bits(codec, AC_I2S_FMT1, + (0x1<dev, "invert clk setting failed\n"); + return -EINVAL; + } + return 0; +} + +static int acx00_codec_dai_set_clkdiv(struct snd_soc_dai *codec_dai, + int clk_id, int clk_div) +{ + struct acx00_priv *priv = snd_soc_dai_get_drvdata(codec_dai); + struct snd_soc_codec *codec = priv->codec; + unsigned int bclk_div; + /* + * when PCM mode, setting as 64fs, when I2S mode as 32fs, + * then two channel, then just as 64fs + */ + unsigned int div_ratio = clk_div / 64; + + switch (div_ratio) { + case 1: + bclk_div = I2S_BCLK_DIV_1; + break; + case 2: + bclk_div = I2S_BCLK_DIV_2; + break; + case 4: + bclk_div = I2S_BCLK_DIV_3; + break; + case 6: + bclk_div = I2S_BCLK_DIV_4; + break; + case 8: + bclk_div = I2S_BCLK_DIV_5; + break; + case 12: + bclk_div = I2S_BCLK_DIV_6; + break; + case 16: + bclk_div = I2S_BCLK_DIV_7; + break; + case 24: + bclk_div = I2S_BCLK_DIV_8; + break; + case 32: + bclk_div = I2S_BCLK_DIV_9; + break; + case 48: + bclk_div = I2S_BCLK_DIV_10; + break; + case 64: + bclk_div = I2S_BCLK_DIV_11; + break; + case 96: + bclk_div = I2S_BCLK_DIV_12; + break; + case 128: + bclk_div = I2S_BCLK_DIV_13; + break; + case 176: + bclk_div = I2S_BCLK_DIV_14; + break; + case 192: + bclk_div = I2S_BCLK_DIV_15; + break; + default: + dev_err(codec->dev, "setting blck div failed\n"); + break; + } + + snd_soc_update_bits(codec, AC_I2S_CLK, + (I2S_BCLK_DIV_MASK<codec, AC_SYS_CLK_CTL, + (0x1<codec, AC_SYS_MOD_RST, + (0x1<stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (acx00_loop_en) + snd_soc_update_bits(codec_dai->codec, AC_I2S_FMT0, + (0x1<codec, AC_I2S_FMT0, + (0x1<codec, 1); + } else + acx00_codec_rxctrl_enable(codec_dai->codec, 1); + return 0; +} + +static int acx00_codec_digital_mute(struct snd_soc_dai *codec_dai, + int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + if (mute) + snd_soc_write(codec, AC_I2S_DAC_VOL, 0); + else + snd_soc_write(codec, AC_I2S_DAC_VOL, ACX00_DEF_VOL); + return 0; +} + +static void acx00_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + acx00_codec_txctrl_enable(codec, 0); + else + acx00_codec_rxctrl_enable(codec, 0); +} + +static const struct snd_soc_dai_ops acx00_codec_dai_ops = { + .hw_params = acx00_codec_hw_params, + .shutdown = acx00_codec_shutdown, + .digital_mute = acx00_codec_digital_mute, + .set_sysclk = acx00_codec_dai_set_sysclk, + .set_fmt = acx00_codec_dai_set_fmt, + .set_clkdiv = acx00_codec_dai_set_clkdiv, + .startup = acx00_codec_startup, + .trigger = acx00_codec_trigger, + .prepare = acx00_codec_prepare, +}; + +static struct snd_soc_dai_driver acx00_codec_dai[] = { + { + .name = "acx00-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 + | SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 + | SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &acx00_codec_dai_ops, + }, +}; + +static void acx00_codec_resume_work(struct work_struct *work) +{ + struct acx00_priv *priv = container_of(work, + struct acx00_priv, resume_work.work); + + acx00_codec_init(priv->codec); +} + +static int acx00_codec_probe(struct snd_soc_codec *codec) +{ + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + int ret = 0; + + mutex_init(&priv->mutex); + + priv->codec = codec; + + /* Add virtual switch */ + ret = snd_soc_add_codec_controls(codec, acx00_codec_controls, + ARRAY_SIZE(acx00_codec_controls)); + if (ret) { + pr_err("[audio-codec] Failed to register audio mode control, will continue without it.\n"); + } + snd_soc_dapm_new_controls(dapm, acx00_codec_dapm_widgets, ARRAY_SIZE(acx00_codec_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, acx00_codec_dapm_routes, ARRAY_SIZE(acx00_codec_dapm_routes)); + + /* using late_initcall to wait 120ms acx00-core to make chip reset */ + acx00_codec_init(codec); + INIT_DELAYED_WORK(&priv->spk_work, acx00_spk_enable); + INIT_DELAYED_WORK(&priv->resume_work, acx00_codec_resume_work); + return 0; +} + +static int acx00_codec_remove(struct snd_soc_codec *codec) +{ + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + cancel_delayed_work_sync(&priv->spk_work); + cancel_delayed_work_sync(&priv->resume_work); + return 0; +} + +static unsigned int acx00_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int data; + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + /* Device I/O API */ + data = acx00_reg_read(priv->acx00, reg); + return data; +} + +static int acx00_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + return acx00_reg_write(priv->acx00, reg, value); +} + +static int sunxi_gpio_iodisable(u32 gpio) +{ + char pin_name[8]; + u32 config, ret; + + sunxi_gpio_to_name(gpio, pin_name); + config = 7 << 16; + ret = pin_config_set(SUNXI_PINCTRL, pin_name, config); + return ret; +} + +static int acx00_codec_suspend(struct snd_soc_codec *codec) +{ + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + pr_debug("Enter %s\n", __func__); + + clk_disable_unprepare(priv->clk); + + /* PA_CTRL first setting low state, then make it iodisabled */ + if (priv->spk_gpio_used) { + sunxi_gpio_iodisable(priv->spk_gpio); + msleep(30); + } + + /* + * when codec suspend, then the register reset, if auto reset produce + * Pop & Click noise, then we should cut down the LINEOUT in this town. + */ + if (priv->enable) { + snd_soc_update_bits(codec, AC_LINEOUT_CTL, + (1<codec, AC_LINEOUT_CTL, + (1<codec, AC_LINEOUT_CTL, + (1<enable = 0; + } + + pr_debug("Exit %s\n", __func__); + + return 0; +} + +static int acx00_codec_resume(struct snd_soc_codec *codec) +{ + struct acx00_priv *priv = snd_soc_codec_get_drvdata(codec); + + pr_debug("Enter %s\n", __func__); + + if (clk_prepare_enable(priv->clk)) { + dev_err(codec->dev, "codec resume clk failed\n"); + return -EBUSY; + } + + schedule_delayed_work(&priv->resume_work, msecs_to_jiffies(300)); + + if (priv->spk_gpio_used) { + gpio_direction_output(priv->spk_gpio, 1); + gpio_set_value(priv->spk_gpio, 0); + } + + pr_debug("Exit %s\n", __func__); + + return 0; +} + + +static int acx00_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + codec->component.dapm.bias_level = level; + return 0; +} + +struct label { + const char *name; + int value; +}; + +#define LABEL(constant) { #constant, constant } +#define LABEL_END { NULL, -1 } + +static struct label reg_labels[] = { + LABEL(AC_SYS_CLK_CTL), + LABEL(AC_SYS_MOD_RST), + LABEL(AC_SYS_SR_CTL), + LABEL(AC_I2S_CTL), + LABEL(AC_I2S_CLK), + LABEL(AC_I2S_FMT0), + LABEL(AC_I2S_FMT1), + LABEL(AC_I2S_MIXER_SRC), + LABEL(AC_I2S_MIXER_GAIN), + LABEL(AC_I2S_DAC_VOL), + LABEL(AC_I2S_ADC_VOL), + LABEL(AC_DAC_CTL), + LABEL(AC_DAC_MIXER_SRC), + LABEL(AC_DAC_MIXER_GAIN), + LABEL(AC_OUT_MIXER_CTL), + LABEL(AC_OUT_MIXER_SRC), + LABEL(AC_LINEOUT_CTL), + LABEL(AC_ADC_CTL), + LABEL(AC_MICBIAS_CTL), + LABEL(AC_ADC_MIC_CTL), + LABEL(AC_ADC_MIXER_SRC), + LABEL(AC_BIAS_CTL), + LABEL(AC_ANALOG_PROF_CTL), + LABEL_END, +}; + +static ssize_t show_audio_reg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acx00_priv *priv = dev_get_drvdata(dev); + int count = 0, i = 0; + unsigned int reg_val; + + count += sprintf(buf, "dump audio reg:\n"); + + while (reg_labels[i].name != NULL) { + reg_val = acx00_reg_read(priv->acx00, reg_labels[i].value); + count += sprintf(buf + count, "%s 0x%x: 0x%04x\n", + reg_labels[i].name, (reg_labels[i].value), reg_val); + i++; + } + + return count; +} + +/* + * 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 acx00_priv *priv = 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) { + 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 (rw_flag) { + acx00_reg_write(priv->acx00, input_reg_offset, input_reg_val); + } else { + input_reg_val = acx00_reg_read(priv->acx00, input_reg_offset); + dev_info(dev, "\n\n Reg[0x%x] : 0x%04x\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 struct snd_soc_codec_driver soc_codec_driver_acx00 = { + .probe = acx00_codec_probe, + .remove = acx00_codec_remove, + .suspend = acx00_codec_suspend, + .resume = acx00_codec_resume, + .read = acx00_codec_read, + .write = acx00_codec_write, + .ignore_pmdown_time = 1, + .set_bias_level = acx00_codec_set_bias_level, +}; + +/* through acx00 is part of mfd devices, after the mfd */ +static int acx00_codec_dev_probe(struct platform_device *pdev) +{ + struct acx00_priv *priv; + int ret; + struct device_node *np = of_find_compatible_node(NULL, NULL, + "allwinner,ac200_codec"); + + priv = devm_kzalloc(&pdev->dev, sizeof(struct acx00_priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "acx00 codec priv mem alloc failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, priv); + priv->acx00 = dev_get_drvdata(pdev->dev.parent); + + if (np) { + ret = of_get_named_gpio(np, "gpio-spk", 0); + if (ret >= 0) { + priv->spk_gpio_used = 1; + priv->spk_gpio = ret; + if (!gpio_is_valid(priv->spk_gpio)) { + dev_err(&pdev->dev, "gpio-spk is valid\n"); + ret = -EINVAL; + goto err_devm_kfree; + } else { + ret = devm_gpio_request(&pdev->dev, + priv->spk_gpio, "SPK"); + if (ret) { + dev_err(&pdev->dev, + "failed request gpio-spk\n"); + ret = -EBUSY; + goto err_devm_kfree; + } else { + gpio_direction_output(priv->spk_gpio, + 1); + gpio_set_value(priv->spk_gpio, 0); + } + } + } else { + priv->spk_gpio_used = 0; + } + } + + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_driver_acx00, + acx00_codec_dai, ARRAY_SIZE(acx00_codec_dai)); + + if (ret < 0) + dev_err(&pdev->dev, "Failed register acx00: %d\n", ret); + + 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_devm_kfree: + devm_kfree(&pdev->dev, priv); + return ret; +} + +/* Mark this space to clear the LINEOUT & gpio */ +static void acx00_codec_dev_shutdown(struct platform_device *pdev) +{ + struct acx00_priv *priv = platform_get_drvdata(pdev); + + if (priv->spk_gpio_used) + gpio_set_value(priv->spk_gpio, 0); +} + +static int acx00_codec_dev_remove(struct platform_device *pdev) +{ + struct acx00_priv *priv = platform_get_drvdata(pdev); + +#ifndef ACX00_DAPM_LINEOUT + snd_soc_update_bits(priv->codec, AC_LINEOUT_CTL, + (1<dev); + clk_disable_unprepare(priv->clk); + devm_kfree(&pdev->dev, priv); + return 0; +} + +static struct platform_driver acx00_codec_driver = { + .driver = { + .name = "acx00-codec", + .owner = THIS_MODULE, + }, + .probe = acx00_codec_dev_probe, + .remove = acx00_codec_dev_remove, + .shutdown = acx00_codec_dev_shutdown, +}; + +static int __init acx00_codec_driver_init(void) +{ + return platform_driver_register(&acx00_codec_driver); +} + +static void __exit acx00_codec_driver_exit(void) +{ + platform_driver_unregister(&acx00_codec_driver); +} +late_initcall(acx00_codec_driver_init); +module_exit(acx00_codec_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SUNXI ASoC ACX00 Codec Driver"); +MODULE_AUTHOR("wolfgang huang"); +MODULE_ALIAS("platform:acx00-codec");