18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PCM179X ASoC codec driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) Amarula Solutions B.V. 2013 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Michael Trimarchi <michael@amarulasolutions.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/device.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <sound/core.h> 168c2ecf20Sopenharmony_ci#include <sound/pcm.h> 178c2ecf20Sopenharmony_ci#include <sound/pcm_params.h> 188c2ecf20Sopenharmony_ci#include <sound/initval.h> 198c2ecf20Sopenharmony_ci#include <sound/soc.h> 208c2ecf20Sopenharmony_ci#include <sound/tlv.h> 218c2ecf20Sopenharmony_ci#include <linux/of.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include "pcm179x.h" 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define PCM179X_DAC_VOL_LEFT 0x10 268c2ecf20Sopenharmony_ci#define PCM179X_DAC_VOL_RIGHT 0x11 278c2ecf20Sopenharmony_ci#define PCM179X_FMT_CONTROL 0x12 288c2ecf20Sopenharmony_ci#define PCM179X_MODE_CONTROL 0x13 298c2ecf20Sopenharmony_ci#define PCM179X_SOFT_MUTE PCM179X_FMT_CONTROL 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define PCM179X_FMT_MASK 0x70 328c2ecf20Sopenharmony_ci#define PCM179X_FMT_SHIFT 4 338c2ecf20Sopenharmony_ci#define PCM179X_MUTE_MASK 0x01 348c2ecf20Sopenharmony_ci#define PCM179X_MUTE_SHIFT 0 358c2ecf20Sopenharmony_ci#define PCM179X_ATLD_ENABLE (1 << 7) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic const struct reg_default pcm179x_reg_defaults[] = { 388c2ecf20Sopenharmony_ci { 0x10, 0xff }, 398c2ecf20Sopenharmony_ci { 0x11, 0xff }, 408c2ecf20Sopenharmony_ci { 0x12, 0x50 }, 418c2ecf20Sopenharmony_ci { 0x13, 0x00 }, 428c2ecf20Sopenharmony_ci { 0x14, 0x00 }, 438c2ecf20Sopenharmony_ci { 0x15, 0x01 }, 448c2ecf20Sopenharmony_ci { 0x16, 0x00 }, 458c2ecf20Sopenharmony_ci { 0x17, 0x00 }, 468c2ecf20Sopenharmony_ci}; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic bool pcm179x_accessible_reg(struct device *dev, unsigned int reg) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci return reg >= 0x10 && reg <= 0x17; 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic bool pcm179x_writeable_reg(struct device *dev, unsigned int reg) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci bool accessible; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci accessible = pcm179x_accessible_reg(dev, reg); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci return accessible && reg != 0x16 && reg != 0x17; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct pcm179x_private { 638c2ecf20Sopenharmony_ci struct regmap *regmap; 648c2ecf20Sopenharmony_ci unsigned int format; 658c2ecf20Sopenharmony_ci unsigned int rate; 668c2ecf20Sopenharmony_ci}; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int pcm179x_set_dai_fmt(struct snd_soc_dai *codec_dai, 698c2ecf20Sopenharmony_ci unsigned int format) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct snd_soc_component *component = codec_dai->component; 728c2ecf20Sopenharmony_ci struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci priv->format = format; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int pcm179x_mute(struct snd_soc_dai *dai, int mute, int direction) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct snd_soc_component *component = dai->component; 828c2ecf20Sopenharmony_ci struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); 838c2ecf20Sopenharmony_ci int ret; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci ret = regmap_update_bits(priv->regmap, PCM179X_SOFT_MUTE, 868c2ecf20Sopenharmony_ci PCM179X_MUTE_MASK, !!mute); 878c2ecf20Sopenharmony_ci if (ret < 0) 888c2ecf20Sopenharmony_ci return ret; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci return 0; 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic int pcm179x_hw_params(struct snd_pcm_substream *substream, 948c2ecf20Sopenharmony_ci struct snd_pcm_hw_params *params, 958c2ecf20Sopenharmony_ci struct snd_soc_dai *dai) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct snd_soc_component *component = dai->component; 988c2ecf20Sopenharmony_ci struct pcm179x_private *priv = snd_soc_component_get_drvdata(component); 998c2ecf20Sopenharmony_ci int val = 0, ret; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci priv->rate = params_rate(params); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { 1048c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_RIGHT_J: 1058c2ecf20Sopenharmony_ci switch (params_width(params)) { 1068c2ecf20Sopenharmony_ci case 24: 1078c2ecf20Sopenharmony_ci case 32: 1088c2ecf20Sopenharmony_ci val = 2; 1098c2ecf20Sopenharmony_ci break; 1108c2ecf20Sopenharmony_ci case 16: 1118c2ecf20Sopenharmony_ci val = 0; 1128c2ecf20Sopenharmony_ci break; 1138c2ecf20Sopenharmony_ci default: 1148c2ecf20Sopenharmony_ci return -EINVAL; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci break; 1178c2ecf20Sopenharmony_ci case SND_SOC_DAIFMT_I2S: 1188c2ecf20Sopenharmony_ci switch (params_width(params)) { 1198c2ecf20Sopenharmony_ci case 24: 1208c2ecf20Sopenharmony_ci case 32: 1218c2ecf20Sopenharmony_ci val = 5; 1228c2ecf20Sopenharmony_ci break; 1238c2ecf20Sopenharmony_ci case 16: 1248c2ecf20Sopenharmony_ci val = 4; 1258c2ecf20Sopenharmony_ci break; 1268c2ecf20Sopenharmony_ci default: 1278c2ecf20Sopenharmony_ci return -EINVAL; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci break; 1308c2ecf20Sopenharmony_ci default: 1318c2ecf20Sopenharmony_ci dev_err(component->dev, "Invalid DAI format\n"); 1328c2ecf20Sopenharmony_ci return -EINVAL; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci val = val << PCM179X_FMT_SHIFT | PCM179X_ATLD_ENABLE; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci ret = regmap_update_bits(priv->regmap, PCM179X_FMT_CONTROL, 1388c2ecf20Sopenharmony_ci PCM179X_FMT_MASK | PCM179X_ATLD_ENABLE, val); 1398c2ecf20Sopenharmony_ci if (ret < 0) 1408c2ecf20Sopenharmony_ci return ret; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return 0; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops pcm179x_dai_ops = { 1468c2ecf20Sopenharmony_ci .set_fmt = pcm179x_set_dai_fmt, 1478c2ecf20Sopenharmony_ci .hw_params = pcm179x_hw_params, 1488c2ecf20Sopenharmony_ci .mute_stream = pcm179x_mute, 1498c2ecf20Sopenharmony_ci .no_capture_mute = 1, 1508c2ecf20Sopenharmony_ci}; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic const DECLARE_TLV_DB_SCALE(pcm179x_dac_tlv, -12000, 50, 1); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new pcm179x_controls[] = { 1558c2ecf20Sopenharmony_ci SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM179X_DAC_VOL_LEFT, 1568c2ecf20Sopenharmony_ci PCM179X_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, 1578c2ecf20Sopenharmony_ci pcm179x_dac_tlv), 1588c2ecf20Sopenharmony_ci SOC_SINGLE("DAC Invert Output Switch", PCM179X_MODE_CONTROL, 7, 1, 0), 1598c2ecf20Sopenharmony_ci SOC_SINGLE("DAC Rolloff Filter Switch", PCM179X_MODE_CONTROL, 1, 1, 0), 1608c2ecf20Sopenharmony_ci}; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_widget pcm179x_dapm_widgets[] = { 1638c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("IOUTL+"), 1648c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("IOUTL-"), 1658c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("IOUTR+"), 1668c2ecf20Sopenharmony_ciSND_SOC_DAPM_OUTPUT("IOUTR-"), 1678c2ecf20Sopenharmony_ci}; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_cistatic const struct snd_soc_dapm_route pcm179x_dapm_routes[] = { 1708c2ecf20Sopenharmony_ci { "IOUTL+", NULL, "Playback" }, 1718c2ecf20Sopenharmony_ci { "IOUTL-", NULL, "Playback" }, 1728c2ecf20Sopenharmony_ci { "IOUTR+", NULL, "Playback" }, 1738c2ecf20Sopenharmony_ci { "IOUTR-", NULL, "Playback" }, 1748c2ecf20Sopenharmony_ci}; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver pcm179x_dai = { 1778c2ecf20Sopenharmony_ci .name = "pcm179x-hifi", 1788c2ecf20Sopenharmony_ci .playback = { 1798c2ecf20Sopenharmony_ci .stream_name = "Playback", 1808c2ecf20Sopenharmony_ci .channels_min = 2, 1818c2ecf20Sopenharmony_ci .channels_max = 2, 1828c2ecf20Sopenharmony_ci .rates = SNDRV_PCM_RATE_CONTINUOUS, 1838c2ecf20Sopenharmony_ci .rate_min = 10000, 1848c2ecf20Sopenharmony_ci .rate_max = 200000, 1858c2ecf20Sopenharmony_ci .formats = PCM1792A_FORMATS, }, 1868c2ecf20Sopenharmony_ci .ops = &pcm179x_dai_ops, 1878c2ecf20Sopenharmony_ci}; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ciconst struct regmap_config pcm179x_regmap_config = { 1908c2ecf20Sopenharmony_ci .reg_bits = 8, 1918c2ecf20Sopenharmony_ci .val_bits = 8, 1928c2ecf20Sopenharmony_ci .max_register = 23, 1938c2ecf20Sopenharmony_ci .reg_defaults = pcm179x_reg_defaults, 1948c2ecf20Sopenharmony_ci .num_reg_defaults = ARRAY_SIZE(pcm179x_reg_defaults), 1958c2ecf20Sopenharmony_ci .writeable_reg = pcm179x_writeable_reg, 1968c2ecf20Sopenharmony_ci .readable_reg = pcm179x_accessible_reg, 1978c2ecf20Sopenharmony_ci}; 1988c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcm179x_regmap_config); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver soc_component_dev_pcm179x = { 2018c2ecf20Sopenharmony_ci .controls = pcm179x_controls, 2028c2ecf20Sopenharmony_ci .num_controls = ARRAY_SIZE(pcm179x_controls), 2038c2ecf20Sopenharmony_ci .dapm_widgets = pcm179x_dapm_widgets, 2048c2ecf20Sopenharmony_ci .num_dapm_widgets = ARRAY_SIZE(pcm179x_dapm_widgets), 2058c2ecf20Sopenharmony_ci .dapm_routes = pcm179x_dapm_routes, 2068c2ecf20Sopenharmony_ci .num_dapm_routes = ARRAY_SIZE(pcm179x_dapm_routes), 2078c2ecf20Sopenharmony_ci .idle_bias_on = 1, 2088c2ecf20Sopenharmony_ci .use_pmdown_time = 1, 2098c2ecf20Sopenharmony_ci .endianness = 1, 2108c2ecf20Sopenharmony_ci .non_legacy_dai_naming = 1, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ciint pcm179x_common_init(struct device *dev, struct regmap *regmap) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci struct pcm179x_private *pcm179x; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci pcm179x = devm_kzalloc(dev, sizeof(struct pcm179x_private), 2188c2ecf20Sopenharmony_ci GFP_KERNEL); 2198c2ecf20Sopenharmony_ci if (!pcm179x) 2208c2ecf20Sopenharmony_ci return -ENOMEM; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci pcm179x->regmap = regmap; 2238c2ecf20Sopenharmony_ci dev_set_drvdata(dev, pcm179x); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return devm_snd_soc_register_component(dev, 2268c2ecf20Sopenharmony_ci &soc_component_dev_pcm179x, &pcm179x_dai, 1); 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pcm179x_common_init); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ASoC PCM179X driver"); 2318c2ecf20Sopenharmony_ciMODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); 2328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 233