--- /dev/null
+/*
+ * rk2928_codec.c ALSA SoC RK2928 codec driver
+ *
+ * Copyright 2012 Rockchip
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "rk2928_codec.h"
+
+static struct rk2928_codec_data {
+ struct device *dev;
+ struct clk *hclk;
+ int regbase;
+ int regbase_phy;
+ int regsize_phy;
+ int mute;
+} rk2928_data;
+
+static const struct snd_soc_dapm_widget rk2928_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DACL", "HIFI Playback", CODEC_REG_POWER, 5, 1),
+ SND_SOC_DAPM_DAC("DACR", "HIFI Playback", CODEC_REG_POWER, 4, 1),
+ SND_SOC_DAPM_PGA("DACL Amp", CODEC_REG_DAC_GAIN, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("DACR Amp", CODEC_REG_DAC_GAIN, 0, 0, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("DACL Drv", CODEC_REG_DAC_MUTE, 1, 1, NULL, 0),
+ SND_SOC_DAPM_OUT_DRV("DACR Drv", CODEC_REG_DAC_MUTE, 0, 1, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_ADC("ADCL", "HIFI Capture", CODEC_REG_POWER, 3, 1),
+ SND_SOC_DAPM_ADC("ADCR", "HIFI Capture", CODEC_REG_POWER, 2, 1),
+ SND_SOC_DAPM_INPUT("MICL"),
+ SND_SOC_DAPM_INPUT("MICR"),
+};
+
+static const struct snd_soc_dapm_route rk2928_audio_map[] = {
+ {"DACL Drv", "DACL Amp", "DACL"},
+ {"DACR Drv", "DACR Amp", "DACR"},
+ {"SPKL", NULL, "DACL Drv"},
+ {"SPKR", NULL, "DACR Drv"},
+ {"ADCL", NULL, "MICL"},
+ {"ADCR", NULL, "MICR"},
+};
+
+void codec_set_spk(bool on)
+{
+
+}
+
+static unsigned int rk2928_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ return readl(rk2928_data.regbase + reg);
+}
+
+static int rk2928_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value)
+{
+ DBG("%s reg 0x%02x value 0x%02x", __FUNCTION__, reg, value);
+ writel(value, rk2928_data.regbase + reg);
+ return 0;
+}
+
+static int rk2928_write_mask(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int mask, unsigned int value)
+{
+ unsigned int regvalue = rk2928_read(codec, reg);
+
+ DBG("%s reg 0x%02x mask 0x%02x value 0x%02x", __FUNCTION__, reg, mask, value);
+
+ regvalue &= ~mask;
+ regvalue |= mask & value;
+ return rk2928_write(codec, reg, regvalue);
+}
+
+static int rk2928_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+// struct snd_soc_pcm_runtime *rtd = substream->private_data;
+// struct snd_soc_codec *codec = rtd->codec;
+// struct rk2928_codec_data *priv = snd_soc_codec_get_drvdata(codec);
+
+ DBG("%s", __FUNCTION__);
+
+ return 0;
+}
+
+static int rk2928_audio_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+// struct snd_soc_pcm_runtime *rtd = substream->private_data;
+// struct snd_soc_codec *codec = rtd->codec;
+// struct rk2928_codec_data *priv = snd_soc_codec_get_drvdata(codec);
+ int err = 0;
+
+ DBG("%s cmd 0x%x", __FUNCTION__, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ err = -EINVAL;
+ }
+ return err;
+}
+
+static int rk2928_audio_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+// struct snd_soc_pcm_runtime *rtd = substream->private_data;
+// struct snd_soc_codec *codec = rtd->codec;
+ DBG("%s", __FUNCTION__);
+ return 0;
+}
+
+static int rk2928_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ DBG("%s level %d", __FUNCTION__, level);
+
+ if(codec == NULL)
+ return -1;
+
+ switch(level)
+ {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ rk2928_write_mask(codec, CODEC_REG_POWER, m_PD_MIC_BIAS | m_PD_CODEC, v_PD_MIC_BIAS(0) | v_PD_CODEC(0));
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ case SND_SOC_BIAS_OFF:
+ rk2928_write(codec, CODEC_REG_POWER, v_PWR_OFF);
+ break;
+ default:
+ return -1;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int rk2928_probe(struct snd_soc_codec *codec)
+{
+ struct platform_device *pdev = to_platform_device(codec->dev);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct resource *res, *mem;
+ int ret;
+
+ DBG("%s", __FUNCTION__);
+
+ snd_soc_codec_set_drvdata(codec, &rk2928_data);
+
+ rk2928_data.dev = &pdev->dev;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Unable to get register resource\n");
+ ret = -ENXIO;
+ goto err0;
+ }
+ rk2928_data.regbase_phy = res->start;
+ rk2928_data.regsize_phy = (res->end - res->start) + 1;
+ mem = request_mem_region(res->start, (res->end - res->start) + 1, pdev->name);
+ if (!mem)
+ {
+ dev_err(&pdev->dev, "failed to request mem region for rk2928 codec\n");
+ ret = -ENOENT;
+ goto err0;
+ }
+
+
+ rk2928_data.regbase = (int)ioremap(res->start, (res->end - res->start) + 1);
+ if (!rk2928_data.regbase) {
+ dev_err(&pdev->dev, "cannot ioremap acodec registers\n");
+ ret = -ENXIO;
+ goto err1;
+ }
+
+ rk2928_write(codec, CODEC_REG_DAC_MUTE, v_MUTE_DAC(1));
+ rk2928_write(codec, CODEC_REG_DAC_GAIN, v_GAIN_DAC(DAC_GAIN_3DB_P));
+ rk2928_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_dapm_new_controls(dapm, rk2928_dapm_widgets,
+ ARRAY_SIZE(rk2928_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, rk2928_audio_map, ARRAY_SIZE(rk2928_audio_map));
+
+ return 0;
+
+err1:
+ release_mem_region(res->start,(res->end - res->start) + 1);
+// clk_disable(rk2928_data.hclk);
+err0:
+ DBG("%s failed", __FUNCTION__);
+ return ret;
+}
+
+static int rk2928_remove(struct snd_soc_codec *codec)
+{
+ return 0;
+}
+
+static int rk2928_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ DBG("%s", __FUNCTION__);
+ rk2928_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int rk2928_resume(struct snd_soc_codec *codec)
+{
+ DBG("%s", __FUNCTION__);
+ rk2928_write(codec, CODEC_REG_POWER, v_PD_ADC(1) | v_PD_DAC(1) | v_PD_MIC_BIAS(1));
+ return 0;
+}
+
+static struct snd_soc_codec_driver rk2928_audio_codec_drv = {
+ .probe = rk2928_probe,
+ .remove = rk2928_remove,
+ .suspend = rk2928_suspend,
+ .resume = rk2928_resume,
+ .read = rk2928_read,
+ .write = rk2928_write,
+ .set_bias_level = rk2928_set_bias_level,
+};
+
+static struct snd_soc_dai_ops rk2928_audio_codec_ops = {
+ .hw_params = rk2928_audio_hw_params,
+ .trigger = rk2928_audio_trigger,
+ .startup = rk2928_audio_startup,
+};
+
+static struct snd_soc_dai_driver rk2928_codec_dai = {
+ .name = "rk2928-codec",
+ .playback = {
+ .stream_name = "HIFI Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "HIFI Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE,
+ },
+ .ops = &rk2928_audio_codec_ops,
+};
+
+static __devinit int rk2928_codec_probe(struct platform_device *pdev)
+{
+ int r;
+
+ DBG("%s", __FUNCTION__);
+
+ /* Register ASoC codec DAI */
+ r = snd_soc_register_codec(&pdev->dev, &rk2928_audio_codec_drv,
+ &rk2928_codec_dai, 1);
+ if (r) {
+ dev_err(&pdev->dev, "can't register ASoC rk2928 audio codec\n");
+ return r;
+ }
+
+ DBG("%s success", __FUNCTION__);
+ return 0;
+
+}
+
+static int __devexit rk2928_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver rk2928_codec_driver = {
+ .probe = rk2928_codec_probe,
+ .remove = __devexit_p(rk2928_codec_remove),
+ .driver = {
+ .name = "rk2928-codec",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init rk2928_codec_init(void)
+{
+ return platform_driver_register(&rk2928_codec_driver);
+}
+module_init(rk2928_codec_init);
+
+static void __exit rk2928_codec_exit(void)
+{
+ #ifdef CODEC_I2C_MODE
+ i2c_del_driver(&rk2928_codec_driver);
+ #else
+ platform_driver_unregister(&rk2928_codec_driver);
+ #endif
+}
+module_exit(rk2928_codec_exit);
+
+MODULE_DESCRIPTION("ASoC RK2928 codec driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * rk2928.h ALSA SoC RK2928 codec driver
+ *
+ * Copyright 2012 Rockchip
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __RK2928_CODEC_H__
+#define __RK2928_CODEC_H__
+
+
+#define CODEC_REG_ADC_PGA_GAIN 0x0b
+ #define m_MIC_GAIN_CHANNEL_L (0x0F << 4)
+ #define m_MIC_GAIN_CHANNEL_R (0x0F)
+ #define v_MIC_GAIN_CHANNEL_L(n) ((n) << 4)
+ #define v_MIC_GAIN_CHANNEL_R(n) (n)
+
+#define CODEC_REG_POWER 0x0c
+ #define m_PD_CODEC (0x01)
+ #define m_PD_MIC_BIAS (0x01 << 1)
+ #define m_PD_ADC (0x03 << 2)
+ #define m_PD_DAC (0x03 << 4)
+ #define v_PD_CODEC(n) (n)
+ #define v_PD_MIC_BIAS(n) (n << 1)
+ #define v_PD_ADC_R(n) (n << 2)
+ #define v_PD_ADC_L(n) (n << 3)
+ #define v_PD_DAC_R(n) (n << 4)
+ #define v_PD_DAC_L(n) (n << 5)
+ #define v_PD_ADC(n) (v_PD_ADC_L(n) | v_PD_ADC_R(n))
+ #define v_PD_DAC(n) (v_PD_DAC_L(n) | v_PD_DAC_R(n))
+ #define v_PWR_OFF v_PD_DAC_L(1) | v_PD_DAC_R(1) | v_PD_ADC_L(1) | v_PD_ADC_R(1) | v_PD_MIC_BIAS(1) | v_PD_CODEC(1)
+
+#define CODEC_REG_VCM_BIAS 0x0d
+ #define v_MIC_BIAS(n) (n)
+ enum {
+ VCM_RESISTOR_100K = 0,
+ VCM_RESISTOR_25K
+ };
+ #define v_VCM_25K_100K(n) (n << 2)
+
+#define CODEC_REG_DAC_MUTE 0x0e
+ #define v_MUTE_DAC_L(n) (n << 1)
+ #define v_MUTE_DAC_R(n) (n)
+ #define v_MUTE_DAC(n) v_MUTE_DAC_L(n) | v_MUTE_DAC_R(n)
+
+#define CODEC_REG_ADC_SOURCE 0x0f
+ enum {
+ ADC_SRC_MIC = 0,
+ ADC_SRC_LINE_IN
+ };
+ #define v_SRC_ADC_L(n) (n << 1)
+ #define v_SRC_ADC_R(n) (n)
+
+#define CODEC_REG_DAC_GAIN 0x10
+ #define m_GAIN_DAC_L (0x03 << 2)
+ #define m_GAIN_DAC_R (0x03)
+ enum {
+ DAC_GAIN_0DB = 0,
+ DAC_GAIN_3DB_P = 0x2, //3db
+ DAC_GAIN_3DB_N //-3db
+ };
+ #define v_GAIN_DAC_L(n) (n << 2)
+ #define v_GAIN_DAC_R(n) (n)
+ #define v_GAIN_DAC(n) (v_GAIN_DAC_L(n) | v_GAIN_DAC_R(n))
+
+//#ifndef DEBUG
+//#define DEBUG
+//#endif
+
+#ifdef DEBUG
+#define DBG(format, ...) \
+ printk(KERN_INFO "RK2928 CODEC: " format "\n", ## __VA_ARGS__)
+#else
+#define DBG(format, ...)
+#endif
+
+
+
+#endif /* __RK2928_CODEC_H__ */
--- /dev/null
+/*
+ * rk2928-card.c -- SoC audio for RockChip RK2928
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "rk29_pcm.h"
+#include "rk29_i2s.h"
+
+#ifdef DEBUG
+#define DBG(format, ...) \
+ printk(KERN_INFO "RK2928 Card: " format "\n", ## __VA_ARGS__)
+#else
+#define DBG(format, ...)
+#endif
+
+static int rk2928_dai_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_dai *cpu_dai = rtd->cpu_dai;
+ unsigned int pll_out = 0;
+ int div_bclk,div_mclk;
+ int ret;
+
+ DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
+
+ /* set cpu DAI configuration */
+ #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
+ DBG("Set cpu_dai master\n");
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+ #endif
+ #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ DBG("Set cpu_dai slave\n");
+ #endif
+ if (ret < 0)
+ return ret;
+
+ switch(params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 96000:
+ pll_out = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ pll_out = 11289600;
+ break;
+ case 176400:
+ pll_out = 11289600*2;
+ break;
+ case 192000:
+ pll_out = 12288000*2;
+ break;
+ default:
+ DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+ return -EINVAL;
+ break;
+ }
+ DBG("Enter:%s, %d, rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+
+ #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)
+ snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
+ #endif
+
+ #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
+ div_bclk = 63;
+ div_mclk = pll_out/(params_rate(params)*64) - 1;
+
+ snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
+ snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_BCLK,div_bclk);
+ snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, div_mclk);
+ #endif
+
+ return 0;
+}
+
+static struct snd_soc_ops rk2928_dai_ops = {
+ .hw_params = rk2928_dai_hw_params,
+};
+
+static struct snd_soc_dai_link rk2928_dai[] = {
+ {
+ .name = "RK2928",
+ .stream_name = "RK2928",
+ .cpu_dai_name = "rk29_i2s.0",
+ .platform_name = "rockchip-audio",
+ .codec_name = "rk2928-codec.0-0001",
+ .codec_dai_name = "rk2928-codec",
+ .ops = &rk2928_dai_ops,
+ },
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_rk2928 = {
+ .name = "RK2928",
+ .dai_link = rk2928_dai,
+ .num_links = ARRAY_SIZE(rk2928_dai),
+};
+
+static struct platform_device *rk2928_snd_device;
+
+static int __init rk2928_soc_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "RK2928 SoC init\n");
+
+ rk2928_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!rk2928_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(rk2928_snd_device, &snd_soc_rk2928);
+
+ ret = platform_device_add(rk2928_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(rk2928_snd_device);
+
+ return ret;
+}
+module_init(rk2928_soc_init);
+
+static void __exit rk2928_soc_exit(void)
+{
+ platform_device_unregister(rk2928_snd_device);
+}
+module_exit(rk2928_soc_exit);
+
+MODULE_DESCRIPTION("ALSA SoC RK2928");
+MODULE_LICENSE("GPL");
\ No newline at end of file