/* * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips * * Copyright (C) 2012 Innovative Converged Devices(ICD) * Copyright (C) 2013 Andrey Smirnov * * Author: Andrey Smirnov <andrew.smirnov@gmail.com> * * 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; version 2 of the License. * * This program is distributed in the hope that 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. * */ #include <linux/module.h> #include <linux/slab.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/initval.h> #include <linux/i2c.h> #include <linux/mfd/si476x-core.h> enum si476x_audio_registers { SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, }; enum si476x_digital_io_output_format { SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, }; #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0b111 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ (0b111 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0b1111110) enum si476x_daudio_formats { SI476X_DAUDIO_MODE_I2S = (0x0 << 1), SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), SI476X_DAUDIO_MODE_IB = (1 << 5), SI476X_DAUDIO_MODE_IF = (1 << 6), }; enum si476x_pcm_format { SI476X_PCM_FORMAT_S8 = 2, SI476X_PCM_FORMAT_S16_LE = 4, SI476X_PCM_FORMAT_S20_3LE = 5, SI476X_PCM_FORMAT_S24_LE = 6, }; static unsigned int si476x_codec_read(struct snd_soc_codec *codec, unsigned int reg) { int err; unsigned int val; struct si476x_core *core = codec->control_data; si476x_core_lock(core); if (!si476x_core_is_powered_up(core)) regcache_cache_only(core->regmap, true); err = regmap_read(core->regmap, reg, &val); if (!si476x_core_is_powered_up(core)) regcache_cache_only(core->regmap, false); si476x_core_unlock(core); if (err < 0) return err; return val; } static int si476x_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { int err; struct si476x_core *core = codec->control_data; si476x_core_lock(core); if (!si476x_core_is_powered_up(core)) regcache_cache_only(core->regmap, true); err = regmap_write(core->regmap, reg, val); if (!si476x_core_is_powered_up(core)) regcache_cache_only(core->regmap, false); si476x_core_unlock(core); return err; } static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { int err; u16 format = 0; if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) return -EINVAL; switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_DSP_A: format |= SI476X_DAUDIO_MODE_DSP_A; break; case SND_SOC_DAIFMT_DSP_B: format |= SI476X_DAUDIO_MODE_DSP_B; break; case SND_SOC_DAIFMT_I2S: format |= SI476X_DAUDIO_MODE_I2S; break; case SND_SOC_DAIFMT_RIGHT_J: format |= SI476X_DAUDIO_MODE_RIGHT_J; break; case SND_SOC_DAIFMT_LEFT_J: format |= SI476X_DAUDIO_MODE_LEFT_J; break; default: return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_NF: format |= SI476X_DAUDIO_MODE_IB; break; default: return -EINVAL; } break; case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_RIGHT_J: case SND_SOC_DAIFMT_LEFT_J: switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_IF: format |= SI476X_DAUDIO_MODE_IB | SI476X_DAUDIO_MODE_IF; break; case SND_SOC_DAIFMT_IB_NF: format |= SI476X_DAUDIO_MODE_IB; break; case SND_SOC_DAIFMT_NB_IF: format |= SI476X_DAUDIO_MODE_IF; break; default: return -EINVAL; } break; default: return -EINVAL; } err = snd_soc_update_bits(codec_dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, format); if (err < 0) { dev_err(codec_dai->codec->dev, "Failed to set output format\n"); return err; } return 0; } static int si476x_codec_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { int rate, width, err; rate = params_rate(params); if (rate < 32000 || rate > 48000) { dev_err(dai->codec->dev, "Rate: %d is not supported\n", rate); return -EINVAL; } switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: width = SI476X_PCM_FORMAT_S8; break; case SNDRV_PCM_FORMAT_S16_LE: width = SI476X_PCM_FORMAT_S16_LE; break; case SNDRV_PCM_FORMAT_S20_3LE: width = SI476X_PCM_FORMAT_S20_3LE; break; case SNDRV_PCM_FORMAT_S24_LE: width = SI476X_PCM_FORMAT_S24_LE; break; default: return -EINVAL; } err = snd_soc_write(dai->codec, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, rate); if (err < 0) { dev_err(dai->codec->dev, "Failed to set sample rate\n"); return err; } err = snd_soc_update_bits(dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); if (err < 0) { dev_err(dai->codec->dev, "Failed to set output width\n"); return err; } return 0; } static int si476x_codec_probe(struct snd_soc_codec *codec) { codec->control_data = i2c_mfd_cell_to_core(codec->dev); return 0; } static struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, }; static struct snd_soc_dai_driver si476x_dai = { .name = "si476x-codec", .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE }, .ops = &si476x_dai_ops, }; static struct snd_soc_codec_driver soc_codec_dev_si476x = { .probe = si476x_codec_probe, .read = si476x_codec_read, .write = si476x_codec_write, }; static int si476x_platform_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si476x, &si476x_dai, 1); } static int si476x_platform_remove(struct platform_device *pdev) { snd_soc_unregister_codec(&pdev->dev); return 0; } MODULE_ALIAS("platform:si476x-codec"); static struct platform_driver si476x_platform_driver = { .driver = { .name = "si476x-codec", .owner = THIS_MODULE, }, .probe = si476x_platform_probe, .remove = si476x_platform_remove, }; module_platform_driver(si476x_platform_driver); MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); MODULE_DESCRIPTION("ASoC Si4761/64 codec driver"); MODULE_LICENSE("GPL");