/* * ak4618.c - AK4618 Audio Codec driver supporting AK4618 * * Copyright (c) 2013-2014 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ak4618.h" /* codec private data */ struct ak4618_priv { struct regmap *regmap; int sysclk; }; /* * AK4618 volume etc. controls */ static const DECLARE_TLV_DB_SCALE(ak4618_dac_volume_tlv, -12700, 50, 1); /* * Mic amp gain control: * 0 dB and from 15 to 33 dB in 3 dB steps * Displayed gain will be 21 dB */ static DECLARE_TLV_DB_SCALE(ak4618_mic_amp_tlv, 0, 300, 0); static const struct snd_kcontrol_new ak4618_snd_controls[] = { /* DAC volume control */ SOC_DOUBLE_R_TLV("DAC1 Volume", AK4618_DAC1L_VOLUME, AK4618_DAC1R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), SOC_DOUBLE_R_TLV("DAC2 Volume", AK4618_DAC2L_VOLUME, AK4618_DAC2R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), SOC_DOUBLE_R_TLV("DAC3 Volume", AK4618_DAC3L_VOLUME, AK4618_DAC3R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), SOC_DOUBLE_R_TLV("DAC4 Volume", AK4618_DAC4L_VOLUME, AK4618_DAC4R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), SOC_DOUBLE_R_TLV("DAC5 Volume", AK4618_DAC5L_VOLUME, AK4618_DAC5R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), SOC_DOUBLE_R_TLV("DAC6 Volume", AK4618_DAC6L_VOLUME, AK4618_DAC6R_VOLUME, 0, 0xFF, 1, ak4618_dac_volume_tlv), /* Mic amplifier gain */ SOC_SINGLE_TLV("MIC1 Volume", AK4618_MICROPHONE_GAIN_0, 0, 0x7, 0, ak4618_mic_amp_tlv), SOC_SINGLE_TLV("MIC2 Volume", AK4618_MICROPHONE_GAIN_0, 3, 0x7, 0, ak4618_mic_amp_tlv), SOC_SINGLE_TLV("MIC3 Volume", AK4618_MICROPHONE_GAIN_1, 0, 0x7, 0, ak4618_mic_amp_tlv), SOC_SINGLE_TLV("MIC4 Volume", AK4618_MICROPHONE_GAIN_1, 3, 0x7, 0, ak4618_mic_amp_tlv), SOC_SINGLE_TLV("MIC5 Volume", AK4618_MICROPHONE_GAIN_2, 0, 0x7, 0, ak4618_mic_amp_tlv), SOC_SINGLE_TLV("MIC6 Volume", AK4618_MICROPHONE_GAIN_2, 3, 0x7, 0, ak4618_mic_amp_tlv), }; static const struct snd_soc_dapm_widget ak4618_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC", NULL, AK4618_POWER_MANAGEMENT_1, 4, 0), SND_SOC_DAPM_ADC("ADC", NULL, AK4618_POWER_MANAGEMENT_1, 5, 0), /* Mic Bias */ SND_SOC_DAPM_SUPPLY("MICBIAS", AK4618_POWER_MANAGEMENT_1, 6, 0, NULL, 0), /* Outputs */ SND_SOC_DAPM_OUTPUT("DACOUT1"), SND_SOC_DAPM_OUTPUT("DACOUT2"), SND_SOC_DAPM_OUTPUT("DACOUT3"), SND_SOC_DAPM_OUTPUT("DACOUT4"), SND_SOC_DAPM_OUTPUT("DACOUT5"), SND_SOC_DAPM_OUTPUT("DACOUT6"), /* Inputs */ SND_SOC_DAPM_INPUT("IN1"), SND_SOC_DAPM_INPUT("IN2"), SND_SOC_DAPM_INPUT("IN3"), SND_SOC_DAPM_INPUT("IN4"), SND_SOC_DAPM_INPUT("IN5"), SND_SOC_DAPM_INPUT("IN6"), }; static const struct snd_soc_dapm_route audio_paths[] = { { "ADC", NULL, "IN1" }, { "ADC", NULL, "IN2" }, { "ADC", NULL, "IN3" }, { "ADC", NULL, "IN4" }, { "ADC", NULL, "IN5" }, { "ADC", NULL, "IN6" }, { "AIFOUT", NULL, "ADC"}, { "DAC", NULL, "AIFIN"}, { "DACOUT1", NULL, "DAC" }, { "DACOUT2", NULL, "DAC" }, { "DACOUT3", NULL, "DAC" }, { "DACOUT4", NULL, "DAC" }, { "DACOUT5", NULL, "DAC" }, { "DACOUT6", NULL, "DAC" }, }; /* * DAI ops entries */ static int ak4618_mute(struct snd_soc_dai *dai, int mute) { struct ak4618_priv *ak4618 = snd_soc_codec_get_drvdata(dai->codec); regmap_update_bits(ak4618->regmap, AK4618_SOFT_MUTE, AK4618_SOFT_MUTE_MASK, mute ? AK4618_SOFT_MUTE_ENABLE : AK4618_SOFT_UNMUTE_ENABLE); return 0; } static int ak4618_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int width) { struct ak4618_priv *ak4618 = snd_soc_codec_get_drvdata(dai->codec); unsigned int dac_power, adc_power, tdm_format; dac_power = 0; adc_power = 0; tdm_format = 0; switch (slots) { case 12: dac_power |= AK4618_PM_DAC6_ON; /* fall-through */ case 10: dac_power |= AK4618_PM_DAC5_ON; /* fall-through */ case 8: dac_power |= AK4618_PM_DAC4_ON; /* fall-through */ case 6: dac_power |= AK4618_PM_DAC3_ON; adc_power |= AK4618_PM_ADC56_ON; /* fall-through */ case 4: dac_power |= AK4618_PM_DAC2_ON; adc_power |= AK4618_PM_ADC34_ON; /* fall-through */ case 2: dac_power |= AK4618_PM_DAC1_ON; adc_power |= AK4618_PM_ADC12_ON; break; default: return -EINVAL; } if (slots > 8) tdm_format = AK4618_FMT_TDM_512; else if (slots > 4) tdm_format = AK4618_FMT_TDM_256; else tdm_format = AK4618_FMT_TDM_128; regmap_update_bits(ak4618->regmap, AK4618_POWER_MANAGEMENT_2, AK4618_PM_DAC_ON_MASK, dac_power); regmap_update_bits(ak4618->regmap, AK4618_POWER_MANAGEMENT_1, AK4618_PM_ADC_ON_MASK, adc_power); regmap_update_bits(ak4618->regmap, AK4618_AUDIO_INTERFACE_FORMAT, AK4618_FMT_TDM_FORMAT_MASK, tdm_format); return 0; } static int ak4618_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct ak4618_priv *ak4618; unsigned int format = 0; ak4618 = snd_soc_codec_get_drvdata(codec_dai->codec); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_DSP_A: format = AK4618_FMT_I2S; break; default: return -EINVAL; } regmap_update_bits(ak4618->regmap, AK4618_AUDIO_INTERFACE_FORMAT, AK4618_FMT_FORMAT_MASK, format); switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ regmap_update_bits(ak4618->regmap, AK4618_POWER_MANAGEMENT_1, AK4618_MODE_SELECT_MASK, AK4618_MASTER_MODE); regmap_update_bits(ak4618->regmap, AK4618_SYSTEM_CLOCK, AK4618_SYS_CLK_AUTO_MASK, AK4618_SYS_CLK_MANUAL); break; case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ regmap_update_bits(ak4618->regmap, AK4618_POWER_MANAGEMENT_1, AK4618_MODE_SELECT_MASK, AK4618_SLAVE_MODE); regmap_update_bits(ak4618->regmap, AK4618_SYSTEM_CLOCK, AK4618_SYS_CLK_AUTO_MASK, AK4618_SYS_CLK_AUTO); break; default: return -EINVAL; } return 0; } static int ak4618_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct ak4618_priv *ak4618 = snd_soc_codec_get_drvdata(codec); switch (freq) { case 8192000: case 11289600: case 12288000: case 16934400: case 16384000: case 18432000: case 22579200: case 24576000: case 33868800: case 36864000: ak4618->sysclk = freq; break; default: return -EINVAL; } return 0; } static int ak4618_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { int srate = 0, system_clk = 0; struct snd_soc_codec *codec = dai->codec; struct ak4618_priv *ak4618 = snd_soc_codec_get_drvdata(codec); srate = params_rate(params); switch (ak4618->sysclk / srate) { case 256: system_clk = AK4618_SYS_CLK_MCLK_SEL_256; break; case 384: system_clk = AK4618_SYS_CLK_MCLK_SEL_384; break; case 512: system_clk = AK4618_SYS_CLK_MCLK_SEL_512; break; default: return -EINVAL; } if (srate > 96000) system_clk |= AK4618_SYS_CLK_SAMPLING_RATE_QUAD; else if (srate > 48000) system_clk |= AK4618_SYS_CLK_SAMPLING_RATE_DOUBLE; else system_clk |= AK4618_SYS_CLK_SAMPLING_RATE_NORMAL; regmap_update_bits(ak4618->regmap, AK4618_SYSTEM_CLOCK, AK4618_SYS_CLK_MASK, system_clk); return 0; } static const struct snd_soc_dai_ops ak4618_dai_ops = { .hw_params = ak4618_hw_params, .digital_mute = ak4618_mute, .set_tdm_slot = ak4618_set_tdm_slot, .set_sysclk = ak4618_set_dai_sysclk, .set_fmt = ak4618_set_dai_fmt, }; /* codec DAI instance */ static struct snd_soc_dai_driver ak4618_dai = { .name = "ak4618-hifi", .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, }, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, }, .ops = &ak4618_dai_ops, }; static int ak4618_probe(struct snd_soc_codec *codec) { struct ak4618_priv *ak4618 = snd_soc_codec_get_drvdata(codec); int ret; codec->control_data = ak4618->regmap; ret = snd_soc_codec_set_cache_io(codec, 0, 0, SND_SOC_REGMAP); if (ret < 0) { dev_err(codec->dev, "failed to set cache I/O: %d\n", ret); return ret; } /* default setting for ak4618 */ /* release reset, power down dac, adc, slave mode, power down adc3-6 */ regmap_write(ak4618->regmap, AK4618_POWER_MANAGEMENT_1, 0x03); /* power down dac2-6 */ regmap_write(ak4618->regmap, AK4618_POWER_MANAGEMENT_2, 0x01); /* Master Clock Frequency Auto Setting Mode Enable for slave mode */ regmap_write(ak4618->regmap, AK4618_SYSTEM_CLOCK, 0x81); /* stereo mode */ regmap_write(ak4618->regmap, AK4618_AUDIO_INTERFACE_FORMAT, 0x04); /* MIC amp */ regmap_write(ak4618->regmap, AK4618_MICROPHONE_GAIN_0, 0x00); return ret; } static struct snd_soc_codec_driver soc_codec_dev_ak4618 = { .probe = ak4618_probe, .controls = ak4618_snd_controls, .num_controls = ARRAY_SIZE(ak4618_snd_controls), .dapm_widgets = ak4618_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(ak4618_dapm_widgets), .dapm_routes = audio_paths, .num_dapm_routes = ARRAY_SIZE(audio_paths), }; static const struct regmap_config ak4618_i2c_regmap_config = { .val_bits = 8, .reg_bits = 8, .max_register = AK4618_NUM_REGS - 1, }; static int ak4618_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ak4618_priv *ak4618; ak4618 = devm_kzalloc(&client->dev, sizeof(struct ak4618_priv), GFP_KERNEL); if (ak4618 == NULL) return -ENOMEM; ak4618->regmap = devm_regmap_init_i2c(client, &ak4618_i2c_regmap_config); if (IS_ERR(ak4618->regmap)) return PTR_ERR(ak4618->regmap); i2c_set_clientdata(client, ak4618); return snd_soc_register_codec(&client->dev, &soc_codec_dev_ak4618, &ak4618_dai, 1); } static int ak4618_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); return 0; } static const struct i2c_device_id ak4618_id[] = { { "ak4618", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ak4618_id); static const struct of_device_id ak4618_of_match[] = { { .compatible = "akm,ak4618", }, { } }; MODULE_DEVICE_TABLE(of, ak4618_of_match); static struct i2c_driver ak4618_i2c_driver = { .driver = { .name = "ak4618", .owner = THIS_MODULE, .of_match_table = ak4618_of_match, }, .probe = ak4618_i2c_probe, .remove = ak4618_i2c_remove, .id_table = ak4618_id, }; module_i2c_driver(ak4618_i2c_driver); MODULE_DESCRIPTION("ASoC ak4618 driver"); MODULE_AUTHOR("Songhee Baek "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ak4618-codec");