1330345ebSAndrey Smirnov #include <linux/module.h> 2330345ebSAndrey Smirnov #include <linux/slab.h> 3330345ebSAndrey Smirnov #include <sound/pcm.h> 4330345ebSAndrey Smirnov #include <sound/pcm_params.h> 5330345ebSAndrey Smirnov #include <sound/soc.h> 6330345ebSAndrey Smirnov #include <sound/initval.h> 7330345ebSAndrey Smirnov 8330345ebSAndrey Smirnov #include <linux/i2c.h> 9330345ebSAndrey Smirnov 10330345ebSAndrey Smirnov #include <linux/mfd/si476x-core.h> 11330345ebSAndrey Smirnov 12330345ebSAndrey Smirnov enum si476x_audio_registers { 13330345ebSAndrey Smirnov SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, 14330345ebSAndrey Smirnov SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, 15330345ebSAndrey Smirnov }; 16330345ebSAndrey Smirnov 17330345ebSAndrey Smirnov enum si476x_digital_io_output_format { 18330345ebSAndrey Smirnov SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, 19330345ebSAndrey Smirnov SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, 20330345ebSAndrey Smirnov }; 21330345ebSAndrey Smirnov 22330345ebSAndrey Smirnov #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0b111 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ 23330345ebSAndrey Smirnov (0b111 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) 24330345ebSAndrey Smirnov #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0b1111110) 25330345ebSAndrey Smirnov 26330345ebSAndrey Smirnov enum si476x_daudio_formats { 27330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_I2S = (0x0 << 1), 28330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), 29330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), 30330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), 31330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), 32330345ebSAndrey Smirnov 33330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_IB = (1 << 5), 34330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_IF = (1 << 6), 35330345ebSAndrey Smirnov }; 36330345ebSAndrey Smirnov 37330345ebSAndrey Smirnov enum si476x_pcm_format { 38330345ebSAndrey Smirnov SI476X_PCM_FORMAT_S8 = 2, 39330345ebSAndrey Smirnov SI476X_PCM_FORMAT_S16_LE = 4, 40330345ebSAndrey Smirnov SI476X_PCM_FORMAT_S20_3LE = 5, 41330345ebSAndrey Smirnov SI476X_PCM_FORMAT_S24_LE = 6, 42330345ebSAndrey Smirnov }; 43330345ebSAndrey Smirnov 44330345ebSAndrey Smirnov static unsigned int si476x_codec_read(struct snd_soc_codec *codec, 45330345ebSAndrey Smirnov unsigned int reg) 46330345ebSAndrey Smirnov { 47330345ebSAndrey Smirnov int err; 48330345ebSAndrey Smirnov struct si476x_core *core = codec->control_data; 49330345ebSAndrey Smirnov 50330345ebSAndrey Smirnov si476x_core_lock(core); 51330345ebSAndrey Smirnov err = si476x_core_cmd_get_property(core, reg); 52330345ebSAndrey Smirnov si476x_core_unlock(core); 53330345ebSAndrey Smirnov 54330345ebSAndrey Smirnov return err; 55330345ebSAndrey Smirnov } 56330345ebSAndrey Smirnov 57330345ebSAndrey Smirnov static int si476x_codec_write(struct snd_soc_codec *codec, 58330345ebSAndrey Smirnov unsigned int reg, unsigned int val) 59330345ebSAndrey Smirnov { 60330345ebSAndrey Smirnov int err; 61330345ebSAndrey Smirnov struct si476x_core *core = codec->control_data; 62330345ebSAndrey Smirnov 63330345ebSAndrey Smirnov si476x_core_lock(core); 64330345ebSAndrey Smirnov err = si476x_core_cmd_set_property(core, reg, val); 65330345ebSAndrey Smirnov si476x_core_unlock(core); 66330345ebSAndrey Smirnov 67330345ebSAndrey Smirnov return err; 68330345ebSAndrey Smirnov } 69330345ebSAndrey Smirnov 70330345ebSAndrey Smirnov static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, 71330345ebSAndrey Smirnov unsigned int fmt) 72330345ebSAndrey Smirnov { 73330345ebSAndrey Smirnov int err; 74330345ebSAndrey Smirnov u16 format = 0; 75330345ebSAndrey Smirnov 76330345ebSAndrey Smirnov if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) 77330345ebSAndrey Smirnov return -EINVAL; 78330345ebSAndrey Smirnov 79330345ebSAndrey Smirnov switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 80330345ebSAndrey Smirnov case SND_SOC_DAIFMT_DSP_A: 81330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_DSP_A; 82330345ebSAndrey Smirnov break; 83330345ebSAndrey Smirnov case SND_SOC_DAIFMT_DSP_B: 84330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_DSP_B; 85330345ebSAndrey Smirnov break; 86330345ebSAndrey Smirnov case SND_SOC_DAIFMT_I2S: 87330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_I2S; 88330345ebSAndrey Smirnov break; 89330345ebSAndrey Smirnov case SND_SOC_DAIFMT_RIGHT_J: 90330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_RIGHT_J; 91330345ebSAndrey Smirnov break; 92330345ebSAndrey Smirnov case SND_SOC_DAIFMT_LEFT_J: 93330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_LEFT_J; 94330345ebSAndrey Smirnov break; 95330345ebSAndrey Smirnov default: 96330345ebSAndrey Smirnov return -EINVAL; 97330345ebSAndrey Smirnov } 98330345ebSAndrey Smirnov 99330345ebSAndrey Smirnov switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 100330345ebSAndrey Smirnov case SND_SOC_DAIFMT_DSP_A: 101330345ebSAndrey Smirnov case SND_SOC_DAIFMT_DSP_B: 102330345ebSAndrey Smirnov switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 103330345ebSAndrey Smirnov case SND_SOC_DAIFMT_NB_NF: 104330345ebSAndrey Smirnov break; 105330345ebSAndrey Smirnov case SND_SOC_DAIFMT_IB_NF: 106330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_IB; 107330345ebSAndrey Smirnov break; 108330345ebSAndrey Smirnov default: 109330345ebSAndrey Smirnov return -EINVAL; 110330345ebSAndrey Smirnov } 111330345ebSAndrey Smirnov break; 112330345ebSAndrey Smirnov case SND_SOC_DAIFMT_I2S: 113330345ebSAndrey Smirnov case SND_SOC_DAIFMT_RIGHT_J: 114330345ebSAndrey Smirnov case SND_SOC_DAIFMT_LEFT_J: 115330345ebSAndrey Smirnov switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 116330345ebSAndrey Smirnov case SND_SOC_DAIFMT_NB_NF: 117330345ebSAndrey Smirnov break; 118330345ebSAndrey Smirnov case SND_SOC_DAIFMT_IB_IF: 119330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_IB | 120330345ebSAndrey Smirnov SI476X_DAUDIO_MODE_IF; 121330345ebSAndrey Smirnov break; 122330345ebSAndrey Smirnov case SND_SOC_DAIFMT_IB_NF: 123330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_IB; 124330345ebSAndrey Smirnov break; 125330345ebSAndrey Smirnov case SND_SOC_DAIFMT_NB_IF: 126330345ebSAndrey Smirnov format |= SI476X_DAUDIO_MODE_IF; 127330345ebSAndrey Smirnov break; 128330345ebSAndrey Smirnov default: 129330345ebSAndrey Smirnov return -EINVAL; 130330345ebSAndrey Smirnov } 131330345ebSAndrey Smirnov break; 132330345ebSAndrey Smirnov default: 133330345ebSAndrey Smirnov return -EINVAL; 134330345ebSAndrey Smirnov } 135330345ebSAndrey Smirnov 136330345ebSAndrey Smirnov err = snd_soc_update_bits(codec_dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, 137330345ebSAndrey Smirnov SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, 138330345ebSAndrey Smirnov format); 139330345ebSAndrey Smirnov if (err < 0) { 140330345ebSAndrey Smirnov dev_err(codec_dai->codec->dev, "Failed to set output format\n"); 141330345ebSAndrey Smirnov return err; 142330345ebSAndrey Smirnov } 143330345ebSAndrey Smirnov 144330345ebSAndrey Smirnov return 0; 145330345ebSAndrey Smirnov } 146330345ebSAndrey Smirnov 147330345ebSAndrey Smirnov static int si476x_codec_hw_params(struct snd_pcm_substream *substream, 148330345ebSAndrey Smirnov struct snd_pcm_hw_params *params, 149330345ebSAndrey Smirnov struct snd_soc_dai *dai) 150330345ebSAndrey Smirnov { 151330345ebSAndrey Smirnov int rate, width, err; 152330345ebSAndrey Smirnov 153330345ebSAndrey Smirnov rate = params_rate(params); 154330345ebSAndrey Smirnov if (rate < 32000 || rate > 48000) { 155330345ebSAndrey Smirnov dev_err(dai->codec->dev, "Rate: %d is not supported\n", rate); 156330345ebSAndrey Smirnov return -EINVAL; 157330345ebSAndrey Smirnov } 158330345ebSAndrey Smirnov 159330345ebSAndrey Smirnov switch (params_format(params)) { 160330345ebSAndrey Smirnov case SNDRV_PCM_FORMAT_S8: 161330345ebSAndrey Smirnov width = SI476X_PCM_FORMAT_S8; 162330345ebSAndrey Smirnov case SNDRV_PCM_FORMAT_S16_LE: 163330345ebSAndrey Smirnov width = SI476X_PCM_FORMAT_S16_LE; 164330345ebSAndrey Smirnov break; 165330345ebSAndrey Smirnov case SNDRV_PCM_FORMAT_S20_3LE: 166330345ebSAndrey Smirnov width = SI476X_PCM_FORMAT_S20_3LE; 167330345ebSAndrey Smirnov break; 168330345ebSAndrey Smirnov case SNDRV_PCM_FORMAT_S24_LE: 169330345ebSAndrey Smirnov width = SI476X_PCM_FORMAT_S24_LE; 170330345ebSAndrey Smirnov break; 171330345ebSAndrey Smirnov default: 172330345ebSAndrey Smirnov return -EINVAL; 173330345ebSAndrey Smirnov } 174330345ebSAndrey Smirnov 175330345ebSAndrey Smirnov err = snd_soc_write(dai->codec, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, 176330345ebSAndrey Smirnov rate); 177330345ebSAndrey Smirnov if (err < 0) { 178330345ebSAndrey Smirnov dev_err(dai->codec->dev, "Failed to set sample rate\n"); 179330345ebSAndrey Smirnov return err; 180330345ebSAndrey Smirnov } 181330345ebSAndrey Smirnov 182330345ebSAndrey Smirnov err = snd_soc_update_bits(dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, 183330345ebSAndrey Smirnov SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, 184330345ebSAndrey Smirnov (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | 185330345ebSAndrey Smirnov (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); 186330345ebSAndrey Smirnov if (err < 0) { 187330345ebSAndrey Smirnov dev_err(dai->codec->dev, "Failed to set output width\n"); 188330345ebSAndrey Smirnov return err; 189330345ebSAndrey Smirnov } 190330345ebSAndrey Smirnov 191330345ebSAndrey Smirnov return 0; 192330345ebSAndrey Smirnov } 193330345ebSAndrey Smirnov 194330345ebSAndrey Smirnov static int si476x_codec_probe(struct snd_soc_codec *codec) 195330345ebSAndrey Smirnov { 196330345ebSAndrey Smirnov codec->control_data = i2c_mfd_cell_to_core(codec->dev); 197330345ebSAndrey Smirnov return 0; 198330345ebSAndrey Smirnov } 199330345ebSAndrey Smirnov 200330345ebSAndrey Smirnov static struct snd_soc_dai_ops si476x_dai_ops = { 201330345ebSAndrey Smirnov .hw_params = si476x_codec_hw_params, 202330345ebSAndrey Smirnov .set_fmt = si476x_codec_set_dai_fmt, 203330345ebSAndrey Smirnov }; 204330345ebSAndrey Smirnov 205330345ebSAndrey Smirnov static struct snd_soc_dai_driver si476x_dai = { 206330345ebSAndrey Smirnov .name = "si476x-codec", 207330345ebSAndrey Smirnov .capture = { 208330345ebSAndrey Smirnov .stream_name = "Capture", 209330345ebSAndrey Smirnov .channels_min = 2, 210330345ebSAndrey Smirnov .channels_max = 2, 211330345ebSAndrey Smirnov 212330345ebSAndrey Smirnov .rates = SNDRV_PCM_RATE_32000 | 213330345ebSAndrey Smirnov SNDRV_PCM_RATE_44100 | 214330345ebSAndrey Smirnov SNDRV_PCM_RATE_48000, 215330345ebSAndrey Smirnov .formats = SNDRV_PCM_FMTBIT_S8 | 216330345ebSAndrey Smirnov SNDRV_PCM_FMTBIT_S16_LE | 217330345ebSAndrey Smirnov SNDRV_PCM_FMTBIT_S20_3LE | 218330345ebSAndrey Smirnov SNDRV_PCM_FMTBIT_S24_LE 219330345ebSAndrey Smirnov }, 220330345ebSAndrey Smirnov .ops = &si476x_dai_ops, 221330345ebSAndrey Smirnov }; 222330345ebSAndrey Smirnov 223330345ebSAndrey Smirnov static struct snd_soc_codec_driver soc_codec_dev_si476x = { 224330345ebSAndrey Smirnov .probe = si476x_codec_probe, 225330345ebSAndrey Smirnov .read = si476x_codec_read, 226330345ebSAndrey Smirnov .write = si476x_codec_write, 227330345ebSAndrey Smirnov }; 228330345ebSAndrey Smirnov 229*7a79e94eSBill Pemberton static int si476x_platform_probe(struct platform_device *pdev) 230330345ebSAndrey Smirnov { 231330345ebSAndrey Smirnov return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si476x, 232330345ebSAndrey Smirnov &si476x_dai, 1); 233330345ebSAndrey Smirnov } 234330345ebSAndrey Smirnov 235*7a79e94eSBill Pemberton static int si476x_platform_remove(struct platform_device *pdev) 236330345ebSAndrey Smirnov { 237330345ebSAndrey Smirnov snd_soc_unregister_codec(&pdev->dev); 238330345ebSAndrey Smirnov return 0; 239330345ebSAndrey Smirnov } 240330345ebSAndrey Smirnov 241330345ebSAndrey Smirnov MODULE_ALIAS("platform:si476x-codec"); 242330345ebSAndrey Smirnov 243330345ebSAndrey Smirnov static struct platform_driver si476x_platform_driver = { 244330345ebSAndrey Smirnov .driver = { 245330345ebSAndrey Smirnov .name = "si476x-codec", 246330345ebSAndrey Smirnov .owner = THIS_MODULE, 247330345ebSAndrey Smirnov }, 248330345ebSAndrey Smirnov .probe = si476x_platform_probe, 249*7a79e94eSBill Pemberton .remove = si476x_platform_remove, 250330345ebSAndrey Smirnov }; 251330345ebSAndrey Smirnov module_platform_driver(si476x_platform_driver); 252330345ebSAndrey Smirnov 253330345ebSAndrey Smirnov MODULE_AUTHOR("Andrey Smirnov <andrey.smirnov@convergeddevices.net>"); 254330345ebSAndrey Smirnov MODULE_DESCRIPTION("ASoC Si4761/64 codec driver"); 255330345ebSAndrey Smirnov MODULE_LICENSE("GPL"); 256