mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 04:10:18 +09:00
ak4396: add ak4396 dac support
This commit is contained in:
@@ -80,6 +80,7 @@ config SND_SOC_ALL_CODECS
|
||||
select SND_SOC_WM8900 if I2C
|
||||
select SND_SOC_RT5621 if I2C
|
||||
select SND_SOC_RT5631 if I2C
|
||||
select SND_SOC_AK4396 if SPI_MASTER
|
||||
select SND_SOC_RT5631_PHONE if I2C
|
||||
select SND_SOC_RT5625 if I2C
|
||||
select SND_SOC_RT5640 if I2C
|
||||
@@ -347,6 +348,9 @@ config SND_SOC_RT5639
|
||||
|
||||
config SND_SOC_RT5616
|
||||
tristate
|
||||
|
||||
config SND_SOC_AK4396
|
||||
tristate
|
||||
|
||||
config SND_SOC_RT5631
|
||||
tristate
|
||||
|
||||
@@ -65,6 +65,7 @@ snd-soc-wm8900-objs := wm8900.o
|
||||
snd-soc-rt5621-objs := rt5621.o
|
||||
snd-soc-rt5623-objs := rt5623.o
|
||||
snd-soc-rt5631-objs := rt5631.o
|
||||
snd-soc-ak4396-objs := ak4396.o
|
||||
snd-soc-rt5616-objs := rt5616.o
|
||||
snd-soc-rt5631-phone-objs := rt5631_phone.o
|
||||
snd-soc-rt5625-objs := rt5625.o
|
||||
@@ -180,6 +181,7 @@ obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o
|
||||
obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o
|
||||
obj-$(CONFIG_SND_SOC_RT5621) += snd-soc-rt5621.o
|
||||
obj-$(CONFIG_SND_SOC_RT5623) += snd-soc-rt5623.o
|
||||
obj-$(CONFIG_SND_SOC_AK4396) += snd-soc-ak4396.o
|
||||
obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o
|
||||
obj-$(CONFIG_SND_SOC_RT5616) += snd-soc-rt5616.o
|
||||
obj-$(CONFIG_SND_SOC_RT5631_PHONE) += snd-soc-rt5631-phone.o
|
||||
|
||||
473
sound/soc/codecs/ak4396.c
Executable file
473
sound/soc/codecs/ak4396.c
Executable file
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
* AK4396 ALSA SoC (ASoC) driver
|
||||
*
|
||||
* Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/initval.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <sound/asoundef.h>
|
||||
#include <linux/delay.h>
|
||||
#include <mach/iomux.h>
|
||||
|
||||
/* AK4396 registers addresses */
|
||||
#define AK4396_REG_CONTROL1 0x00
|
||||
#define AK4396_REG_CONTROL2 0x01
|
||||
#define AK4396_REG_CONTROL3 0x02
|
||||
#define AK4396_REG_LCH_ATT 0x03
|
||||
#define AK4396_REG_RCH_ATT 0x04
|
||||
#define AK4396_NUM_REGS 5
|
||||
|
||||
#define AK4396_REG_MASK 0x1f
|
||||
#define AK4396_WRITE 0x20 /*C1 C0 R/W A4 A3 A2 A1 A0 8bit==0010 0000 */
|
||||
|
||||
/* Bit masks for AK4396 registers */
|
||||
#define AK4396_CONTROL1_RSTN (1 << 0)
|
||||
#define AK4396_CONTROL1_DIF0 (1 << 1)
|
||||
#define AK4396_CONTROL1_DIF1 (1 << 2)
|
||||
#define AK4396_CONTROL1_DIF2 (1 << 3)
|
||||
|
||||
#define DRV_NAME "AK4396"
|
||||
|
||||
struct ak4396_private {
|
||||
enum snd_soc_control_type control_type;
|
||||
void *control_data;
|
||||
unsigned int sysclk;
|
||||
};
|
||||
|
||||
#if 0
|
||||
|
||||
static const u16 ak4396_reg[AK4396_NUM_REGS] = {
|
||||
0x87, 0x02, 0x00, 0xff, 0xff
|
||||
}; //CONFIG_LINF
|
||||
#else
|
||||
static const u16 ak4396_reg[AK4396_NUM_REGS] = {
|
||||
0x05, 0x02, 0x00, 0xff, 0xff
|
||||
}; //CONFIG_LINF
|
||||
|
||||
#endif
|
||||
|
||||
static void on_off_ext_amp(int i)
|
||||
{
|
||||
|
||||
#ifdef SPK_CTL
|
||||
//gpio_direction_output(SPK_CTL, GPIO_LOW);
|
||||
gpio_set_value(SPK_CTL, i);
|
||||
printk("*** %s() SPEAKER set as %d\n", __FUNCTION__, i);
|
||||
#endif
|
||||
#ifdef EAR_CON_PIN
|
||||
//gpio_direction_output(EAR_CON_PIN, GPIO_LOW);
|
||||
gpio_set_value(EAR_CON_PIN, i);
|
||||
printk("*** %s() HEADPHONE set as %d\n", __FUNCTION__, i);
|
||||
mdelay(50);
|
||||
#endif
|
||||
}
|
||||
|
||||
void codec_set_spk(bool on)
|
||||
{
|
||||
on_off_ext_amp(on);
|
||||
}
|
||||
|
||||
static int ak4396_fill_cache(struct snd_soc_codec *codec)
|
||||
{
|
||||
int i;
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
struct spi_device *spi = codec->control_data;
|
||||
|
||||
for (i = 0; i < codec->driver->reg_cache_size; i++) {
|
||||
int ret = spi_w8r8(spi, i);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "SPI write failure\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg_cache[i] = ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read the reg_cache */
|
||||
static unsigned int ak4396_read_reg_cache(struct snd_soc_codec *codec,
|
||||
unsigned int reg)
|
||||
{
|
||||
u8 *reg_cache = codec->reg_cache;
|
||||
|
||||
if (reg >= codec->driver->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
// printk("read reg_cache[%x]====%d\n", reg, reg_cache[reg]);
|
||||
return reg_cache[reg];
|
||||
}
|
||||
|
||||
static int ak4396_spi_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int value)
|
||||
{
|
||||
u8 *cache = codec->reg_cache;
|
||||
struct spi_device *spi = codec->control_data;
|
||||
|
||||
if (reg >= codec->driver->reg_cache_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* only write to the hardware if value has changed */
|
||||
//if (cache[reg] != value)
|
||||
//{
|
||||
u8 tmp[2] = { (reg & AK4396_REG_MASK) | AK4396_WRITE, value};
|
||||
//printk("tmp[0]===%d\n", tmp[0]);
|
||||
//printk("tmp[1]===%d\n", tmp[1]);
|
||||
if (spi_write(spi, tmp, sizeof(tmp))) {
|
||||
dev_err(&spi->dev, "SPI write failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
cache[reg] = value;
|
||||
//}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* write the register space */
|
||||
static ak4396_write(struct snd_soc_codec *codec)
|
||||
{
|
||||
int ret, val, i;
|
||||
val = 0;
|
||||
int addr[5] = {0x00, 0x01, 0x02, 0x03, 0x04};
|
||||
int dat[5] = {0x87, 0x02, 0x00, 0xff, 0xff};
|
||||
// while(1){
|
||||
val |= AK4396_CONTROL1_RSTN;
|
||||
ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
|
||||
|
||||
for(i=0; i<5; i++)
|
||||
{
|
||||
ret = ak4396_spi_write(codec, addr[i], dat[i]);
|
||||
if (ret < 0)
|
||||
printk("ak4396_spi_write failed!\n");
|
||||
|
||||
printk("write %d time(s)\n", i);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that this should be called from init rather than from hw_params.
|
||||
*/
|
||||
static int ak4396_set_dai_sysclk(struct snd_soc_dai *codec_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
struct ak4396_private *ak4396 = snd_soc_codec_get_drvdata(codec);
|
||||
|
||||
printk("Enter::%s----%d\n",__FUNCTION__,__LINE__);
|
||||
printk("freq======%d\n", freq);
|
||||
ak4396->sysclk = freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ak4396_set_dai_fmt(struct snd_soc_dai *codec_dai,
|
||||
unsigned int format)
|
||||
{
|
||||
struct snd_soc_codec *codec = codec_dai->codec;
|
||||
int val = 0;
|
||||
|
||||
printk("%s----%d, format[%02x]\n",__FUNCTION__,__LINE__,format);
|
||||
val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
|
||||
if (val < 0)
|
||||
return val;
|
||||
val &= ~(AK4396_CONTROL1_DIF0 | AK4396_CONTROL1_DIF1 | AK4396_CONTROL1_DIF2);
|
||||
// printk("ak4396 val=%d\n", val);
|
||||
|
||||
/* set DAI format */
|
||||
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
val |= AK4396_CONTROL1_DIF2 ;
|
||||
printk("SND_SOC_DAIFMT_RIGHT_J: \n");
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
val |= AK4396_CONTROL1_DIF1 ;
|
||||
printk("SND_SOC_DAIFMT_LEFT_J: \n");
|
||||
break;
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
val |= AK4396_CONTROL1_DIF0 | AK4396_CONTROL1_DIF1 ;
|
||||
//val |= 0x87;
|
||||
|
||||
printk("SND_SOC_DAIFMT_I2S is ok!\n");
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "invalid dai format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* This device can only be slave */
|
||||
if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
|
||||
{
|
||||
printk("%s failed!----%d\n",__FUNCTION__,__LINE__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
//val |= AK4396_CONTROL1_RSTN;
|
||||
printk("AK4396 CONTROL1 val ==== %d\n", val);
|
||||
ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ak4396_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;
|
||||
int val = 0;
|
||||
|
||||
switch (params_rate(params)) {
|
||||
|
||||
case 176400:
|
||||
val |= IEC958_AES3_CON_FS_176400;
|
||||
printk("params_rate::=176400!\n");
|
||||
break;
|
||||
|
||||
case 192000:
|
||||
val |= IEC958_AES3_CON_FS_192000;
|
||||
printk("params_rate::=192000!\n");
|
||||
break;
|
||||
|
||||
case 88200:
|
||||
val |= IEC958_AES3_CON_FS_88200;
|
||||
printk("params_rate::=88200!\n");
|
||||
break;
|
||||
|
||||
|
||||
case 96000:
|
||||
val |= IEC958_AES3_CON_FS_96000;
|
||||
printk("params_rate::=96000!\n");
|
||||
break;
|
||||
|
||||
case 44100:
|
||||
val |= IEC958_AES3_CON_FS_44100;
|
||||
printk("params_rate::=44100!\n");
|
||||
break;
|
||||
case 48000:
|
||||
val |= IEC958_AES3_CON_FS_48000;
|
||||
break;
|
||||
case 32000:
|
||||
val |= IEC958_AES3_CON_FS_32000;
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "unsupported sampling rate\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
val = 0;
|
||||
val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
|
||||
//reset RSTN bit;
|
||||
val &= 0xFE;
|
||||
printk("val ==== %d\n", val);
|
||||
ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
|
||||
val |= 0x01;
|
||||
printk("val ==== %d\n", val);
|
||||
ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
|
||||
|
||||
//printk("val === %d\n", val);
|
||||
//ak4396_spi_write(codec, AK4396_REG_CONTROL2, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops ak4396_dai_ops = {
|
||||
.hw_params = ak4396_hw_params,
|
||||
.set_fmt = ak4396_set_dai_fmt,
|
||||
.set_sysclk = ak4396_set_dai_sysclk,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver ak4396_dai = {
|
||||
.name = "AK4396 HiFi",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE
|
||||
},
|
||||
.ops = &ak4396_dai_ops,
|
||||
};
|
||||
|
||||
struct snd_soc_codec *codec_temp;
|
||||
static int ak4396_probe(struct snd_soc_codec *codec)
|
||||
{
|
||||
struct ak4396_private *ak4396 = snd_soc_codec_get_drvdata(codec);
|
||||
int ret;
|
||||
printk("ak4396_probe begin!\n");
|
||||
codec->control_data = ak4396->control_data;
|
||||
codec_temp = codec;
|
||||
#if 0
|
||||
/* read all regs and fill the cache */
|
||||
ret = ak4396_fill_cache(codec);
|
||||
if (ret < 0) {
|
||||
dev_err(codec->dev, "failed to fill register cache\n");
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
/* write to ak4396_reg */
|
||||
// ak4396_write(codec);
|
||||
|
||||
printk("ak4396_probe is ok!\n");
|
||||
dev_info(codec->dev, "SPI device initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ak4396_remove(struct snd_soc_codec *codec)
|
||||
{
|
||||
int val, ret;
|
||||
|
||||
val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
/* set non-reset bits */
|
||||
val &= ~AK4396_CONTROL1_RSTN;
|
||||
ret = ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ak4396_suspend(struct snd_soc_codec *codec, pm_message_t state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ak4396_resume(struct snd_soc_codec *codec)
|
||||
{
|
||||
//ak4396_write(codec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_codec_driver soc_codec_device_ak4396 = {
|
||||
.probe = ak4396_probe,
|
||||
.remove = ak4396_remove,
|
||||
.suspend = ak4396_suspend,
|
||||
.resume = ak4396_resume,
|
||||
.reg_cache_size = AK4396_NUM_REGS,
|
||||
.reg_word_size = sizeof(u16),
|
||||
.reg_cache_default = ak4396_reg,
|
||||
};
|
||||
|
||||
static struct class *cls = NULL;
|
||||
|
||||
static ssize_t store_ak4396_reg(struct class *dev,
|
||||
struct class_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int reg, value, ret;
|
||||
// char buf[10] = "123 11";
|
||||
char *start = buf;
|
||||
|
||||
printk("%s, the first dat is reg, the second dat is data, data type is dex\n", __FUNCTION__);
|
||||
while (*start == ' ')
|
||||
start++;
|
||||
reg = simple_strtoull(start, &start, 16);
|
||||
|
||||
while (*start == ' ')
|
||||
start++;
|
||||
value = simple_strtoull(start, &start, 16);
|
||||
|
||||
|
||||
ret = ak4396_spi_write(codec_temp, reg, value);
|
||||
if (ret < 0)
|
||||
printk("ak4396_spi_write failed!\n");
|
||||
|
||||
printk("reg = %d, value =%d\n", reg, value);
|
||||
// return 0;
|
||||
}
|
||||
static struct class_attribute attr[] = {
|
||||
__ATTR(write_reg, 0644, NULL, store_ak4396_reg),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
static int ak4396_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct ak4396_private *ak4396;
|
||||
int ret;
|
||||
printk("ak4396_spi_probe begin!\n");
|
||||
|
||||
#if 0 //defined(CONFIG_ARCH_RK3188)
|
||||
iomux_set(SPI1_CS0);
|
||||
iomux_set(SPI1_CLK);
|
||||
iomux_set(SPI1_TXD);
|
||||
printk("iomux_set is OK!!!\n");
|
||||
#endif
|
||||
|
||||
spi->bits_per_word = 8;
|
||||
spi->mode = SPI_MODE_0;
|
||||
ret = spi_setup(spi);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ak4396 = kzalloc(sizeof(struct ak4396_private), GFP_KERNEL);
|
||||
if (ak4396 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
ak4396->control_data = spi;
|
||||
ak4396->control_type = SND_SOC_SPI;
|
||||
spi_set_drvdata(spi, ak4396);
|
||||
|
||||
cls = class_create(THIS_MODULE, DRV_NAME);
|
||||
if (IS_ERR(cls))
|
||||
{
|
||||
printk("class_create failed!\n");
|
||||
}
|
||||
ret = class_create_file(cls, attr);
|
||||
if (ret < 0)
|
||||
{
|
||||
printk("class_create_file failed!\n");
|
||||
}
|
||||
|
||||
ret = snd_soc_register_codec(&spi->dev,
|
||||
&soc_codec_device_ak4396, &ak4396_dai, 1);
|
||||
if (ret < 0)
|
||||
kfree(ak4396);
|
||||
|
||||
printk("ak4396_spi_probe successful!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit ak4396_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
snd_soc_unregister_codec(&spi->dev);
|
||||
kfree(spi_get_drvdata(spi));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver ak4396_spi_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ak4396_spi_probe,
|
||||
.remove = __devexit_p(ak4396_spi_remove),
|
||||
};
|
||||
|
||||
static int __init ak4396_init(void)
|
||||
{
|
||||
printk("%s\n", __FUNCTION__);
|
||||
return spi_register_driver(&ak4396_spi_driver);
|
||||
}
|
||||
module_init(ak4396_init);
|
||||
|
||||
static void __exit ak4396_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&ak4396_spi_driver);
|
||||
}
|
||||
module_exit(ak4396_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
|
||||
MODULE_DESCRIPTION("Asahi Kasei AK4396 ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -87,6 +87,15 @@ choice
|
||||
endchoice
|
||||
endif
|
||||
|
||||
config SND_RK29_SOC_AK4396
|
||||
tristate "SoC I2S Audio support for rockchip - AK4396"
|
||||
depends on SND_RK29_SOC
|
||||
select SND_RK29_SOC_I2S
|
||||
select SND_SOC_AK4396
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on rockchip
|
||||
with the AK4396.
|
||||
|
||||
config SND_RK29_SOC_ES8323
|
||||
tristate "SoC I2S Audio support for rockchip - ES8323"
|
||||
depends on SND_RK29_SOC
|
||||
@@ -136,6 +145,7 @@ config SND_RK29_SOC_RT5623
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on rockchip
|
||||
with the rt5623.
|
||||
|
||||
config SND_RK29_SOC_RT5631
|
||||
tristate "SoC I2S Audio support for rockchip - RT5631"
|
||||
depends on SND_RK29_SOC
|
||||
|
||||
@@ -15,6 +15,7 @@ obj-$(CONFIG_SND_RK_SOC_SPDIF) += snd-soc-rockchip-spdif.o
|
||||
snd-soc-wm8900-objs := rk29_wm8900.o
|
||||
snd-soc-rt5621-objs := rk29_rt5621.o
|
||||
snd-soc-rt5631-objs := rk29_rt5631.o
|
||||
snd-soc-ak4396-objs := rk29_ak4396.o
|
||||
snd-soc-rt5616-objs := rk29_rt5616.o
|
||||
snd-soc-rt5631-phone-objs := rk29_rt5631_phone.o
|
||||
snd-soc-rt5625-objs := rk29_rt5625.o
|
||||
@@ -41,6 +42,7 @@ obj-$(CONFIG_SND_RK29_SOC_WM8988) += snd-soc-wm8988.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_WM8900) += snd-soc-wm8900.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_RT5621) += snd-soc-rt5621.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_RT5631) += snd-soc-rt5631.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_AK4396) += snd-soc-ak4396.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_RT5631_PHONE) += snd-soc-rt5631-phone.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_RT5625) += snd-soc-rt5625.o
|
||||
obj-$(CONFIG_SND_RK29_SOC_RT5640) += snd-soc-rt5640.o
|
||||
|
||||
177
sound/soc/rk29/rk29_ak4396.c
Executable file
177
sound/soc/rk29/rk29_ak4396.c
Executable file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* rk29_ak4396.c -- SoC audio for rockchip
|
||||
*
|
||||
* Driver for rockchip ak4396 audio
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <asm/io.h>
|
||||
#include <mach/hardware.h>
|
||||
#include "rk29_pcm.h"
|
||||
#include "rk29_i2s.h"
|
||||
|
||||
#include <mach/gpio.h>
|
||||
|
||||
#if 1
|
||||
#define DBG(x...) printk(KERN_INFO x)
|
||||
#else
|
||||
#define DBG(x...)
|
||||
#endif
|
||||
|
||||
static int rk29_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 *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
unsigned int pll_out = 0;
|
||||
int ret=-1;
|
||||
|
||||
DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
|
||||
|
||||
/* set codec DAI configuration */
|
||||
#if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_RIGHT_J |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret < 0) return ret;
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_RIGHT_J |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret < 0) return ret;
|
||||
#endif
|
||||
|
||||
switch(params_rate(params)) {
|
||||
case 8000:
|
||||
case 16000:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 48000:
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 88200:
|
||||
case 176400:
|
||||
pll_out = 11289600*2;
|
||||
break;
|
||||
case 96000:
|
||||
case 192000:
|
||||
pll_out = 12288000*2;
|
||||
break;
|
||||
default:
|
||||
DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DBG("Enter:%s, %d, rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
|
||||
|
||||
#if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
|
||||
snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
|
||||
snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_BCLK, (2 * 32 )-1); //bclk = 2 * 32 * lrck
|
||||
|
||||
switch(params_rate(params)){
|
||||
case 192000:
|
||||
case 176400:
|
||||
snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK,1);
|
||||
DBG("Enter:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n",
|
||||
__FUNCTION__,__LINE__,pll_out,pll_out/2,params_rate(params));
|
||||
break;
|
||||
default :
|
||||
snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, 3);
|
||||
DBG("default:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n",
|
||||
__FUNCTION__,__LINE__,pll_out,pll_out/4,params_rate(params));
|
||||
break;
|
||||
}
|
||||
snd_soc_dai_set_sysclk(codec_dai,0,pll_out,SND_SOC_CLOCK_IN);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logic for a ak4396 as connected on a rockchip board.
|
||||
*/
|
||||
static int rk29_ak4396_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops rk29_ops = {
|
||||
.hw_params = rk29_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link rk29_dai = {
|
||||
.name = "AK4396",
|
||||
.stream_name = "AK4396 PCM",
|
||||
.codec_name = "spi1.0",
|
||||
.platform_name = "rockchip-audio",
|
||||
#if defined(CONFIG_SND_RK29_SOC_I2S_8CH)
|
||||
.cpu_dai_name = "rk29_i2s.0",
|
||||
#elif defined(CONFIG_SND_RK29_SOC_I2S_2CH)
|
||||
.cpu_dai_name = "rk29_i2s.1",
|
||||
#else
|
||||
.cpu_dai_name = "rk29_i2s.2",
|
||||
#endif
|
||||
.codec_dai_name = "AK4396 HiFi",
|
||||
.init = rk29_ak4396_init,
|
||||
.ops = &rk29_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_card_rk29 = {
|
||||
.name = "RK29_AK4396",
|
||||
.dai_link = &rk29_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static struct platform_device *rk29_snd_device;
|
||||
|
||||
static int __init audio_card_init(void)
|
||||
{
|
||||
int ret =0;
|
||||
|
||||
rk29_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!rk29_snd_device) {
|
||||
printk("platform device allocation failed\n");
|
||||
ret = -ENOMEM;
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(rk29_snd_device, &snd_soc_card_rk29);
|
||||
|
||||
ret = platform_device_add(rk29_snd_device);
|
||||
if (ret) {
|
||||
printk("platform device add failed\n");
|
||||
platform_device_put(rk29_snd_device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit audio_card_exit(void)
|
||||
{
|
||||
platform_device_unregister(rk29_snd_device);;
|
||||
}
|
||||
|
||||
module_init(audio_card_init);
|
||||
module_exit(audio_card_exit);
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("rockchip");
|
||||
MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user