ak4396: add ak4396 dac support

This commit is contained in:
邱建斌
2013-08-20 11:26:22 +08:00
parent 95d60a3b12
commit 4beee34fab
6 changed files with 668 additions and 0 deletions

View File

@@ -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

View File

@@ -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
View 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");

View File

@@ -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

View File

@@ -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
View 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");