linux/sound/soc/codecs/si476x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips
   4 *
   5 * Copyright (C) 2012 Innovative Converged Devices(ICD)
   6 * Copyright (C) 2013 Andrey Smirnov
   7 *
   8 * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/slab.h>
  13#include <sound/pcm.h>
  14#include <sound/pcm_params.h>
  15#include <linux/regmap.h>
  16#include <sound/soc.h>
  17#include <sound/initval.h>
  18
  19#include <linux/i2c.h>
  20
  21#include <linux/mfd/si476x-core.h>
  22
  23enum si476x_audio_registers {
  24        SI476X_DIGITAL_IO_OUTPUT_FORMAT         = 0x0203,
  25        SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE    = 0x0202,
  26};
  27
  28enum si476x_digital_io_output_format {
  29        SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT       = 11,
  30        SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT     = 8,
  31};
  32
  33#define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK     ((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \
  34                                                  (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT))
  35#define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK    (0x7e)
  36
  37enum si476x_daudio_formats {
  38        SI476X_DAUDIO_MODE_I2S          = (0x0 << 1),
  39        SI476X_DAUDIO_MODE_DSP_A        = (0x6 << 1),
  40        SI476X_DAUDIO_MODE_DSP_B        = (0x7 << 1),
  41        SI476X_DAUDIO_MODE_LEFT_J       = (0x8 << 1),
  42        SI476X_DAUDIO_MODE_RIGHT_J      = (0x9 << 1),
  43
  44        SI476X_DAUDIO_MODE_IB           = (1 << 5),
  45        SI476X_DAUDIO_MODE_IF           = (1 << 6),
  46};
  47
  48enum si476x_pcm_format {
  49        SI476X_PCM_FORMAT_S8            = 2,
  50        SI476X_PCM_FORMAT_S16_LE        = 4,
  51        SI476X_PCM_FORMAT_S20_3LE       = 5,
  52        SI476X_PCM_FORMAT_S24_LE        = 6,
  53};
  54
  55static const struct snd_soc_dapm_widget si476x_dapm_widgets[] = {
  56SND_SOC_DAPM_OUTPUT("LOUT"),
  57SND_SOC_DAPM_OUTPUT("ROUT"),
  58};
  59
  60static const struct snd_soc_dapm_route si476x_dapm_routes[] = {
  61        { "Capture", NULL, "LOUT" },
  62        { "Capture", NULL, "ROUT" },
  63};
  64
  65static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
  66                                    unsigned int fmt)
  67{
  68        struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev);
  69        int err;
  70        u16 format = 0;
  71
  72        if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
  73                return -EINVAL;
  74
  75        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
  76        case SND_SOC_DAIFMT_DSP_A:
  77                format |= SI476X_DAUDIO_MODE_DSP_A;
  78                break;
  79        case SND_SOC_DAIFMT_DSP_B:
  80                format |= SI476X_DAUDIO_MODE_DSP_B;
  81                break;
  82        case SND_SOC_DAIFMT_I2S:
  83                format |= SI476X_DAUDIO_MODE_I2S;
  84                break;
  85        case SND_SOC_DAIFMT_RIGHT_J:
  86                format |= SI476X_DAUDIO_MODE_RIGHT_J;
  87                break;
  88        case SND_SOC_DAIFMT_LEFT_J:
  89                format |= SI476X_DAUDIO_MODE_LEFT_J;
  90                break;
  91        default:
  92                return -EINVAL;
  93        }
  94
  95        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
  96        case SND_SOC_DAIFMT_DSP_A:
  97        case SND_SOC_DAIFMT_DSP_B:
  98                switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
  99                case SND_SOC_DAIFMT_NB_NF:
 100                        break;
 101                case SND_SOC_DAIFMT_IB_NF:
 102                        format |= SI476X_DAUDIO_MODE_IB;
 103                        break;
 104                default:
 105                        return -EINVAL;
 106                }
 107                break;
 108        case SND_SOC_DAIFMT_I2S:
 109        case SND_SOC_DAIFMT_RIGHT_J:
 110        case SND_SOC_DAIFMT_LEFT_J:
 111                switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 112                case SND_SOC_DAIFMT_NB_NF:
 113                        break;
 114                case SND_SOC_DAIFMT_IB_IF:
 115                        format |= SI476X_DAUDIO_MODE_IB |
 116                                SI476X_DAUDIO_MODE_IF;
 117                        break;
 118                case SND_SOC_DAIFMT_IB_NF:
 119                        format |= SI476X_DAUDIO_MODE_IB;
 120                        break;
 121                case SND_SOC_DAIFMT_NB_IF:
 122                        format |= SI476X_DAUDIO_MODE_IF;
 123                        break;
 124                default:
 125                        return -EINVAL;
 126                }
 127                break;
 128        default:
 129                return -EINVAL;
 130        }
 131
 132        si476x_core_lock(core);
 133
 134        err = snd_soc_component_update_bits(codec_dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
 135                                  SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK,
 136                                  format);
 137
 138        si476x_core_unlock(core);
 139
 140        if (err < 0) {
 141                dev_err(codec_dai->component->dev, "Failed to set output format\n");
 142                return err;
 143        }
 144
 145        return 0;
 146}
 147
 148static int si476x_codec_hw_params(struct snd_pcm_substream *substream,
 149                                  struct snd_pcm_hw_params *params,
 150                                  struct snd_soc_dai *dai)
 151{
 152        struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
 153        int rate, width, err;
 154
 155        rate = params_rate(params);
 156        if (rate < 32000 || rate > 48000) {
 157                dev_err(dai->component->dev, "Rate: %d is not supported\n", rate);
 158                return -EINVAL;
 159        }
 160
 161        switch (params_width(params)) {
 162        case 8:
 163                width = SI476X_PCM_FORMAT_S8;
 164                break;
 165        case 16:
 166                width = SI476X_PCM_FORMAT_S16_LE;
 167                break;
 168        case 20:
 169                width = SI476X_PCM_FORMAT_S20_3LE;
 170                break;
 171        case 24:
 172                width = SI476X_PCM_FORMAT_S24_LE;
 173                break;
 174        default:
 175                return -EINVAL;
 176        }
 177
 178        si476x_core_lock(core);
 179
 180        err = snd_soc_component_write(dai->component, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE,
 181                            rate);
 182        if (err < 0) {
 183                dev_err(dai->component->dev, "Failed to set sample rate\n");
 184                goto out;
 185        }
 186
 187        err = snd_soc_component_update_bits(dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
 188                                  SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK,
 189                                  (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) |
 190                                  (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT));
 191        if (err < 0) {
 192                dev_err(dai->component->dev, "Failed to set output width\n");
 193                goto out;
 194        }
 195
 196out:
 197        si476x_core_unlock(core);
 198
 199        return err;
 200}
 201
 202static const struct snd_soc_dai_ops si476x_dai_ops = {
 203        .hw_params      = si476x_codec_hw_params,
 204        .set_fmt        = si476x_codec_set_dai_fmt,
 205};
 206
 207static struct snd_soc_dai_driver si476x_dai = {
 208        .name           = "si476x-codec",
 209        .capture        = {
 210                .stream_name    = "Capture",
 211                .channels_min   = 2,
 212                .channels_max   = 2,
 213
 214                .rates = SNDRV_PCM_RATE_32000 |
 215                SNDRV_PCM_RATE_44100 |
 216                SNDRV_PCM_RATE_48000,
 217                .formats = SNDRV_PCM_FMTBIT_S8 |
 218                SNDRV_PCM_FMTBIT_S16_LE |
 219                SNDRV_PCM_FMTBIT_S20_3LE |
 220                SNDRV_PCM_FMTBIT_S24_LE
 221        },
 222        .ops            = &si476x_dai_ops,
 223};
 224
 225static int si476x_probe(struct snd_soc_component *component)
 226{
 227        snd_soc_component_init_regmap(component,
 228                                dev_get_regmap(component->dev->parent, NULL));
 229
 230        return 0;
 231}
 232
 233static const struct snd_soc_component_driver soc_component_dev_si476x = {
 234        .probe                  = si476x_probe,
 235        .dapm_widgets           = si476x_dapm_widgets,
 236        .num_dapm_widgets       = ARRAY_SIZE(si476x_dapm_widgets),
 237        .dapm_routes            = si476x_dapm_routes,
 238        .num_dapm_routes        = ARRAY_SIZE(si476x_dapm_routes),
 239        .idle_bias_on           = 1,
 240        .use_pmdown_time        = 1,
 241        .endianness             = 1,
 242        .non_legacy_dai_naming  = 1,
 243};
 244
 245static int si476x_platform_probe(struct platform_device *pdev)
 246{
 247        return devm_snd_soc_register_component(&pdev->dev,
 248                                      &soc_component_dev_si476x,
 249                                      &si476x_dai, 1);
 250}
 251
 252MODULE_ALIAS("platform:si476x-codec");
 253
 254static struct platform_driver si476x_platform_driver = {
 255        .driver         = {
 256                .name   = "si476x-codec",
 257        },
 258        .probe          = si476x_platform_probe,
 259};
 260module_platform_driver(si476x_platform_driver);
 261
 262MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
 263MODULE_DESCRIPTION("ASoC Si4761/64 codec driver");
 264MODULE_LICENSE("GPL");
 265