232 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * PCM179X ASoC codec driver4 *5 * Copyright (c) Amarula Solutions B.V. 20136 *7 * Michael Trimarchi <michael@amarulasolutions.com>8 */9 10#include <linux/module.h>11#include <linux/slab.h>12#include <linux/kernel.h>13#include <linux/device.h>14 15#include <sound/core.h>16#include <sound/pcm.h>17#include <sound/pcm_params.h>18#include <sound/initval.h>19#include <sound/soc.h>20#include <sound/tlv.h>21#include <linux/of.h>22 23#include "pcm179x.h"24 25#define PCM179X_DAC_VOL_LEFT 0x1026#define PCM179X_DAC_VOL_RIGHT 0x1127#define PCM179X_FMT_CONTROL 0x1228#define PCM179X_MODE_CONTROL 0x1329#define PCM179X_SOFT_MUTE PCM179X_FMT_CONTROL30 31#define PCM179X_FMT_MASK 0x7032#define PCM179X_FMT_SHIFT 433#define PCM179X_MUTE_MASK 0x0134#define PCM179X_MUTE_SHIFT 035#define PCM179X_ATLD_ENABLE (1 << 7)36 37static const struct reg_default pcm179x_reg_defaults[] = {38 { 0x10, 0xff },39 { 0x11, 0xff },40 { 0x12, 0x50 },41 { 0x13, 0x00 },42 { 0x14, 0x00 },43 { 0x15, 0x01 },44 { 0x16, 0x00 },45 { 0x17, 0x00 },46};47 48static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg)49{50 return reg >= 0x10 && reg <= 0x17;51}52 53static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg)54{55 bool accessible;56 57 accessible = pcm179x_accessible_reg(dev, reg);58 59 return accessible && reg != 0x16 && reg != 0x17;60}61 62struct pcm179x_private {63 struct regmap *regmap;64 unsigned int format;65 unsigned int rate;66};67 68static int pcm179x_set_dai_fmt(struct snd_soc_dai *codec_dai,69 unsigned int format)70{71 struct snd_soc_component *component = codec_dai->component;72 struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);73 74 priv->format = format;75 76 return 0;77}78 79static int pcm179x_mute(struct snd_soc_dai *dai, int mute, int direction)80{81 struct snd_soc_component *component = dai->component;82 struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);83 int ret;84 85 ret = regmap_update_bits(priv->regmap, PCM179X_SOFT_MUTE,86 PCM179X_MUTE_MASK, !!mute);87 if (ret < 0)88 return ret;89 90 return 0;91}92 93static int pcm179x_hw_params(struct snd_pcm_substream *substream,94 struct snd_pcm_hw_params *params,95 struct snd_soc_dai *dai)96{97 struct snd_soc_component *component = dai->component;98 struct pcm179x_private *priv = snd_soc_component_get_drvdata(component);99 int val = 0, ret;100 101 priv->rate = params_rate(params);102 103 switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {104 case SND_SOC_DAIFMT_RIGHT_J:105 switch (params_width(params)) {106 case 24:107 case 32:108 val = 2;109 break;110 case 16:111 val = 0;112 break;113 default:114 return -EINVAL;115 }116 break;117 case SND_SOC_DAIFMT_I2S:118 switch (params_width(params)) {119 case 24:120 case 32:121 val = 5;122 break;123 case 16:124 val = 4;125 break;126 default:127 return -EINVAL;128 }129 break;130 default:131 dev_err(component->dev, "Invalid DAI format\n");132 return -EINVAL;133 }134 135 val = val << PCM179X_FMT_SHIFT | PCM179X_ATLD_ENABLE;136 137 ret = regmap_update_bits(priv->regmap, PCM179X_FMT_CONTROL,138 PCM179X_FMT_MASK | PCM179X_ATLD_ENABLE, val);139 if (ret < 0)140 return ret;141 142 return 0;143}144 145static const struct snd_soc_dai_ops pcm179x_dai_ops = {146 .set_fmt = pcm179x_set_dai_fmt,147 .hw_params = pcm179x_hw_params,148 .mute_stream = pcm179x_mute,149 .no_capture_mute = 1,150};151 152static const DECLARE_TLV_DB_SCALE(pcm179x_dac_tlv, -12000, 50, 1);153 154static const struct snd_kcontrol_new pcm179x_controls[] = {155 SOC_DOUBLE_R_RANGE_TLV("DAC Playback Volume", PCM179X_DAC_VOL_LEFT,156 PCM179X_DAC_VOL_RIGHT, 0, 0xf, 0xff, 0,157 pcm179x_dac_tlv),158 SOC_SINGLE("DAC Invert Output Switch", PCM179X_MODE_CONTROL, 7, 1, 0),159 SOC_SINGLE("DAC Rolloff Filter Switch", PCM179X_MODE_CONTROL, 1, 1, 0),160};161 162static const struct snd_soc_dapm_widget pcm179x_dapm_widgets[] = {163SND_SOC_DAPM_OUTPUT("IOUTL+"),164SND_SOC_DAPM_OUTPUT("IOUTL-"),165SND_SOC_DAPM_OUTPUT("IOUTR+"),166SND_SOC_DAPM_OUTPUT("IOUTR-"),167};168 169static const struct snd_soc_dapm_route pcm179x_dapm_routes[] = {170 { "IOUTL+", NULL, "Playback" },171 { "IOUTL-", NULL, "Playback" },172 { "IOUTR+", NULL, "Playback" },173 { "IOUTR-", NULL, "Playback" },174};175 176static struct snd_soc_dai_driver pcm179x_dai = {177 .name = "pcm179x-hifi",178 .playback = {179 .stream_name = "Playback",180 .channels_min = 2,181 .channels_max = 2,182 .rates = SNDRV_PCM_RATE_CONTINUOUS,183 .rate_min = 10000,184 .rate_max = 200000,185 .formats = PCM1792A_FORMATS, },186 .ops = &pcm179x_dai_ops,187};188 189const struct regmap_config pcm179x_regmap_config = {190 .reg_bits = 8,191 .val_bits = 8,192 .max_register = 23,193 .reg_defaults = pcm179x_reg_defaults,194 .num_reg_defaults = ARRAY_SIZE(pcm179x_reg_defaults),195 .writeable_reg = pcm179x_writeable_reg,196 .readable_reg = pcm179x_accessible_reg,197};198EXPORT_SYMBOL_GPL(pcm179x_regmap_config);199 200static const struct snd_soc_component_driver soc_component_dev_pcm179x = {201 .controls = pcm179x_controls,202 .num_controls = ARRAY_SIZE(pcm179x_controls),203 .dapm_widgets = pcm179x_dapm_widgets,204 .num_dapm_widgets = ARRAY_SIZE(pcm179x_dapm_widgets),205 .dapm_routes = pcm179x_dapm_routes,206 .num_dapm_routes = ARRAY_SIZE(pcm179x_dapm_routes),207 .idle_bias_on = 1,208 .use_pmdown_time = 1,209 .endianness = 1,210};211 212int pcm179x_common_init(struct device *dev, struct regmap *regmap)213{214 struct pcm179x_private *pcm179x;215 216 pcm179x = devm_kzalloc(dev, sizeof(struct pcm179x_private),217 GFP_KERNEL);218 if (!pcm179x)219 return -ENOMEM;220 221 pcm179x->regmap = regmap;222 dev_set_drvdata(dev, pcm179x);223 224 return devm_snd_soc_register_component(dev,225 &soc_component_dev_pcm179x, &pcm179x_dai, 1);226}227EXPORT_SYMBOL_GPL(pcm179x_common_init);228 229MODULE_DESCRIPTION("ASoC PCM179X driver");230MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");231MODULE_LICENSE("GPL");232