391 lines · c
1/*2 * ASoC codec driver for spear platform3 *4 * sound/soc/codecs/sta529.c -- spear ALSA Soc codec driver5 *6 * Copyright (C) 2012 ST Microelectronics7 * Rajeev Kumar <rajeevkumar.linux@gmail.com>8 *9 * This file is licensed under the terms of the GNU General Public10 * License version 2. This program is licensed "as is" without any11 * warranty of any kind, whether express or implied.12 */13 14#include <linux/clk.h>15#include <linux/init.h>16#include <linux/i2c.h>17#include <linux/io.h>18#include <linux/module.h>19#include <linux/moduleparam.h>20#include <linux/pm.h>21#include <linux/regmap.h>22#include <linux/slab.h>23 24#include <sound/core.h>25#include <sound/initval.h>26#include <sound/pcm.h>27#include <sound/pcm_params.h>28#include <sound/soc.h>29#include <sound/soc-dapm.h>30#include <sound/tlv.h>31 32/* STA529 Register offsets */33#define STA529_FFXCFG0 0x0034#define STA529_FFXCFG1 0x0135#define STA529_MVOL 0x0236#define STA529_LVOL 0x0337#define STA529_RVOL 0x0438#define STA529_TTF0 0x0539#define STA529_TTF1 0x0640#define STA529_TTP0 0x0741#define STA529_TTP1 0x0842#define STA529_S2PCFG0 0x0A43#define STA529_S2PCFG1 0x0B44#define STA529_P2SCFG0 0x0C45#define STA529_P2SCFG1 0x0D46#define STA529_PLLCFG0 0x1447#define STA529_PLLCFG1 0x1548#define STA529_PLLCFG2 0x1649#define STA529_PLLCFG3 0x1750#define STA529_PLLPFE 0x1851#define STA529_PLLST 0x1952#define STA529_ADCCFG 0x1E /*mic_select*/53#define STA529_CKOCFG 0x1F54#define STA529_MISC 0x2055#define STA529_PADST0 0x2156#define STA529_PADST1 0x2257#define STA529_FFXST 0x2358#define STA529_PWMIN1 0x2D59#define STA529_PWMIN2 0x2E60#define STA529_POWST 0x3261 62#define STA529_MAX_REGISTER 0x3263 64#define STA529_RATES (SNDRV_PCM_RATE_8000 | \65 SNDRV_PCM_RATE_11025 | \66 SNDRV_PCM_RATE_16000 | \67 SNDRV_PCM_RATE_22050 | \68 SNDRV_PCM_RATE_32000 | \69 SNDRV_PCM_RATE_44100 | \70 SNDRV_PCM_RATE_48000)71 72#define STA529_FORMAT (SNDRV_PCM_FMTBIT_S16_LE | \73 SNDRV_PCM_FMTBIT_S24_LE | \74 SNDRV_PCM_FMTBIT_S32_LE)75#define S2PC_VALUE 0x9876#define CLOCK_OUT 0x6077#define DATA_FORMAT_MSK 0x0E78#define LEFT_J_DATA_FORMAT 0x0079#define I2S_DATA_FORMAT 0x0280#define RIGHT_J_DATA_FORMAT 0x0481#define CODEC_MUTE_VAL 0x8082 83#define POWER_CNTLMSAK 0x4084#define POWER_STDBY 0x4085#define FFX_MASK 0x8086#define FFX_OFF 0x8087#define POWER_UP 0x0088#define FFX_CLK_ENB 0x0189#define FFX_CLK_DIS 0x0090#define FFX_CLK_MSK 0x0191#define PLAY_FREQ_RANGE_MSK 0x7092#define CAP_FREQ_RANGE_MSK 0x0C93#define PDATA_LEN_MSK 0xC094#define BCLK_TO_FS_MSK 0x3095#define AUDIO_MUTE_MSK 0x8096 97static const struct reg_default sta529_reg_defaults[] = {98 { 0, 0x35 }, /* R0 - FFX Configuration reg 0 */99 { 1, 0xc8 }, /* R1 - FFX Configuration reg 1 */100 { 2, 0x50 }, /* R2 - Master Volume */101 { 3, 0x00 }, /* R3 - Left Volume */102 { 4, 0x00 }, /* R4 - Right Volume */103 { 10, 0xb2 }, /* R10 - S2P Config Reg 0 */104 { 11, 0x41 }, /* R11 - S2P Config Reg 1 */105 { 12, 0x92 }, /* R12 - P2S Config Reg 0 */106 { 13, 0x41 }, /* R13 - P2S Config Reg 1 */107 { 30, 0xd2 }, /* R30 - ADC Config Reg */108 { 31, 0x40 }, /* R31 - clock Out Reg */109 { 32, 0x21 }, /* R32 - Misc Register */110};111 112struct sta529 {113 struct regmap *regmap;114};115 116static bool sta529_readable(struct device *dev, unsigned int reg)117{118 switch (reg) {119 120 case STA529_FFXCFG0:121 case STA529_FFXCFG1:122 case STA529_MVOL:123 case STA529_LVOL:124 case STA529_RVOL:125 case STA529_S2PCFG0:126 case STA529_S2PCFG1:127 case STA529_P2SCFG0:128 case STA529_P2SCFG1:129 case STA529_ADCCFG:130 case STA529_CKOCFG:131 case STA529_MISC:132 return true;133 default:134 return false;135 }136}137 138 139static const char *pwm_mode_text[] = { "Binary", "Headphone", "Ternary",140 "Phase-shift"};141 142static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -9150, 50, 0);143static const DECLARE_TLV_DB_SCALE(master_vol_tlv, -12750, 50, 0);144static SOC_ENUM_SINGLE_DECL(pwm_src, STA529_FFXCFG1, 4, pwm_mode_text);145 146static const struct snd_kcontrol_new sta529_snd_controls[] = {147 SOC_DOUBLE_R_TLV("Digital Playback Volume", STA529_LVOL, STA529_RVOL, 0,148 127, 0, out_gain_tlv),149 SOC_SINGLE_TLV("Master Playback Volume", STA529_MVOL, 0, 127, 1,150 master_vol_tlv),151 SOC_ENUM("PWM Select", pwm_src),152};153 154static int sta529_set_bias_level(struct snd_soc_component *component, enum155 snd_soc_bias_level level)156{157 struct sta529 *sta529 = snd_soc_component_get_drvdata(component);158 159 switch (level) {160 case SND_SOC_BIAS_ON:161 case SND_SOC_BIAS_PREPARE:162 snd_soc_component_update_bits(component, STA529_FFXCFG0, POWER_CNTLMSAK,163 POWER_UP);164 snd_soc_component_update_bits(component, STA529_MISC, FFX_CLK_MSK,165 FFX_CLK_ENB);166 break;167 case SND_SOC_BIAS_STANDBY:168 if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)169 regcache_sync(sta529->regmap);170 snd_soc_component_update_bits(component, STA529_FFXCFG0,171 POWER_CNTLMSAK, POWER_STDBY);172 /* Making FFX output to zero */173 snd_soc_component_update_bits(component, STA529_FFXCFG0, FFX_MASK,174 FFX_OFF);175 snd_soc_component_update_bits(component, STA529_MISC, FFX_CLK_MSK,176 FFX_CLK_DIS);177 break;178 case SND_SOC_BIAS_OFF:179 break;180 }181 182 return 0;183 184}185 186static int sta529_hw_params(struct snd_pcm_substream *substream,187 struct snd_pcm_hw_params *params,188 struct snd_soc_dai *dai)189{190 struct snd_soc_component *component = dai->component;191 int pdata, play_freq_val, record_freq_val;192 int bclk_to_fs_ratio;193 194 switch (params_width(params)) {195 case 16:196 pdata = 1;197 bclk_to_fs_ratio = 0;198 break;199 case 24:200 pdata = 2;201 bclk_to_fs_ratio = 1;202 break;203 case 32:204 pdata = 3;205 bclk_to_fs_ratio = 2;206 break;207 default:208 dev_err(component->dev, "Unsupported format\n");209 return -EINVAL;210 }211 212 switch (params_rate(params)) {213 case 8000:214 case 11025:215 play_freq_val = 0;216 record_freq_val = 2;217 break;218 case 16000:219 case 22050:220 play_freq_val = 1;221 record_freq_val = 0;222 break;223 224 case 32000:225 case 44100:226 case 48000:227 play_freq_val = 2;228 record_freq_val = 0;229 break;230 default:231 dev_err(component->dev, "Unsupported rate\n");232 return -EINVAL;233 }234 235 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {236 snd_soc_component_update_bits(component, STA529_S2PCFG1, PDATA_LEN_MSK,237 pdata << 6);238 snd_soc_component_update_bits(component, STA529_S2PCFG1, BCLK_TO_FS_MSK,239 bclk_to_fs_ratio << 4);240 snd_soc_component_update_bits(component, STA529_MISC, PLAY_FREQ_RANGE_MSK,241 play_freq_val << 4);242 } else {243 snd_soc_component_update_bits(component, STA529_P2SCFG1, PDATA_LEN_MSK,244 pdata << 6);245 snd_soc_component_update_bits(component, STA529_P2SCFG1, BCLK_TO_FS_MSK,246 bclk_to_fs_ratio << 4);247 snd_soc_component_update_bits(component, STA529_MISC, CAP_FREQ_RANGE_MSK,248 record_freq_val << 2);249 }250 251 return 0;252}253 254static int sta529_mute(struct snd_soc_dai *dai, int mute, int direction)255{256 u8 val = 0;257 258 if (mute)259 val |= CODEC_MUTE_VAL;260 261 snd_soc_component_update_bits(dai->component, STA529_FFXCFG0, AUDIO_MUTE_MSK, val);262 263 return 0;264}265 266static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt)267{268 struct snd_soc_component *component = codec_dai->component;269 u8 mode = 0;270 271 /* interface format */272 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {273 case SND_SOC_DAIFMT_LEFT_J:274 mode = LEFT_J_DATA_FORMAT;275 break;276 case SND_SOC_DAIFMT_I2S:277 mode = I2S_DATA_FORMAT;278 break;279 case SND_SOC_DAIFMT_RIGHT_J:280 mode = RIGHT_J_DATA_FORMAT;281 break;282 default:283 return -EINVAL;284 }285 286 snd_soc_component_update_bits(component, STA529_S2PCFG0, DATA_FORMAT_MSK, mode);287 288 return 0;289}290 291static const struct snd_soc_dai_ops sta529_dai_ops = {292 .hw_params = sta529_hw_params,293 .set_fmt = sta529_set_dai_fmt,294 .mute_stream = sta529_mute,295 .no_capture_mute = 1,296};297 298static struct snd_soc_dai_driver sta529_dai = {299 .name = "sta529-audio",300 .playback = {301 .stream_name = "Playback",302 .channels_min = 2,303 .channels_max = 2,304 .rates = STA529_RATES,305 .formats = STA529_FORMAT,306 },307 .capture = {308 .stream_name = "Capture",309 .channels_min = 2,310 .channels_max = 2,311 .rates = STA529_RATES,312 .formats = STA529_FORMAT,313 },314 .ops = &sta529_dai_ops,315};316 317static const struct snd_soc_component_driver sta529_component_driver = {318 .set_bias_level = sta529_set_bias_level,319 .controls = sta529_snd_controls,320 .num_controls = ARRAY_SIZE(sta529_snd_controls),321 .suspend_bias_off = 1,322 .idle_bias_on = 1,323 .use_pmdown_time = 1,324 .endianness = 1,325};326 327static const struct regmap_config sta529_regmap = {328 .reg_bits = 8,329 .val_bits = 8,330 331 .max_register = STA529_MAX_REGISTER,332 .readable_reg = sta529_readable,333 334 .cache_type = REGCACHE_MAPLE,335 .reg_defaults = sta529_reg_defaults,336 .num_reg_defaults = ARRAY_SIZE(sta529_reg_defaults),337};338 339static int sta529_i2c_probe(struct i2c_client *i2c)340{341 struct sta529 *sta529;342 int ret;343 344 sta529 = devm_kzalloc(&i2c->dev, sizeof(struct sta529), GFP_KERNEL);345 if (!sta529)346 return -ENOMEM;347 348 sta529->regmap = devm_regmap_init_i2c(i2c, &sta529_regmap);349 if (IS_ERR(sta529->regmap)) {350 ret = PTR_ERR(sta529->regmap);351 dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret);352 return ret;353 }354 355 i2c_set_clientdata(i2c, sta529);356 357 ret = devm_snd_soc_register_component(&i2c->dev,358 &sta529_component_driver, &sta529_dai, 1);359 if (ret != 0)360 dev_err(&i2c->dev, "Failed to register CODEC: %d\n", ret);361 362 return ret;363}364 365static const struct i2c_device_id sta529_i2c_id[] = {366 { "sta529" },367 { }368};369MODULE_DEVICE_TABLE(i2c, sta529_i2c_id);370 371static const struct of_device_id sta529_of_match[] = {372 { .compatible = "st,sta529", },373 { }374};375MODULE_DEVICE_TABLE(of, sta529_of_match);376 377static struct i2c_driver sta529_i2c_driver = {378 .driver = {379 .name = "sta529",380 .of_match_table = sta529_of_match,381 },382 .probe = sta529_i2c_probe,383 .id_table = sta529_i2c_id,384};385 386module_i2c_driver(sta529_i2c_driver);387 388MODULE_DESCRIPTION("ASoC STA529 codec driver");389MODULE_AUTHOR("Rajeev Kumar <rajeevkumar.linux@gmail.com>");390MODULE_LICENSE("GPL");391