18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2012 Innovative Converged Devices(ICD)
68c2ecf20Sopenharmony_ci * Copyright (C) 2013 Andrey Smirnov
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <sound/pcm.h>
148c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
158c2ecf20Sopenharmony_ci#include <linux/regmap.h>
168c2ecf20Sopenharmony_ci#include <sound/soc.h>
178c2ecf20Sopenharmony_ci#include <sound/initval.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/i2c.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <linux/mfd/si476x-core.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cienum si476x_audio_registers {
248c2ecf20Sopenharmony_ci	SI476X_DIGITAL_IO_OUTPUT_FORMAT		= 0x0203,
258c2ecf20Sopenharmony_ci	SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE	= 0x0202,
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cienum si476x_digital_io_output_format {
298c2ecf20Sopenharmony_ci	SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT	= 11,
308c2ecf20Sopenharmony_ci	SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT	= 8,
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK	((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \
348c2ecf20Sopenharmony_ci						  (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT))
358c2ecf20Sopenharmony_ci#define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK	(0x7e)
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cienum si476x_daudio_formats {
388c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_I2S		= (0x0 << 1),
398c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_DSP_A	= (0x6 << 1),
408c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_DSP_B	= (0x7 << 1),
418c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_LEFT_J	= (0x8 << 1),
428c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_RIGHT_J	= (0x9 << 1),
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_IB		= (1 << 5),
458c2ecf20Sopenharmony_ci	SI476X_DAUDIO_MODE_IF		= (1 << 6),
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cienum si476x_pcm_format {
498c2ecf20Sopenharmony_ci	SI476X_PCM_FORMAT_S8		= 2,
508c2ecf20Sopenharmony_ci	SI476X_PCM_FORMAT_S16_LE	= 4,
518c2ecf20Sopenharmony_ci	SI476X_PCM_FORMAT_S20_3LE	= 5,
528c2ecf20Sopenharmony_ci	SI476X_PCM_FORMAT_S24_LE	= 6,
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget si476x_dapm_widgets[] = {
568c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("LOUT"),
578c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("ROUT"),
588c2ecf20Sopenharmony_ci};
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route si476x_dapm_routes[] = {
618c2ecf20Sopenharmony_ci	{ "Capture", NULL, "LOUT" },
628c2ecf20Sopenharmony_ci	{ "Capture", NULL, "ROUT" },
638c2ecf20Sopenharmony_ci};
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
668c2ecf20Sopenharmony_ci				    unsigned int fmt)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev);
698c2ecf20Sopenharmony_ci	int err;
708c2ecf20Sopenharmony_ci	u16 format = 0;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
738c2ecf20Sopenharmony_ci		return -EINVAL;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
768c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_DSP_A:
778c2ecf20Sopenharmony_ci		format |= SI476X_DAUDIO_MODE_DSP_A;
788c2ecf20Sopenharmony_ci		break;
798c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_DSP_B:
808c2ecf20Sopenharmony_ci		format |= SI476X_DAUDIO_MODE_DSP_B;
818c2ecf20Sopenharmony_ci		break;
828c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_I2S:
838c2ecf20Sopenharmony_ci		format |= SI476X_DAUDIO_MODE_I2S;
848c2ecf20Sopenharmony_ci		break;
858c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_RIGHT_J:
868c2ecf20Sopenharmony_ci		format |= SI476X_DAUDIO_MODE_RIGHT_J;
878c2ecf20Sopenharmony_ci		break;
888c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_LEFT_J:
898c2ecf20Sopenharmony_ci		format |= SI476X_DAUDIO_MODE_LEFT_J;
908c2ecf20Sopenharmony_ci		break;
918c2ecf20Sopenharmony_ci	default:
928c2ecf20Sopenharmony_ci		return -EINVAL;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
968c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_DSP_A:
978c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_DSP_B:
988c2ecf20Sopenharmony_ci		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
998c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_NB_NF:
1008c2ecf20Sopenharmony_ci			break;
1018c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_IB_NF:
1028c2ecf20Sopenharmony_ci			format |= SI476X_DAUDIO_MODE_IB;
1038c2ecf20Sopenharmony_ci			break;
1048c2ecf20Sopenharmony_ci		default:
1058c2ecf20Sopenharmony_ci			return -EINVAL;
1068c2ecf20Sopenharmony_ci		}
1078c2ecf20Sopenharmony_ci		break;
1088c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_I2S:
1098c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_RIGHT_J:
1108c2ecf20Sopenharmony_ci	case SND_SOC_DAIFMT_LEFT_J:
1118c2ecf20Sopenharmony_ci		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
1128c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_NB_NF:
1138c2ecf20Sopenharmony_ci			break;
1148c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_IB_IF:
1158c2ecf20Sopenharmony_ci			format |= SI476X_DAUDIO_MODE_IB |
1168c2ecf20Sopenharmony_ci				SI476X_DAUDIO_MODE_IF;
1178c2ecf20Sopenharmony_ci			break;
1188c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_IB_NF:
1198c2ecf20Sopenharmony_ci			format |= SI476X_DAUDIO_MODE_IB;
1208c2ecf20Sopenharmony_ci			break;
1218c2ecf20Sopenharmony_ci		case SND_SOC_DAIFMT_NB_IF:
1228c2ecf20Sopenharmony_ci			format |= SI476X_DAUDIO_MODE_IF;
1238c2ecf20Sopenharmony_ci			break;
1248c2ecf20Sopenharmony_ci		default:
1258c2ecf20Sopenharmony_ci			return -EINVAL;
1268c2ecf20Sopenharmony_ci		}
1278c2ecf20Sopenharmony_ci		break;
1288c2ecf20Sopenharmony_ci	default:
1298c2ecf20Sopenharmony_ci		return -EINVAL;
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	si476x_core_lock(core);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	err = snd_soc_component_update_bits(codec_dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
1358c2ecf20Sopenharmony_ci				  SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK,
1368c2ecf20Sopenharmony_ci				  format);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	si476x_core_unlock(core);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	if (err < 0) {
1418c2ecf20Sopenharmony_ci		dev_err(codec_dai->component->dev, "Failed to set output format\n");
1428c2ecf20Sopenharmony_ci		return err;
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	return 0;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic int si476x_codec_hw_params(struct snd_pcm_substream *substream,
1498c2ecf20Sopenharmony_ci				  struct snd_pcm_hw_params *params,
1508c2ecf20Sopenharmony_ci				  struct snd_soc_dai *dai)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
1538c2ecf20Sopenharmony_ci	int rate, width, err;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	rate = params_rate(params);
1568c2ecf20Sopenharmony_ci	if (rate < 32000 || rate > 48000) {
1578c2ecf20Sopenharmony_ci		dev_err(dai->component->dev, "Rate: %d is not supported\n", rate);
1588c2ecf20Sopenharmony_ci		return -EINVAL;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	switch (params_width(params)) {
1628c2ecf20Sopenharmony_ci	case 8:
1638c2ecf20Sopenharmony_ci		width = SI476X_PCM_FORMAT_S8;
1648c2ecf20Sopenharmony_ci		break;
1658c2ecf20Sopenharmony_ci	case 16:
1668c2ecf20Sopenharmony_ci		width = SI476X_PCM_FORMAT_S16_LE;
1678c2ecf20Sopenharmony_ci		break;
1688c2ecf20Sopenharmony_ci	case 20:
1698c2ecf20Sopenharmony_ci		width = SI476X_PCM_FORMAT_S20_3LE;
1708c2ecf20Sopenharmony_ci		break;
1718c2ecf20Sopenharmony_ci	case 24:
1728c2ecf20Sopenharmony_ci		width = SI476X_PCM_FORMAT_S24_LE;
1738c2ecf20Sopenharmony_ci		break;
1748c2ecf20Sopenharmony_ci	default:
1758c2ecf20Sopenharmony_ci		return -EINVAL;
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	si476x_core_lock(core);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	err = snd_soc_component_write(dai->component, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE,
1818c2ecf20Sopenharmony_ci			    rate);
1828c2ecf20Sopenharmony_ci	if (err < 0) {
1838c2ecf20Sopenharmony_ci		dev_err(dai->component->dev, "Failed to set sample rate\n");
1848c2ecf20Sopenharmony_ci		goto out;
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	err = snd_soc_component_update_bits(dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
1888c2ecf20Sopenharmony_ci				  SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK,
1898c2ecf20Sopenharmony_ci				  (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) |
1908c2ecf20Sopenharmony_ci				  (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT));
1918c2ecf20Sopenharmony_ci	if (err < 0) {
1928c2ecf20Sopenharmony_ci		dev_err(dai->component->dev, "Failed to set output width\n");
1938c2ecf20Sopenharmony_ci		goto out;
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ciout:
1978c2ecf20Sopenharmony_ci	si476x_core_unlock(core);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	return err;
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops si476x_dai_ops = {
2038c2ecf20Sopenharmony_ci	.hw_params	= si476x_codec_hw_params,
2048c2ecf20Sopenharmony_ci	.set_fmt	= si476x_codec_set_dai_fmt,
2058c2ecf20Sopenharmony_ci};
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver si476x_dai = {
2088c2ecf20Sopenharmony_ci	.name		= "si476x-codec",
2098c2ecf20Sopenharmony_ci	.capture	= {
2108c2ecf20Sopenharmony_ci		.stream_name	= "Capture",
2118c2ecf20Sopenharmony_ci		.channels_min	= 2,
2128c2ecf20Sopenharmony_ci		.channels_max	= 2,
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci		.rates = SNDRV_PCM_RATE_32000 |
2158c2ecf20Sopenharmony_ci		SNDRV_PCM_RATE_44100 |
2168c2ecf20Sopenharmony_ci		SNDRV_PCM_RATE_48000,
2178c2ecf20Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S8 |
2188c2ecf20Sopenharmony_ci		SNDRV_PCM_FMTBIT_S16_LE |
2198c2ecf20Sopenharmony_ci		SNDRV_PCM_FMTBIT_S20_3LE |
2208c2ecf20Sopenharmony_ci		SNDRV_PCM_FMTBIT_S24_LE
2218c2ecf20Sopenharmony_ci	},
2228c2ecf20Sopenharmony_ci	.ops		= &si476x_dai_ops,
2238c2ecf20Sopenharmony_ci};
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic int si476x_probe(struct snd_soc_component *component)
2268c2ecf20Sopenharmony_ci{
2278c2ecf20Sopenharmony_ci	snd_soc_component_init_regmap(component,
2288c2ecf20Sopenharmony_ci				dev_get_regmap(component->dev->parent, NULL));
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	return 0;
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver soc_component_dev_si476x = {
2348c2ecf20Sopenharmony_ci	.probe			= si476x_probe,
2358c2ecf20Sopenharmony_ci	.dapm_widgets		= si476x_dapm_widgets,
2368c2ecf20Sopenharmony_ci	.num_dapm_widgets	= ARRAY_SIZE(si476x_dapm_widgets),
2378c2ecf20Sopenharmony_ci	.dapm_routes		= si476x_dapm_routes,
2388c2ecf20Sopenharmony_ci	.num_dapm_routes	= ARRAY_SIZE(si476x_dapm_routes),
2398c2ecf20Sopenharmony_ci	.idle_bias_on		= 1,
2408c2ecf20Sopenharmony_ci	.use_pmdown_time	= 1,
2418c2ecf20Sopenharmony_ci	.endianness		= 1,
2428c2ecf20Sopenharmony_ci	.non_legacy_dai_naming	= 1,
2438c2ecf20Sopenharmony_ci};
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cistatic int si476x_platform_probe(struct platform_device *pdev)
2468c2ecf20Sopenharmony_ci{
2478c2ecf20Sopenharmony_ci	return devm_snd_soc_register_component(&pdev->dev,
2488c2ecf20Sopenharmony_ci				      &soc_component_dev_si476x,
2498c2ecf20Sopenharmony_ci				      &si476x_dai, 1);
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:si476x-codec");
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic struct platform_driver si476x_platform_driver = {
2558c2ecf20Sopenharmony_ci	.driver		= {
2568c2ecf20Sopenharmony_ci		.name	= "si476x-codec",
2578c2ecf20Sopenharmony_ci	},
2588c2ecf20Sopenharmony_ci	.probe		= si476x_platform_probe,
2598c2ecf20Sopenharmony_ci};
2608c2ecf20Sopenharmony_cimodule_platform_driver(si476x_platform_driver);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
2638c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASoC Si4761/64 codec driver");
2648c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
265