305 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Universal Interface for Intel High Definition Audio Codec4 *5 * HD audio interface patch for Silicon Labs 3054/5 modem codec6 *7 * Copyright (c) 2005 Sasha Khapyorsky <sashak@alsa-project.org>8 * Takashi Iwai <tiwai@suse.de>9 */10 11#include <linux/init.h>12#include <linux/delay.h>13#include <linux/slab.h>14#include <linux/module.h>15#include <sound/core.h>16#include <sound/hda_codec.h>17#include "hda_local.h"18 19/* si3054 verbs */20#define SI3054_VERB_READ_NODE 0x90021#define SI3054_VERB_WRITE_NODE 0x10022 23/* si3054 nodes (registers) */24#define SI3054_EXTENDED_MID 225#define SI3054_LINE_RATE 326#define SI3054_LINE_LEVEL 427#define SI3054_GPIO_CFG 528#define SI3054_GPIO_POLARITY 629#define SI3054_GPIO_STICKY 730#define SI3054_GPIO_WAKEUP 831#define SI3054_GPIO_STATUS 932#define SI3054_GPIO_CONTROL 1033#define SI3054_MISC_AFE 1134#define SI3054_CHIPID 1235#define SI3054_LINE_CFG1 1336#define SI3054_LINE_STATUS 1437#define SI3054_DC_TERMINATION 1538#define SI3054_LINE_CONFIG 1639#define SI3054_CALLPROG_ATT 1740#define SI3054_SQ_CONTROL 1841#define SI3054_MISC_CONTROL 1942#define SI3054_RING_CTRL1 2043#define SI3054_RING_CTRL2 2144 45/* extended MID */46#define SI3054_MEI_READY 0xf47 48/* line level */49#define SI3054_ATAG_MASK 0x00f050#define SI3054_DTAG_MASK 0xf00051 52/* GPIO bits */53#define SI3054_GPIO_OH 0x000154#define SI3054_GPIO_CID 0x000255 56/* chipid and revisions */57#define SI3054_CHIPID_CODEC_REV_MASK 0x000f58#define SI3054_CHIPID_DAA_REV_MASK 0x00f059#define SI3054_CHIPID_INTERNATIONAL 0x010060#define SI3054_CHIPID_DAA_ID 0x0f0061#define SI3054_CHIPID_CODEC_ID (1<<12)62 63/* si3054 codec registers (nodes) access macros */64#define GET_REG(codec,reg) (snd_hda_codec_read(codec,reg,0,SI3054_VERB_READ_NODE,0))65#define SET_REG(codec,reg,val) (snd_hda_codec_write(codec,reg,0,SI3054_VERB_WRITE_NODE,val))66#define SET_REG_CACHE(codec,reg,val) \67 snd_hda_codec_write_cache(codec,reg,0,SI3054_VERB_WRITE_NODE,val)68 69 70struct si3054_spec {71 unsigned international;72};73 74 75/*76 * Modem mixer77 */78 79#define PRIVATE_VALUE(reg,mask) ((reg<<16)|(mask&0xffff))80#define PRIVATE_REG(val) ((val>>16)&0xffff)81#define PRIVATE_MASK(val) (val&0xffff)82 83#define si3054_switch_info snd_ctl_boolean_mono_info84 85static int si3054_switch_get(struct snd_kcontrol *kcontrol,86 struct snd_ctl_elem_value *uvalue)87{88 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);89 u16 reg = PRIVATE_REG(kcontrol->private_value);90 u16 mask = PRIVATE_MASK(kcontrol->private_value);91 uvalue->value.integer.value[0] = (GET_REG(codec, reg)) & mask ? 1 : 0 ;92 return 0;93}94 95static int si3054_switch_put(struct snd_kcontrol *kcontrol,96 struct snd_ctl_elem_value *uvalue)97{98 struct hda_codec *codec = snd_kcontrol_chip(kcontrol);99 u16 reg = PRIVATE_REG(kcontrol->private_value);100 u16 mask = PRIVATE_MASK(kcontrol->private_value);101 if (uvalue->value.integer.value[0])102 SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) | mask);103 else104 SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) & ~mask);105 return 0;106}107 108#define SI3054_KCONTROL(kname,reg,mask) { \109 .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \110 .name = kname, \111 .subdevice = HDA_SUBDEV_NID_FLAG | reg, \112 .info = si3054_switch_info, \113 .get = si3054_switch_get, \114 .put = si3054_switch_put, \115 .private_value = PRIVATE_VALUE(reg,mask), \116}117 118 119static const struct snd_kcontrol_new si3054_modem_mixer[] = {120 SI3054_KCONTROL("Off-hook Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_OH),121 SI3054_KCONTROL("Caller ID Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_CID),122 {}123};124 125static int si3054_build_controls(struct hda_codec *codec)126{127 return snd_hda_add_new_ctls(codec, si3054_modem_mixer);128}129 130 131/*132 * PCM callbacks133 */134 135static int si3054_pcm_prepare(struct hda_pcm_stream *hinfo,136 struct hda_codec *codec,137 unsigned int stream_tag,138 unsigned int format,139 struct snd_pcm_substream *substream)140{141 u16 val;142 143 SET_REG(codec, SI3054_LINE_RATE, substream->runtime->rate);144 val = GET_REG(codec, SI3054_LINE_LEVEL);145 val &= 0xff << (8 * (substream->stream != SNDRV_PCM_STREAM_PLAYBACK));146 val |= ((stream_tag & 0xf) << 4) << (8 * (substream->stream == SNDRV_PCM_STREAM_PLAYBACK));147 SET_REG(codec, SI3054_LINE_LEVEL, val);148 149 snd_hda_codec_setup_stream(codec, hinfo->nid,150 stream_tag, 0, format);151 return 0;152}153 154static int si3054_pcm_open(struct hda_pcm_stream *hinfo,155 struct hda_codec *codec,156 struct snd_pcm_substream *substream)157{158 static const unsigned int rates[] = { 8000, 9600, 16000 };159 static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {160 .count = ARRAY_SIZE(rates),161 .list = rates,162 .mask = 0,163 };164 substream->runtime->hw.period_bytes_min = 80;165 return snd_pcm_hw_constraint_list(substream->runtime, 0,166 SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);167}168 169 170static const struct hda_pcm_stream si3054_pcm = {171 .substreams = 1,172 .channels_min = 1,173 .channels_max = 1,174 .nid = 0x1,175 .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_KNOT,176 .formats = SNDRV_PCM_FMTBIT_S16_LE,177 .maxbps = 16,178 .ops = {179 .open = si3054_pcm_open,180 .prepare = si3054_pcm_prepare,181 },182};183 184 185static int si3054_build_pcms(struct hda_codec *codec)186{187 struct hda_pcm *info;188 189 info = snd_hda_codec_pcm_new(codec, "Si3054 Modem");190 if (!info)191 return -ENOMEM;192 info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm;193 info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm;194 info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = codec->core.mfg;195 info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = codec->core.mfg;196 info->pcm_type = HDA_PCM_TYPE_MODEM;197 return 0;198}199 200 201/*202 * Init part203 */204 205static int si3054_init(struct hda_codec *codec)206{207 struct si3054_spec *spec = codec->spec;208 unsigned wait_count;209 u16 val;210 211 if (snd_hdac_regmap_add_vendor_verb(&codec->core,212 SI3054_VERB_WRITE_NODE))213 return -ENOMEM;214 215 snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0);216 snd_hda_codec_write(codec, codec->core.mfg, 0, AC_VERB_SET_STREAM_FORMAT, 0);217 SET_REG(codec, SI3054_LINE_RATE, 9600);218 SET_REG(codec, SI3054_LINE_LEVEL, SI3054_DTAG_MASK|SI3054_ATAG_MASK);219 SET_REG(codec, SI3054_EXTENDED_MID, 0);220 221 wait_count = 10;222 do {223 msleep(2);224 val = GET_REG(codec, SI3054_EXTENDED_MID);225 } while ((val & SI3054_MEI_READY) != SI3054_MEI_READY && wait_count--);226 227 if((val&SI3054_MEI_READY) != SI3054_MEI_READY) {228 codec_err(codec, "si3054: cannot initialize. EXT MID = %04x\n", val);229 /* let's pray that this is no fatal error */230 /* return -EACCES; */231 }232 233 SET_REG(codec, SI3054_GPIO_POLARITY, 0xffff);234 SET_REG(codec, SI3054_GPIO_CFG, 0x0);235 SET_REG(codec, SI3054_MISC_AFE, 0);236 SET_REG(codec, SI3054_LINE_CFG1,0x200);237 238 if((GET_REG(codec,SI3054_LINE_STATUS) & (1<<6)) == 0) {239 codec_dbg(codec,240 "Link Frame Detect(FDT) is not ready (line status: %04x)\n",241 GET_REG(codec,SI3054_LINE_STATUS));242 }243 244 spec->international = GET_REG(codec, SI3054_CHIPID) & SI3054_CHIPID_INTERNATIONAL;245 246 return 0;247}248 249static void si3054_free(struct hda_codec *codec)250{251 kfree(codec->spec);252}253 254 255/*256 */257 258static const struct hda_codec_ops si3054_patch_ops = {259 .build_controls = si3054_build_controls,260 .build_pcms = si3054_build_pcms,261 .init = si3054_init,262 .free = si3054_free,263};264 265static int patch_si3054(struct hda_codec *codec)266{267 struct si3054_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL);268 if (spec == NULL)269 return -ENOMEM;270 codec->spec = spec;271 codec->patch_ops = si3054_patch_ops;272 return 0;273}274 275/*276 * patch entries277 */278static const struct hda_device_id snd_hda_id_si3054[] = {279 HDA_CODEC_ENTRY(0x163c3055, "Si3054", patch_si3054),280 HDA_CODEC_ENTRY(0x163c3155, "Si3054", patch_si3054),281 HDA_CODEC_ENTRY(0x11c13026, "Si3054", patch_si3054),282 HDA_CODEC_ENTRY(0x11c13055, "Si3054", patch_si3054),283 HDA_CODEC_ENTRY(0x11c13155, "Si3054", patch_si3054),284 HDA_CODEC_ENTRY(0x10573055, "Si3054", patch_si3054),285 HDA_CODEC_ENTRY(0x10573057, "Si3054", patch_si3054),286 HDA_CODEC_ENTRY(0x10573155, "Si3054", patch_si3054),287 /* VIA HDA on Clevo m540 */288 HDA_CODEC_ENTRY(0x11063288, "Si3054", patch_si3054),289 /* Asus A8J Modem (SM56) */290 HDA_CODEC_ENTRY(0x15433155, "Si3054", patch_si3054),291 /* LG LW20 modem */292 HDA_CODEC_ENTRY(0x18540018, "Si3054", patch_si3054),293 {}294};295MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_si3054);296 297MODULE_LICENSE("GPL");298MODULE_DESCRIPTION("Si3054 HD-audio modem codec");299 300static struct hda_codec_driver si3054_driver = {301 .id = snd_hda_id_si3054,302};303 304module_hda_codec_driver(si3054_driver);305