/* * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipq-adss.h" #include "ipq-codec.h" struct audio_hw_params audio_params; static struct regmap *akd4613_regmap; static const u8 akd4613_reg[AK4613_MAX_REG] = { 0x0F, 0x07, 0x3F, 0x20, 0x20, 0x55, 0x05, 0x07, 0x0F, 0x07, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; uint8_t ipq_compare_hw_params(struct audio_hw_params *curr_params) { if ((curr_params->bit_width == audio_params.bit_width) && (curr_params->freq == audio_params.freq) && (curr_params->channels == audio_params.channels)) return 0; else return -EINVAL; } /* DFS : Sampling Speed * * DFS1 DFS0 Sampling Speed Mode (fs) * 0 0 Normal Speed Mode 32kHz~48kHz (default) * 0 1 Double Speed Mode 64kHz~96kHz * 1 0 Quad Speed Mode 128kHz~192kHz * 1 1 N/A - */ static int ipq_codec_i2c_set_dfs(struct snd_soc_codec *codec, int mode) { uint32_t reg; if (mode > QUAD_SPEED) { pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); return -EINVAL; } reg = snd_soc_read(codec, AKD4613_04_CTRL2); reg &= ~(AKD4613_DFS_MASK); reg |= AKD4613_DFS(mode); snd_soc_write(codec, AKD4613_04_CTRL2, reg); return 0; } /* CKS : Master Clock Input Frequency Select * * CKS1 CKS0 Normal Speed Double Speed Quad Speed * 0 0 256fs 256fs 128fs * 0 1 384fs 256fs 128fs * 1 0 512fs 256fs 128fs (default) * 1 1 512fs 256fs 128fs */ static int ipq_codec_i2c_set_cks(struct snd_soc_codec *codec, int config, int mode) { uint32_t cks_val; uint32_t reg; if (mode == NORMAL_SPEED) { if (config == FS_256) cks_val = 0; else if (config == FS_384) cks_val = 1; else if (config == FS_512) cks_val = 2; else cks_val = -EINVAL; } else if (mode == DOUBLE_SPEED) { if (config == FS_256) cks_val = 2; else cks_val = -EINVAL; } else if (mode == QUAD_SPEED) { if (config == FS_128) cks_val = 2; else cks_val = -EINVAL; } else { pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); return -EINVAL; } if (cks_val < 0) { pr_err("%s: %d: Invalid CKS config", __func__, __LINE__); return cks_val; } reg = snd_soc_read(codec, AKD4613_04_CTRL2); reg &= ~(AKD4613_CKS_MASK); reg |= AKD4613_CKS(cks_val); snd_soc_write(codec, AKD4613_04_CTRL2, reg); return 0; } static int ipq_codec_i2c_set_tdm_mode(struct snd_soc_codec *codec, int tdm_mode) { uint32_t reg; if (tdm_mode >= TDM_MAX) { pr_err("%s: %d: Invalid DFS mode", __func__, __LINE__); return -EINVAL; } reg = snd_soc_read(codec, AKD4613_03_CTRL1); reg &= ~(AKD4613_TDM_MODE_MASK); reg |= AKD4613_TDM_MODE(tdm_mode); snd_soc_write(codec, AKD4613_03_CTRL1, reg); return 0; } static int ipq_codec_i2c_set_dif(struct snd_soc_codec *codec, int dif_val) { uint32_t reg; reg = snd_soc_read(codec, AKD4613_03_CTRL1); reg &= ~(AKD4613_DIF_MASK); reg |= AKD4613_DIF(dif_val); snd_soc_write(codec, AKD4613_03_CTRL1, reg); return 0; } static void ipq_codec_i2c_write_defaults(struct snd_soc_codec *codec) { int i; for (i = 0; i < AK4613_MAX_REG; i++) snd_soc_write(codec, i, akd4613_reg[i]); udelay(10); } static int ipq_codec_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec; codec = dai->codec; /* I2S and TDM cannot co-exist. CPU DAI startup would * have already checked this case, by this time. */ if (!dai->active) ipq_codec_i2c_write_defaults(codec); return 0; } static int ipq_codec_audio_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_codec *codec; int samp_rate = params_rate(params); u32 bit_width = params_format(params); int channels = params_channels(params); int bit_act; int dfs, cks, tdm_mode, dif; uint32_t intf = dai->driver->id; struct audio_hw_params curr_params; bit_act = ipq_get_act_bit_width(bit_width); if (bit_act == __BIT_INVAL) return -EINVAL; curr_params.freq = samp_rate; curr_params.channels = channels; curr_params.bit_width = bit_act; codec = dai->codec; /* * Since CLKS in the codec are shared by I2S TX and RX channels, * Rx and Tx when used simulatneoulsy will have to use the same channel, * sampling frequency and bit widths. So compare the settings and then * update the codec settings. */ if (dai->active > 1) { if (ipq_compare_hw_params(&curr_params)) { /* Playback and capture settings do not match */ pr_err("\nPlayback & capture settings do not match\n"); return -EINVAL; } /* Settings match, codec settings are already done*/ return 0; } audio_params.freq = samp_rate; audio_params.channels = channels; audio_params.bit_width = bit_act; if (intf == I2S) { /* default values */ dfs = NORMAL_SPEED; cks = FS_512; tdm_mode = STEREO; dif = DIF_I2S_MODE; } else if (intf == TDM) { /* Codec settings for 8 channels */ dfs = DOUBLE_SPEED; cks = FS_256; tdm_mode = TDM_256; dif = DIF_LR_MODE3; } else { pr_err("\n%s Invalid interface\n", __func__); return -EINVAL; } ipq_codec_i2c_set_dfs(codec, dfs); ipq_codec_i2c_set_cks(codec, cks, dfs); ipq_codec_i2c_set_tdm_mode(codec, tdm_mode); ipq_codec_i2c_set_dif(codec, dif); udelay(10); return 0; } static int ipq_codec_audio_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); return 0; } static void ipq_codec_audio_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { dev_dbg(dai->dev, "%s:%d\n", __func__, __LINE__); } static struct snd_soc_dai_ops ipq_codec_audio_ops = { .startup = ipq_codec_audio_startup, .hw_params = ipq_codec_audio_hw_params, .prepare = ipq_codec_audio_prepare, .shutdown = ipq_codec_audio_shutdown, }; static struct snd_soc_dai_driver ipq4019_codec_dais[] = { { .name = "qca-i2s-codec-dai", .playback = { .stream_name = "qca-i2s-playback", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .capture = { .stream_name = "qca-i2s-capture", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .ops = &ipq_codec_audio_ops, .id = I2S, }, { .name = "qca-tdm-codec-dai", .playback = { .stream_name = "qca-tdm-playback", .channels_min = CH_STEREO, .channels_max = CH_7_1, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .capture = { .stream_name = "qca-tdm-capture", .channels_min = CH_STEREO, .channels_max = CH_7_1, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .ops = &ipq_codec_audio_ops, .id = TDM, }, { .name = "qca-i2s1-codec-dai", .playback = { .stream_name = "qca-i2s1-playback", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, }, { .name = "qca-i2s2-codec-dai", .playback = { .stream_name = "qca-i2s2-playback", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, }, { .name = "qca-spdif-codec-dai", .playback = { .stream_name = "qca-spdif-playback", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S24_3, }, .capture = { .stream_name = "qca-spdif-capture", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S24_3, }, }, }; static struct snd_soc_dai_driver ipq8074_codec_dais[] = { { .name = "qca-i2s-codec-dai", .playback = { .stream_name = "qca-i2s-playback", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .capture = { .stream_name = "qca-i2s-capture", .channels_min = CH_STEREO, .channels_max = CH_STEREO, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .ops = &ipq_codec_audio_ops, .id = I2S, }, { .name = "qca-tdm-codec-dai", .playback = { .stream_name = "qca-tdm-playback", .channels_min = CH_STEREO, .channels_max = CH_7_1, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .capture = { .stream_name = "qca-tdm-capture", .channels_min = CH_STEREO, .channels_max = CH_7_1, .rates = RATE_16000_96000, .formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32, }, .ops = &ipq_codec_audio_ops, .id = TDM, }, }; static int ipq_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { return -ENOTSUPP; } static const struct snd_kcontrol_new vol_ctrl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "playback volume", .access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE), .info = ipq_info, }; static const struct regmap_config akd4613_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = AK4613_MAX_REG, .cache_type = REGCACHE_RBTREE, }; static int ipq_codec_probe(struct snd_soc_codec *codec) { snd_soc_codec_init_regmap(codec, akd4613_regmap); return 0; } static int ipq_codec_remove(struct snd_soc_codec *codec) { snd_soc_codec_exit_regmap(codec); return 0; } static const struct snd_soc_codec_driver ipq_codec = { .probe = ipq_codec_probe, .remove = ipq_codec_remove, .num_controls = 0, .reg_cache_size = ARRAY_SIZE(akd4613_reg), .reg_word_size = sizeof(u8), .reg_cache_default = akd4613_reg, }; static const struct of_device_id ipq_codec_of_match[] = { { .compatible = "qca,ipq4019-codec", .data = (void *)IPQ4019 }, { .compatible = "qca,ipq8074-codec", .data = (void *)IPQ8074 }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, ipq_codec_of_match); static int ipq_codec_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct device *dev = &i2c->dev; int ret; const struct of_device_id *match; enum ipq_hw_type ipq_hw; match = of_match_device(ipq_codec_of_match, dev); if (!match) return -ENODEV; ipq_hw = (enum ipq_hw_type)match->data; akd4613_regmap = devm_regmap_init_i2c(i2c, &akd4613_regmap_config); if (IS_ERR(akd4613_regmap)) { ret = PTR_ERR(akd4613_regmap); dev_err(&i2c->dev, "Failed to allocate register map: %d\n", ret); return ret; } dev_info(&i2c->dev, "i2c regmap done\n"); if (ipq_hw == IPQ4019) ret = snd_soc_register_codec(&i2c->dev, &ipq_codec, ipq4019_codec_dais, ARRAY_SIZE(ipq4019_codec_dais)); else ret = snd_soc_register_codec(&i2c->dev, &ipq_codec, ipq8074_codec_dais, ARRAY_SIZE(ipq8074_codec_dais)); if (ret < 0) dev_err(&i2c->dev, "snd_soc_register_codec failed (%d)\n", ret); return ret; } static int ipq_codec_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); return 0; } static const struct i2c_device_id ipq_codec_i2c_id[] = { { "qca_codec", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ipq_codec_i2c_id); static struct i2c_driver ipq_codec_i2c_driver = { .driver = { .name = "qca_codec", .owner = THIS_MODULE, .of_match_table = ipq_codec_of_match, }, .probe = ipq_codec_i2c_probe, .remove = ipq_codec_i2c_remove, .id_table = ipq_codec_i2c_id, }; static int ipq_codec_init(void) { int ret; ret = i2c_add_driver(&ipq_codec_i2c_driver); if (ret < 0) pr_err("%s: %d: Failed to add I2C driver", __func__, __LINE__); return ret; } module_init(ipq_codec_init); static void ipq_codec_exit(void) { i2c_del_driver(&ipq_codec_i2c_driver); } module_exit(ipq_codec_exit); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("IPQ Codec Driver");