162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2012 Innovative Converged Devices(ICD) 662306a36Sopenharmony_ci * Copyright (C) 2013 Andrey Smirnov 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Author: Andrey Smirnov <andrew.smirnov@gmail.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <sound/pcm.h> 1462306a36Sopenharmony_ci#include <sound/pcm_params.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci#include <sound/soc.h> 1762306a36Sopenharmony_ci#include <sound/initval.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <linux/i2c.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <linux/mfd/si476x-core.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cienum si476x_audio_registers { 2462306a36Sopenharmony_ci SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, 2562306a36Sopenharmony_ci SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cienum si476x_digital_io_output_format { 2962306a36Sopenharmony_ci SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, 3062306a36Sopenharmony_ci SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ 3462306a36Sopenharmony_ci (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) 3562306a36Sopenharmony_ci#define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0x7e) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cienum si476x_daudio_formats { 3862306a36Sopenharmony_ci SI476X_DAUDIO_MODE_I2S = (0x0 << 1), 3962306a36Sopenharmony_ci SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), 4062306a36Sopenharmony_ci SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), 4162306a36Sopenharmony_ci SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), 4262306a36Sopenharmony_ci SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci SI476X_DAUDIO_MODE_IB = (1 << 5), 4562306a36Sopenharmony_ci SI476X_DAUDIO_MODE_IF = (1 << 6), 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cienum si476x_pcm_format { 4962306a36Sopenharmony_ci SI476X_PCM_FORMAT_S8 = 2, 5062306a36Sopenharmony_ci SI476X_PCM_FORMAT_S16_LE = 4, 5162306a36Sopenharmony_ci SI476X_PCM_FORMAT_S20_3LE = 5, 5262306a36Sopenharmony_ci SI476X_PCM_FORMAT_S24_LE = 6, 5362306a36Sopenharmony_ci}; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic const struct snd_soc_dapm_widget si476x_dapm_widgets[] = { 5662306a36Sopenharmony_ciSND_SOC_DAPM_OUTPUT("LOUT"), 5762306a36Sopenharmony_ciSND_SOC_DAPM_OUTPUT("ROUT"), 5862306a36Sopenharmony_ci}; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic const struct snd_soc_dapm_route si476x_dapm_routes[] = { 6162306a36Sopenharmony_ci { "Capture", NULL, "LOUT" }, 6262306a36Sopenharmony_ci { "Capture", NULL, "ROUT" }, 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, 6662306a36Sopenharmony_ci unsigned int fmt) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev); 6962306a36Sopenharmony_ci int err; 7062306a36Sopenharmony_ci u16 format = 0; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) 7362306a36Sopenharmony_ci return -EINVAL; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 7662306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_A: 7762306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_DSP_A; 7862306a36Sopenharmony_ci break; 7962306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_B: 8062306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_DSP_B; 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci case SND_SOC_DAIFMT_I2S: 8362306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_I2S; 8462306a36Sopenharmony_ci break; 8562306a36Sopenharmony_ci case SND_SOC_DAIFMT_RIGHT_J: 8662306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_RIGHT_J; 8762306a36Sopenharmony_ci break; 8862306a36Sopenharmony_ci case SND_SOC_DAIFMT_LEFT_J: 8962306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_LEFT_J; 9062306a36Sopenharmony_ci break; 9162306a36Sopenharmony_ci default: 9262306a36Sopenharmony_ci return -EINVAL; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 9662306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_A: 9762306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_B: 9862306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 9962306a36Sopenharmony_ci case SND_SOC_DAIFMT_NB_NF: 10062306a36Sopenharmony_ci break; 10162306a36Sopenharmony_ci case SND_SOC_DAIFMT_IB_NF: 10262306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_IB; 10362306a36Sopenharmony_ci break; 10462306a36Sopenharmony_ci default: 10562306a36Sopenharmony_ci return -EINVAL; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci break; 10862306a36Sopenharmony_ci case SND_SOC_DAIFMT_I2S: 10962306a36Sopenharmony_ci case SND_SOC_DAIFMT_RIGHT_J: 11062306a36Sopenharmony_ci case SND_SOC_DAIFMT_LEFT_J: 11162306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 11262306a36Sopenharmony_ci case SND_SOC_DAIFMT_NB_NF: 11362306a36Sopenharmony_ci break; 11462306a36Sopenharmony_ci case SND_SOC_DAIFMT_IB_IF: 11562306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_IB | 11662306a36Sopenharmony_ci SI476X_DAUDIO_MODE_IF; 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci case SND_SOC_DAIFMT_IB_NF: 11962306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_IB; 12062306a36Sopenharmony_ci break; 12162306a36Sopenharmony_ci case SND_SOC_DAIFMT_NB_IF: 12262306a36Sopenharmony_ci format |= SI476X_DAUDIO_MODE_IF; 12362306a36Sopenharmony_ci break; 12462306a36Sopenharmony_ci default: 12562306a36Sopenharmony_ci return -EINVAL; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci default: 12962306a36Sopenharmony_ci return -EINVAL; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci si476x_core_lock(core); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci err = snd_soc_component_update_bits(codec_dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT, 13562306a36Sopenharmony_ci SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, 13662306a36Sopenharmony_ci format); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci si476x_core_unlock(core); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (err < 0) { 14162306a36Sopenharmony_ci dev_err(codec_dai->component->dev, "Failed to set output format\n"); 14262306a36Sopenharmony_ci return err; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int si476x_codec_hw_params(struct snd_pcm_substream *substream, 14962306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 15062306a36Sopenharmony_ci struct snd_soc_dai *dai) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev); 15362306a36Sopenharmony_ci int rate, width, err; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci rate = params_rate(params); 15662306a36Sopenharmony_ci if (rate < 32000 || rate > 48000) { 15762306a36Sopenharmony_ci dev_err(dai->component->dev, "Rate: %d is not supported\n", rate); 15862306a36Sopenharmony_ci return -EINVAL; 15962306a36Sopenharmony_ci } 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci switch (params_width(params)) { 16262306a36Sopenharmony_ci case 8: 16362306a36Sopenharmony_ci width = SI476X_PCM_FORMAT_S8; 16462306a36Sopenharmony_ci break; 16562306a36Sopenharmony_ci case 16: 16662306a36Sopenharmony_ci width = SI476X_PCM_FORMAT_S16_LE; 16762306a36Sopenharmony_ci break; 16862306a36Sopenharmony_ci case 20: 16962306a36Sopenharmony_ci width = SI476X_PCM_FORMAT_S20_3LE; 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case 24: 17262306a36Sopenharmony_ci width = SI476X_PCM_FORMAT_S24_LE; 17362306a36Sopenharmony_ci break; 17462306a36Sopenharmony_ci default: 17562306a36Sopenharmony_ci return -EINVAL; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci si476x_core_lock(core); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci err = snd_soc_component_write(dai->component, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, 18162306a36Sopenharmony_ci rate); 18262306a36Sopenharmony_ci if (err < 0) { 18362306a36Sopenharmony_ci dev_err(dai->component->dev, "Failed to set sample rate\n"); 18462306a36Sopenharmony_ci goto out; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci err = snd_soc_component_update_bits(dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT, 18862306a36Sopenharmony_ci SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, 18962306a36Sopenharmony_ci (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | 19062306a36Sopenharmony_ci (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); 19162306a36Sopenharmony_ci if (err < 0) { 19262306a36Sopenharmony_ci dev_err(dai->component->dev, "Failed to set output width\n"); 19362306a36Sopenharmony_ci goto out; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ciout: 19762306a36Sopenharmony_ci si476x_core_unlock(core); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci return err; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic const struct snd_soc_dai_ops si476x_dai_ops = { 20362306a36Sopenharmony_ci .hw_params = si476x_codec_hw_params, 20462306a36Sopenharmony_ci .set_fmt = si476x_codec_set_dai_fmt, 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic struct snd_soc_dai_driver si476x_dai = { 20862306a36Sopenharmony_ci .name = "si476x-codec", 20962306a36Sopenharmony_ci .capture = { 21062306a36Sopenharmony_ci .stream_name = "Capture", 21162306a36Sopenharmony_ci .channels_min = 2, 21262306a36Sopenharmony_ci .channels_max = 2, 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_32000 | 21562306a36Sopenharmony_ci SNDRV_PCM_RATE_44100 | 21662306a36Sopenharmony_ci SNDRV_PCM_RATE_48000, 21762306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S8 | 21862306a36Sopenharmony_ci SNDRV_PCM_FMTBIT_S16_LE | 21962306a36Sopenharmony_ci SNDRV_PCM_FMTBIT_S20_3LE | 22062306a36Sopenharmony_ci SNDRV_PCM_FMTBIT_S24_LE 22162306a36Sopenharmony_ci }, 22262306a36Sopenharmony_ci .ops = &si476x_dai_ops, 22362306a36Sopenharmony_ci}; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic int si476x_probe(struct snd_soc_component *component) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci snd_soc_component_init_regmap(component, 22862306a36Sopenharmony_ci dev_get_regmap(component->dev->parent, NULL)); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic const struct snd_soc_component_driver soc_component_dev_si476x = { 23462306a36Sopenharmony_ci .probe = si476x_probe, 23562306a36Sopenharmony_ci .dapm_widgets = si476x_dapm_widgets, 23662306a36Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(si476x_dapm_widgets), 23762306a36Sopenharmony_ci .dapm_routes = si476x_dapm_routes, 23862306a36Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(si476x_dapm_routes), 23962306a36Sopenharmony_ci .idle_bias_on = 1, 24062306a36Sopenharmony_ci .use_pmdown_time = 1, 24162306a36Sopenharmony_ci .endianness = 1, 24262306a36Sopenharmony_ci}; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic int si476x_platform_probe(struct platform_device *pdev) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci return devm_snd_soc_register_component(&pdev->dev, 24762306a36Sopenharmony_ci &soc_component_dev_si476x, 24862306a36Sopenharmony_ci &si476x_dai, 1); 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ciMODULE_ALIAS("platform:si476x-codec"); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic struct platform_driver si476x_platform_driver = { 25462306a36Sopenharmony_ci .driver = { 25562306a36Sopenharmony_ci .name = "si476x-codec", 25662306a36Sopenharmony_ci }, 25762306a36Sopenharmony_ci .probe = si476x_platform_probe, 25862306a36Sopenharmony_ci}; 25962306a36Sopenharmony_cimodule_platform_driver(si476x_platform_driver); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ciMODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); 26262306a36Sopenharmony_ciMODULE_DESCRIPTION("ASoC Si4761/64 codec driver"); 26362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 264