| 
									
										
										
										
											2013-08-03 16:20:43 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * PCM1792A ASoC codec driver | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) Amarula Solutions B.V. 2013 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     Michael Trimarchi <michael@amarulasolutions.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; either version 2 | 
					
						
							|  |  |  |  * of the License, or (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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 <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/device.h>
 | 
					
						
							|  |  |  | #include <linux/spi/spi.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <sound/core.h>
 | 
					
						
							|  |  |  | #include <sound/pcm.h>
 | 
					
						
							|  |  |  | #include <sound/pcm_params.h>
 | 
					
						
							|  |  |  | #include <sound/initval.h>
 | 
					
						
							|  |  |  | #include <sound/soc.h>
 | 
					
						
							|  |  |  | #include <sound/tlv.h>
 | 
					
						
							|  |  |  | #include <linux/of_device.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "pcm1792a.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PCM1792A_DAC_VOL_LEFT	0x10
 | 
					
						
							|  |  |  | #define PCM1792A_DAC_VOL_RIGHT	0x11
 | 
					
						
							|  |  |  | #define PCM1792A_FMT_CONTROL	0x12
 | 
					
						
							|  |  |  | #define PCM1792A_SOFT_MUTE	PCM1792A_FMT_CONTROL
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define PCM1792A_FMT_MASK	0x70
 | 
					
						
							|  |  |  | #define PCM1792A_FMT_SHIFT	4
 | 
					
						
							|  |  |  | #define PCM1792A_MUTE_MASK	0x01
 | 
					
						
							|  |  |  | #define PCM1792A_MUTE_SHIFT	0
 | 
					
						
							|  |  |  | #define PCM1792A_ATLD_ENABLE	(1 << 7)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct reg_default pcm1792a_reg_defaults[] = { | 
					
						
							|  |  |  | 	{ 0x10, 0xff }, | 
					
						
							|  |  |  | 	{ 0x11, 0xff }, | 
					
						
							|  |  |  | 	{ 0x12, 0x50 }, | 
					
						
							|  |  |  | 	{ 0x13, 0x00 }, | 
					
						
							|  |  |  | 	{ 0x14, 0x00 }, | 
					
						
							|  |  |  | 	{ 0x15, 0x01 }, | 
					
						
							|  |  |  | 	{ 0x16, 0x00 }, | 
					
						
							|  |  |  | 	{ 0x17, 0x00 }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool pcm1792a_accessible_reg(struct device *dev, unsigned int reg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return reg >= 0x10 && reg <= 0x17; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool pcm1792a_writeable_reg(struct device *dev, unsigned register reg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bool accessible; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	accessible = pcm1792a_accessible_reg(dev, reg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return accessible && reg != 0x16 && reg != 0x17; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct pcm1792a_private { | 
					
						
							|  |  |  | 	struct regmap *regmap; | 
					
						
							|  |  |  | 	unsigned int format; | 
					
						
							|  |  |  | 	unsigned int rate; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pcm1792a_set_dai_fmt(struct snd_soc_dai *codec_dai, | 
					
						
							|  |  |  |                              unsigned int format) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct snd_soc_codec *codec = codec_dai->codec; | 
					
						
							|  |  |  | 	struct pcm1792a_private *priv = snd_soc_codec_get_drvdata(codec); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv->format = format; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pcm1792a_digital_mute(struct snd_soc_dai *dai, int mute) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct snd_soc_codec *codec = dai->codec; | 
					
						
							|  |  |  | 	struct pcm1792a_private *priv = snd_soc_codec_get_drvdata(codec); | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = regmap_update_bits(priv->regmap, PCM1792A_SOFT_MUTE, | 
					
						
							|  |  |  | 				 PCM1792A_MUTE_MASK, !!mute); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pcm1792a_hw_params(struct snd_pcm_substream *substream, | 
					
						
							|  |  |  | 			     struct snd_pcm_hw_params *params, | 
					
						
							|  |  |  | 			     struct snd_soc_dai *dai) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct snd_soc_codec *codec = dai->codec; | 
					
						
							|  |  |  | 	struct pcm1792a_private *priv = snd_soc_codec_get_drvdata(codec); | 
					
						
							|  |  |  | 	int val = 0, ret; | 
					
						
							|  |  |  | 	int pcm_format = params_format(params); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv->rate = params_rate(params); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) { | 
					
						
							|  |  |  | 	case SND_SOC_DAIFMT_RIGHT_J: | 
					
						
							|  |  |  | 		if (pcm_format == SNDRV_PCM_FORMAT_S24_LE || | 
					
						
							|  |  |  | 		    pcm_format == SNDRV_PCM_FORMAT_S32_LE) | 
					
						
							|  |  |  | 			val = 0x02; | 
					
						
							|  |  |  | 		else if (pcm_format == SNDRV_PCM_FORMAT_S16_LE) | 
					
						
							|  |  |  | 			val = 0x00; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case SND_SOC_DAIFMT_I2S: | 
					
						
							|  |  |  | 		if (pcm_format == SNDRV_PCM_FORMAT_S24_LE || | 
					
						
							|  |  |  | 		    pcm_format == SNDRV_PCM_FORMAT_S32_LE) | 
					
						
							|  |  |  | 			val = 0x05; | 
					
						
							|  |  |  | 		else if (pcm_format == SNDRV_PCM_FORMAT_S16_LE) | 
					
						
							|  |  |  | 			val = 0x04; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		dev_err(codec->dev, "Invalid DAI format\n"); | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	val = val << PCM1792A_FMT_SHIFT | PCM1792A_ATLD_ENABLE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = regmap_update_bits(priv->regmap, PCM1792A_FMT_CONTROL, | 
					
						
							|  |  |  | 				 PCM1792A_FMT_MASK | PCM1792A_ATLD_ENABLE, val); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct snd_soc_dai_ops pcm1792a_dai_ops = { | 
					
						
							|  |  |  | 	.set_fmt	= pcm1792a_set_dai_fmt, | 
					
						
							|  |  |  | 	.hw_params	= pcm1792a_hw_params, | 
					
						
							|  |  |  | 	.digital_mute	= pcm1792a_digital_mute, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const DECLARE_TLV_DB_SCALE(pcm1792a_dac_tlv, -12000, 50, 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct snd_kcontrol_new pcm1792a_controls[] = { | 
					
						
							|  |  |  | 	SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM1792A_DAC_VOL_LEFT, | 
					
						
							|  |  |  | 			 PCM1792A_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0, | 
					
						
							|  |  |  | 			 pcm1792a_dac_tlv), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-13 18:30:00 +01:00
										 |  |  | static const struct snd_soc_dapm_widget pcm1792a_dapm_widgets[] = { | 
					
						
							|  |  |  | SND_SOC_DAPM_OUTPUT("IOUTL+"), | 
					
						
							|  |  |  | SND_SOC_DAPM_OUTPUT("IOUTL-"), | 
					
						
							|  |  |  | SND_SOC_DAPM_OUTPUT("IOUTR+"), | 
					
						
							|  |  |  | SND_SOC_DAPM_OUTPUT("IOUTR-"), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct snd_soc_dapm_route pcm1792a_dapm_routes[] = { | 
					
						
							|  |  |  | 	{ "IOUTL+", NULL, "Playback" }, | 
					
						
							|  |  |  | 	{ "IOUTL-", NULL, "Playback" }, | 
					
						
							|  |  |  | 	{ "IOUTR+", NULL, "Playback" }, | 
					
						
							|  |  |  | 	{ "IOUTR-", NULL, "Playback" }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-03 16:20:43 +02:00
										 |  |  | static struct snd_soc_dai_driver pcm1792a_dai = { | 
					
						
							|  |  |  | 	.name = "pcm1792a-hifi", | 
					
						
							|  |  |  | 	.playback = { | 
					
						
							|  |  |  | 		.stream_name = "Playback", | 
					
						
							|  |  |  | 		.channels_min = 2, | 
					
						
							|  |  |  | 		.channels_max = 2, | 
					
						
							|  |  |  | 		.rates = PCM1792A_RATES, | 
					
						
							|  |  |  | 		.formats = PCM1792A_FORMATS, }, | 
					
						
							|  |  |  | 	.ops = &pcm1792a_dai_ops, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct of_device_id pcm1792a_of_match[] = { | 
					
						
							|  |  |  | 	{ .compatible = "ti,pcm1792a", }, | 
					
						
							|  |  |  | 	{ } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | MODULE_DEVICE_TABLE(of, pcm1792a_of_match); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct regmap_config pcm1792a_regmap = { | 
					
						
							|  |  |  | 	.reg_bits		= 8, | 
					
						
							|  |  |  | 	.val_bits		= 8, | 
					
						
							|  |  |  | 	.max_register		= 24, | 
					
						
							|  |  |  | 	.reg_defaults		= pcm1792a_reg_defaults, | 
					
						
							|  |  |  | 	.num_reg_defaults	= ARRAY_SIZE(pcm1792a_reg_defaults), | 
					
						
							|  |  |  | 	.writeable_reg		= pcm1792a_writeable_reg, | 
					
						
							|  |  |  | 	.readable_reg		= pcm1792a_accessible_reg, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct snd_soc_codec_driver soc_codec_dev_pcm1792a = { | 
					
						
							|  |  |  | 	.controls		= pcm1792a_controls, | 
					
						
							|  |  |  | 	.num_controls		= ARRAY_SIZE(pcm1792a_controls), | 
					
						
							| 
									
										
										
										
											2013-08-13 18:30:00 +01:00
										 |  |  | 	.dapm_widgets		= pcm1792a_dapm_widgets, | 
					
						
							|  |  |  | 	.num_dapm_widgets	= ARRAY_SIZE(pcm1792a_dapm_widgets), | 
					
						
							|  |  |  | 	.dapm_routes		= pcm1792a_dapm_routes, | 
					
						
							|  |  |  | 	.num_dapm_routes	= ARRAY_SIZE(pcm1792a_dapm_routes), | 
					
						
							| 
									
										
										
										
											2013-08-03 16:20:43 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pcm1792a_spi_probe(struct spi_device *spi) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pcm1792a_private *pcm1792a; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pcm1792a = devm_kzalloc(&spi->dev, sizeof(struct pcm1792a_private), | 
					
						
							|  |  |  | 				GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!pcm1792a) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spi_set_drvdata(spi, pcm1792a); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pcm1792a->regmap = devm_regmap_init_spi(spi, &pcm1792a_regmap); | 
					
						
							|  |  |  | 	if (IS_ERR(pcm1792a->regmap)) { | 
					
						
							|  |  |  | 		ret = PTR_ERR(pcm1792a->regmap); | 
					
						
							|  |  |  | 		dev_err(&spi->dev, "Failed to register regmap: %d\n", ret); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return snd_soc_register_codec(&spi->dev, | 
					
						
							|  |  |  | 			&soc_codec_dev_pcm1792a, &pcm1792a_dai, 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int pcm1792a_spi_remove(struct spi_device *spi) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	snd_soc_unregister_codec(&spi->dev); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct spi_device_id pcm1792a_spi_ids[] = { | 
					
						
							|  |  |  | 	{ "pcm1792a", 0 }, | 
					
						
							|  |  |  | 	{ }, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | MODULE_DEVICE_TABLE(spi, pcm1792a_spi_ids); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct spi_driver pcm1792a_codec_driver = { | 
					
						
							|  |  |  | 	.driver = { | 
					
						
							|  |  |  | 		.name = "pcm1792a", | 
					
						
							|  |  |  | 		.owner = THIS_MODULE, | 
					
						
							| 
									
										
										
										
											2013-08-05 18:20:53 +01:00
										 |  |  | 		.of_match_table = of_match_ptr(pcm1792a_of_match), | 
					
						
							| 
									
										
										
										
											2013-08-03 16:20:43 +02:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	.id_table = pcm1792a_spi_ids, | 
					
						
							|  |  |  | 	.probe = pcm1792a_spi_probe, | 
					
						
							|  |  |  | 	.remove = pcm1792a_spi_remove, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_spi_driver(pcm1792a_codec_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_DESCRIPTION("ASoC PCM1792A driver"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); |