📄 wm8750.c
字号:
{12000000, 22050, 544, 0x1b, 0x1}, /* 32k */ {12288000, 32000, 384, 0xc, 0x0}, {18432000, 32000, 576, 0xd, 0x0}, {12000000, 32000, 375, 0xa, 0x1}, /* 44.1k */ {11289600, 44100, 256, 0x10, 0x0}, {16934400, 44100, 384, 0x11, 0x0}, {12000000, 44100, 272, 0x11, 0x1}, /* 48k */ {12288000, 48000, 256, 0x0, 0x0}, {18432000, 48000, 384, 0x1, 0x0}, {12000000, 48000, 250, 0x0, 0x1}, /* 88.2k */ {11289600, 88200, 128, 0x1e, 0x0}, {16934400, 88200, 192, 0x1f, 0x0}, {12000000, 88200, 136, 0x1f, 0x1}, /* 96k */ {12288000, 96000, 128, 0xe, 0x0}, {18432000, 96000, 192, 0xf, 0x0}, {12000000, 96000, 125, 0xe, 0x1},};static inline int get_coeff(int mclk, int rate){ int i; for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) return i; } printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n", mclk, rate); return -EINVAL;}static int wm8750_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, int clk_id, unsigned int freq, int dir){ struct snd_soc_codec *codec = codec_dai->codec; struct wm8750_priv *wm8750 = codec->private_data; switch (freq) { case 11289600: case 12000000: case 12288000: case 16934400: case 18432000: wm8750->sysclk = freq; return 0; } return -EINVAL;}static int wm8750_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt){ struct snd_soc_codec *codec = codec_dai->codec; u16 iface = 0; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: iface = 0x0040; break; case SND_SOC_DAIFMT_CBS_CFS: break; default: return -EINVAL; } /* interface format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: iface |= 0x0002; break; case SND_SOC_DAIFMT_RIGHT_J: break; case SND_SOC_DAIFMT_LEFT_J: iface |= 0x0001; break; case SND_SOC_DAIFMT_DSP_A: iface |= 0x0003; break; case SND_SOC_DAIFMT_DSP_B: iface |= 0x0013; break; default: return -EINVAL; } /* clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_IF: iface |= 0x0090; break; case SND_SOC_DAIFMT_IB_NF: iface |= 0x0080; break; case SND_SOC_DAIFMT_NB_IF: iface |= 0x0010; break; default: return -EINVAL; } wm8750_write(codec, WM8750_IFACE, iface); return 0;}static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; struct wm8750_priv *wm8750 = codec->private_data; u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3; u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0; int coeff = get_coeff(wm8750->sysclk, params_rate(params)); /* bit size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: break; case SNDRV_PCM_FORMAT_S20_3LE: iface |= 0x0004; break; case SNDRV_PCM_FORMAT_S24_LE: iface |= 0x0008; break; case SNDRV_PCM_FORMAT_S32_LE: iface |= 0x000c; break; } /* set iface & srate */ wm8750_write(codec, WM8750_IFACE, iface); if (coeff >= 0) wm8750_write(codec, WM8750_SRATE, srate | (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); return 0;}static int wm8750_mute(struct snd_soc_codec_dai *dai, int mute){ struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7; if (mute) wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8); else wm8750_write(codec, WM8750_ADCDAC, mute_reg); return 0;}static int wm8750_dapm_event(struct snd_soc_codec *codec, int event){ u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e; switch (event) { case SNDRV_CTL_POWER_D0: /* full On */ /* set vmid to 50k and unmute dac */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; case SNDRV_CTL_POWER_D1: /* partial On */ case SNDRV_CTL_POWER_D2: /* partial On */ /* set vmid to 5k for quick power up */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; case SNDRV_CTL_POWER_D3hot: /* Off, with power */ /* mute dac and set vmid to 500k, enable VREF */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; case SNDRV_CTL_POWER_D3cold: /* Off, without power */ wm8750_write(codec, WM8750_PWR1, 0x0001); break; } codec->dapm_state = event; return 0;}#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE)struct snd_soc_codec_dai wm8750_dai = { .name = "WM8750", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = WM8750_RATES, .formats = WM8750_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8750_RATES, .formats = WM8750_FORMATS,}, .ops = { .hw_params = wm8750_pcm_hw_params, }, .dai_ops = { .digital_mute = wm8750_mute, .set_fmt = wm8750_set_dai_fmt, .set_sysclk = wm8750_set_dai_sysclk, },};EXPORT_SYMBOL_GPL(wm8750_dai);static void wm8750_work(struct work_struct *work){ struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); wm8750_dapm_event(codec, codec->dapm_state);}static int wm8750_suspend(struct platform_device *pdev, pm_message_t state){ struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); return 0;}static int wm8750_resume(struct platform_device *pdev){ struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; int i; u8 data[2]; u16 *cache = codec->reg_cache; /* Sync reg_cache with the hardware */ for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) { if (i == WM8750_RESET) continue; data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); data[1] = cache[i] & 0x00ff; codec->hw_write(codec->control_data, data, 2); } wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3hot); /* charge wm8750 caps */ if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); codec->dapm_state = SNDRV_CTL_POWER_D0; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); } return 0;}/* * initialise the WM8750 driver * register the mixer and dsp interfaces with the kernel */static int wm8750_init(struct snd_soc_device *socdev){ struct snd_soc_codec *codec = socdev->codec; int reg, ret = 0; codec->name = "WM8750"; codec->owner = THIS_MODULE; codec->read = wm8750_read_reg_cache; codec->write = wm8750_write; codec->dapm_event = wm8750_dapm_event; codec->dai = &wm8750_dai; codec->num_dai = 1; codec->reg_cache_size = sizeof(wm8750_reg); codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; wm8750_reset(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "wm8750: failed to create pcms\n"); goto pcm_err; } /* charge output caps */ wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); codec->dapm_state = SNDRV_CTL_POWER_D3hot; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ reg = wm8750_read_reg_cache(codec, WM8750_LDAC); wm8750_write(codec, WM8750_LDAC, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_RDAC); wm8750_write(codec, WM8750_RDAC, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V); wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V); wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V); wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V); wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_LINVOL); wm8750_write(codec, WM8750_LINVOL, reg | 0x0100); reg = wm8750_read_reg_cache(codec, WM8750_RINVOL); wm8750_write(codec, WM8750_RINVOL, reg | 0x0100); wm8750_add_controls(codec); wm8750_add_widgets(codec); ret = snd_soc_register_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8750: failed to register card\n"); goto card_err; } return ret;card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev);pcm_err: kfree(codec->reg_cache); return ret;}/* If the i2c layer weren't so broken, we could pass this kind of data around */static struct snd_soc_device *wm8750_socdev;#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)/* * WM8731 2 wire address is determined by GPIO5 * state during powerup. * low = 0x1a * high = 0x1b */static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };/* Magic definition of all other variables and things */I2C_CLIENT_INSMOD;static struct i2c_driver wm8750_i2c_driver;static struct i2c_client client_template;static int wm8750_codec_probe(struct i2c_adapter *adap, int addr, int kind){ struct snd_soc_device *socdev = wm8750_socdev; struct wm8750_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; struct i2c_client *i2c; int ret; if (addr != setup->i2c_address) return -ENODEV; client_template.adapter = adap; client_template.addr = addr; i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); if (i2c == NULL) { kfree(codec); return -ENOMEM; } i2c_set_clientdata(i2c, codec); codec->control_data = i2c; ret = i2c_attach_client(i2c); if (ret < 0) { err("failed to attach codec at addr %x\n", addr); goto err; } ret = wm8750_init(socdev); if (ret < 0) { err("failed to initialise WM8750\n"); goto err; } return ret;err: kfree(codec); kfree(i2c); return ret;}static int wm8750_i2c_detach(struct i2c_client *client){ struct snd_soc_codec *codec = i2c_get_clientdata(client); i2c_detach_client(client); kfree(codec->reg_cache); kfree(client); return 0;}static int wm8750_i2c_attach(struct i2c_adapter *adap){ return i2c_probe(adap, &addr_data, wm8750_codec_probe);}/* corgi i2c codec control layer */static struct i2c_driver wm8750_i2c_driver = { .driver = { .name = "WM8750 I2C Codec", .owner = THIS_MODULE, }, .id = I2C_DRIVERID_WM8750, .attach_adapter = wm8750_i2c_attach, .detach_client = wm8750_i2c_detach, .command = NULL,};static struct i2c_client client_template = { .name = "WM8750", .driver = &wm8750_i2c_driver,};#endifstatic int wm8750_probe(struct platform_device *pdev){ struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct wm8750_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec; struct wm8750_priv *wm8750; int ret = 0; info("WM8750 Audio Codec %s", WM8750_VERSION); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); if (wm8750 == NULL) { kfree(codec); return -ENOMEM; } codec->private_data = wm8750; socdev->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); wm8750_socdev = socdev; INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); #if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) if (setup->i2c_address) { normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; ret = i2c_add_driver(&wm8750_i2c_driver); if (ret != 0) printk(KERN_ERR "can't add i2c driver"); }#else /* Add other interfaces here */#endif return ret;}/* * This function forces any delayed work to be queued and run. */static int run_delayed_work(struct delayed_work *dwork){ int ret; /* cancel any work waiting to be queued. */ ret = cancel_delayed_work(dwork); /* if there was any work waiting then we run it now and * wait for it's completion */ if (ret) { schedule_delayed_work(dwork, 0); flush_scheduled_work(); } return ret;}/* power down chip */static int wm8750_remove(struct platform_device *pdev){ struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev);#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) i2c_del_driver(&wm8750_i2c_driver);#endif kfree(codec->private_data); kfree(codec); return 0;}struct snd_soc_codec_device soc_codec_dev_wm8750 = { .probe = wm8750_probe, .remove = wm8750_remove, .suspend = wm8750_suspend, .resume = wm8750_resume,};EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750);MODULE_DESCRIPTION("ASoC WM8750 driver");MODULE_AUTHOR("Liam Girdwood");MODULE_LICENSE("GPL");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -