sdk-hwV1.3/lichee/linux-4.9/drivers/char/hichs-has/snd_has_play_sound.c

1089 lines
27 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/of_address.h>
#include <sound/dmaengine_pcm.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/mtd/mtd.h>
// #include <linux/delay.h>
#include <linux/dma/sunxi-dma.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/semaphore.h>
/* 引用全志驱动 */
#include "snd_has_sunxi_common.h"
#include "snd_has_sun8iw21_codec.h"
#define DRV_NAME "has-snd-codec"
/* all init code ref to snd_sun8iw21_codec.c */
#define DMA_ALLOC_SIZE 131072 // dma的buffer大小
#define FREQ_IN_OUT 24576000 // clk频率同步全志驱动
/* 采样率16000单声道16bit */
#define SAMPLE_RATE 16000
#define PCM_FORMAT_BITS 16 // SNDRV_PCM_FORMAT_S16_LE
#define PCM_CHANNEL_NUMBER 1
#define SOUND_VOLUME (0x000000A2)// TODO:从806获取音量 0~0xFFdefault 0xA0
#define HAS_WAIT_WAKEUP_SRC_TIMEOUT 500 // ms
#define HAS_WAIT_PLAY_END_TIMEOUT 3000 // ms
#define WAKEUP_SOURCE_NONE 0x00 // 复位就是0
#define WAKEUP_SOURCE_TOUCH 0x01
#define WAKEUP_SOURCE_STREAM 0x02
#define WAKEUP_SOURCE_DOORBELL 0x03
#define WAKEUP_SOURCE_PEEPHOLE 0x04
#define WAKEUP_SOURCE_INVALID 0xFF // 无效值,忽略,表示已经获取过唤醒源
#define DOOLBELL_PARTITION_NAME "doorbell"
// TODO:可以动态分配管理全局变量,放入device数据中
static struct sunxi_has_mem g_mem;
static struct sunxi_has_clk g_clk;
static struct has_pa_config *g_pa_config = NULL;
static struct platform_device *g_pdev = NULL;
static unsigned int g_pa_pin_max = 0;
struct resource g_res;
static unsigned int g_reg_val = 0xA0;
static atomic_t g_wakeup_src = ATOMIC_INIT(0);
// static atomic_t g_wakeup_src;
static DECLARE_COMPLETION(kill_doorbell);
static DECLARE_COMPLETION(relase_done);
static DECLARE_COMPLETION(wakeup_get);
/* WAV文件格式 */
#pragma pack(1)
struct wav_header_t {
char riff[4]; // "RIFF"
uint32_t file_size; // 文件总大小 - 8
char wave[4]; // "WAVE"
char fmt[4]; // "fmt "
uint32_t fmt_size; // fmt块大小16 for PCM
uint16_t audio_format; // 1 for PCM
uint16_t num_channels; // 声道数
uint32_t sample_rate; // 采样率
uint32_t byte_rate; // 每秒字节数sample_rate * block_align
uint16_t block_align; // 每样本字节数bits_per_sample/8 * num_channels
uint16_t bits_per_sample;// 位深
char data[4]; // "data"
uint32_t data_size; // PCM数据大小
};
#pragma pack()
struct sample_rate
{
unsigned int samplerate;
unsigned int rate_bit;
};
static const struct sample_rate sample_rate_conv[] = {
{8000, 5},
{11025, 4},
{12000, 4},
{16000, 3},
{22050, 2},
{24000, 2},
{32000, 1},
{44100, 0},
{48000, 0},
{96000, 7},
{192000, 6},
};
/* soc寄存器参数 */
static struct regmap_config g_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_CODEC_REG_MAX,
.cache_type = REGCACHE_NONE,
};
static struct dma_slave_config slave_config = {
.direction = DMA_MEM_TO_DEV,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
.device_fc = 0,
.dst_addr = 0x02030020, // DMA寄存器地址 ref to datasheet
.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
.slave_id = sunxi_slave_id(DRQDST_AUDIO_CODEC, DRQSRC_SDRAM), // 全志DMA controller ID
.dst_maxburst = 4,
.src_maxburst = 4,
};
void doorbell_control(unsigned char wakeup_src) {
if (wakeup_src == WAKEUP_SOURCE_DOORBELL)
{
atomic_set(&g_wakeup_src, wakeup_src);
}
complete(&wakeup_get);
}
EXPORT_SYMBOL(doorbell_control);
static void snd_sunxi_has_mem_deinit(struct platform_device *pdev)
{
devm_iounmap(&pdev->dev, g_mem.membase);
devm_release_mem_region(&pdev->dev, g_mem.memregion->start, resource_size(g_mem.memregion));
}
static void snd_sunxi_has_clk_deinit(void)
{
clk_disable_unprepare(g_clk.adcclk); // disable + unprepare
clk_disable_unprepare(g_clk.dacclk);
clk_disable_unprepare(g_clk.pllaudio);
clk_put(g_clk.adcclk); // free
clk_put(g_clk.dacclk);
clk_put(g_clk.pllaudio);
}
static void snd_sunxi_has_pa_deinit(struct platform_device *pdev, struct has_pa_config *pa_config)
{
int i;
if (pa_config == NULL)
{
return;
}
for (i = 0; i < g_pa_pin_max; i++) {
if (!pa_config[i].used)
continue;
devm_gpio_free(&pdev->dev, pa_config[i].pin);
}
kfree(pa_config);
}
static int has_free_own_device(struct platform_device *pdev)
{
if (pdev == NULL)
{
pr_emerg("pdev is NULL\n");
return -1;
}
/* set default volume */
regmap_update_bits(g_mem.regmap, SUNXI_DAC_VOL_CTRL, 0x0000FFFF, g_reg_val);
/* clk */
snd_sunxi_has_clk_deinit();
/* memery io unmap */
snd_sunxi_has_mem_deinit(pdev);
/* TODO:regulator, 可以不用去初始化 */
/* pa pin free 不需要关闭pa引脚 */
snd_sunxi_has_pa_deinit(pdev, g_pa_config);
g_pa_config = NULL;
return 0;
}
static void has_dma_complete(void *arg)
{
// enum dma_status status;
// struct dma_tx_state state;
complete(&kill_doorbell);
// status = dmaengine_tx_status(dma_chan, dma_cookie, &state);
// pr_emerg("status:%d residue:%d\n", status, state.residue);
}
static size_t has_decode_WAV_and_getPCM(unsigned char *pcm_data)
{
struct mtd_info *mtd;
size_t retlen = 0;
struct wav_header_t wav_header;
mtd = get_mtd_device_nm(DOOLBELL_PARTITION_NAME); // 名称
if (IS_ERR(mtd))
{
pr_emerg("Failed to get MTD device: %ld\n", PTR_ERR(mtd));
return -1;
}
mtd_read(mtd, 0, sizeof(struct wav_header_t), &retlen, (unsigned char *)&wav_header);
if (retlen != sizeof(struct wav_header_t))
{
pr_emerg("read err wav!");
return -1;
}
if ((memcmp(wav_header.riff, "RIFF", sizeof(wav_header.riff)) != 0) || (memcmp(wav_header.wave, "WAVE", sizeof(wav_header.wave)) != 0)
|| (memcmp(wav_header.fmt, "fmt ", sizeof(wav_header.fmt)) != 0) || ((wav_header.file_size + 8 - sizeof(struct wav_header_t)) != wav_header.data_size)
|| (wav_header.audio_format != 1) || (wav_header.num_channels != PCM_CHANNEL_NUMBER) || (wav_header.sample_rate != SAMPLE_RATE)
|| (wav_header.bits_per_sample != PCM_FORMAT_BITS) || (memcmp(wav_header.data, "data", sizeof(wav_header.data)) != 0))
{
pr_emerg("WAV format error, please check the wav file or fix this driver to adapt different sample data.\n");
pr_emerg("this driver supports bits:%d channel:%d sample rate:%d\n", PCM_FORMAT_BITS, PCM_CHANNEL_NUMBER, SAMPLE_RATE);
return -1;
}
#if 0
// pr_emerg("riff:%s\n", wav_header.riff);
pr_emerg("file size:%d\n", wav_header.file_size);
// pr_emerg("wave:%s\n", wav_header.wave);
// pr_emerg("fmt:%s\n", wav_header.fmt);
pr_emerg("fmt size:%d\n", wav_header.fmt_size);
pr_emerg("audio format:%d\n", wav_header.audio_format);
pr_emerg("channel:%d\n", wav_header.num_channels);
pr_emerg("sample rate:%d\n", wav_header.sample_rate);
pr_emerg("bps:%d\n", wav_header.byte_rate);
pr_emerg("sample bytes:%d\n", wav_header.block_align);
pr_emerg("bits:%d\n", wav_header.bits_per_sample);
// pr_emerg("data:%s\n", wav_header.data);
pr_emerg("pcm size:%d\n", wav_header.data_size);
#endif
if (wav_header.data_size > DMA_ALLOC_SIZE) // 限制128K
{
pr_emerg("file size(%d) out of limit(%d)\n", wav_header.data_size, DMA_ALLOC_SIZE);
return -1;
}
mtd_read(mtd, sizeof(struct wav_header_t), wav_header.data_size, &retlen, (unsigned char *)pcm_data);
if (retlen != wav_header.data_size)
{
pr_emerg("read pcm err\n");
return -1;
}
return (size_t)wav_header.data_size;
}
static int snd_has_pcm_playsound(void *param)
{
int ret;
struct dma_async_tx_descriptor *desc = NULL;
struct dma_chan *dma_chan = NULL;
size_t pcm_len = 0;
dma_cap_mask_t mask;
dma_addr_t dma_addr = 0;
unsigned char *dma_area = NULL;
wait_for_completion_interruptible_timeout(&wakeup_get, msecs_to_jiffies(HAS_WAIT_WAKEUP_SRC_TIMEOUT));
if (atomic_read(&g_wakeup_src) != WAKEUP_SOURCE_DOORBELL)
{
goto skip_doorbell;
}
dma_area = dma_alloc_coherent(NULL, DMA_ALLOC_SIZE, &dma_addr, GFP_KERNEL); // 申请dma
// pr_emerg("dma_area:0x%x dma_addr:0x%x\n", (int)dma_area, (int)dma_addr);
if ((!dma_area) || (!dma_addr))
{
pr_emerg("malloc dma buffer err!\n");
goto err_alloc_init;
}
pcm_len = has_decode_WAV_and_getPCM(dma_area);
if (pcm_len == -1)
{
goto err_read_init;
}
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_CYCLIC, mask); // DMA_LINEAR
dma_chan = dma_request_channel(mask, NULL, NULL);
if (!dma_chan)
{
pr_emerg("dma_request_channel err\n");
goto err_request_init;
}
ret = dmaengine_slave_config(dma_chan, &slave_config);
if (ret < 0)
{
pr_emerg("dma slave config failed, err %d\n", ret);
goto err_config_init;
}
desc = dmaengine_prep_dma_cyclic(dma_chan,
dma_addr,
pcm_len, // buffersize*27680*2
pcm_len, // periodsize*2(960*2)
DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
if (!desc)
{
pr_emerg("dmaengine_prep_dma_cyclic err!\n");
goto err_desc_init;
}
desc->callback = has_dma_complete;
desc->callback_param = NULL;
msleep(10); // 要延时,否则会导致前面一小段音缺失
dmaengine_submit(desc);
// dma_cookie = dmaengine_submit(desc);
dma_async_issue_pending(dma_chan);
// pr_emerg("plad sound start!\n");
wait_for_completion_interruptible_timeout(&kill_doorbell, msecs_to_jiffies(HAS_WAIT_PLAY_END_TIMEOUT));
// dmaengine_synchronize(dma_chan);
dmaengine_terminate_async(dma_chan);
dma_release_channel(dma_chan);
dma_free_coherent(NULL, DMA_ALLOC_SIZE, dma_area, dma_addr); // 如果有申请就删除
has_free_own_device(g_pdev);
g_pdev = NULL;
complete(&relase_done);
// pr_emerg("plad sound end!\n");
return 0;
err_desc_init:
err_config_init:
err_request_init:
dma_release_channel(dma_chan);
err_read_init:
dma_free_coherent(NULL, DMA_ALLOC_SIZE, dma_area, dma_addr);
err_alloc_init:
skip_doorbell:
has_free_own_device(g_pdev);
g_pdev = NULL;
complete(&relase_done);
return -1;
}
static void has_start_dma_codec(void)
{
struct task_struct *snd_thread = NULL;
snd_thread = kthread_create(snd_has_pcm_playsound, NULL, "has_sound");
if (IS_ERR(snd_thread))
{
snd_thread = NULL;
pr_warning("unable to start kernel process_file\n");
}
else
{
wake_up_process(snd_thread);
}
}
static int snd_sunxi_clk_enable(struct platform_device *pdev, struct sunxi_has_clk *clk)
{
int ret = 0;
/* (22579200 or 24576000) * n */
if (clk_set_rate(clk->pllaudio, 22579200))
{
pr_emerg("pllaudio set rate failed\n");
goto err_set_rate;
}
if (clk_prepare_enable(clk->pllaudio))
{
pr_emerg("pllaudio enable failed\n");
goto err_enable_pllaudio;
}
if (clk_prepare_enable(clk->dacclk))
{
pr_emerg("dacclk enable failed\n");
goto err_enable_dacclk;
}
if (clk_prepare_enable(clk->adcclk))
{
pr_emerg("adcclk enable failed\n");
goto err_enable_adcclk;
}
return 0;
err_enable_adcclk:
clk_disable_unprepare(clk->dacclk);
err_enable_dacclk:
clk_disable_unprepare(clk->pllaudio);
err_enable_pllaudio:
err_set_rate:
return ret;
}
/*******************************************************************************
* *** kernel source ***
* @1 regmap
* @2 clk
* @3 regulator
* @4 pa pin
* @5 dts params
******************************************************************************/
static int snd_sunxi_has_clk_init(struct platform_device *pdev, struct sunxi_has_clk *clk)
{
int ret = 0;
struct device_node *np = pdev->dev.of_node;
clk->pllaudio = of_clk_get(np, 0);
if (IS_ERR_OR_NULL(clk->pllaudio))
{
pr_emerg("pllaudio get failed\n");
ret = PTR_ERR(clk->pllaudio);
goto err_pllaudio;
}
clk->dacclk = of_clk_get(np, 1);
if (IS_ERR_OR_NULL(clk->dacclk))
{
pr_emerg("dacclk get failed\n");
ret = PTR_ERR(clk->dacclk);
goto err_dacclk;
}
clk->adcclk = of_clk_get(np, 2);
if (IS_ERR_OR_NULL(clk->adcclk))
{
pr_emerg("adcclk get failed\n");
ret = PTR_ERR(clk->adcclk);
goto err_adcclk;
}
if (clk_set_parent(clk->dacclk, clk->pllaudio))
{
pr_emerg("set parent of dacclk to pllaudio failed\n");
ret = -EINVAL;
goto err_set_parent;
}
if (clk_set_parent(clk->adcclk, clk->pllaudio))
{
pr_emerg("set parent of adcclk to pllaudio failed\n");
ret = -EINVAL;
goto err_set_parent;
}
ret = snd_sunxi_clk_enable(pdev, clk);
if (ret)
{
pr_emerg("clk enable failed\n");
ret = -EINVAL;
goto err_clk_enable;
}
return 0;
err_clk_enable:
err_set_parent:
clk_put(clk->adcclk);
err_adcclk:
clk_put(clk->dacclk);
err_dacclk:
clk_put(clk->pllaudio);
err_pllaudio:
return ret;
}
static int snd_sunxi_has_regulator_init(struct platform_device *pdev, struct sunxi_regulator *rglt)
{
int ret = 0;
unsigned int temp_val;
struct device_node *np = pdev->dev.of_node;
rglt->external_avcc = of_property_read_bool(np, "external-avcc");
ret = of_property_read_u32(np, "avcc-vol", &temp_val);
if (ret < 0)
{
/* default avcc voltage: 1.8v */
rglt->avcc_vol = 1800000;
}
else
{
rglt->avcc_vol = temp_val;
}
if (rglt->external_avcc)
{
rglt->avcc = regulator_get(&pdev->dev, "avcc");
if (IS_ERR_OR_NULL(rglt->avcc))
{
return 0;
}
}
else
{
return 0;
}
ret = regulator_set_voltage(rglt->avcc, rglt->avcc_vol, rglt->avcc_vol);
if (ret < 0)
{
pr_emerg("set avcc voltage failed\n");
ret = -EFAULT;
goto err_regulator_set_vol_avcc;
}
ret = regulator_enable(rglt->avcc);
if (ret < 0)
{
pr_emerg("enable avcc failed\n");
ret = -EFAULT;
goto err_regulator_enable_avcc;
}
return 0;
err_regulator_enable_avcc:
err_regulator_set_vol_avcc:
if (rglt->avcc)
regulator_put(rglt->avcc);
return ret;
}
/* for regmap */
static int snd_has_sunxi_mem_init(struct platform_device *pdev, struct sunxi_has_mem *mem)
{
int ret = 0;
struct device_node *np = pdev->dev.of_node;
ret = of_address_to_resource(np, 0, mem->res);
if (ret)
{
pr_emerg("parse device node resource failed\n");
ret = -EINVAL;
goto err_of_addr_to_resource;
}
mem->memregion = devm_request_mem_region(&pdev->dev, mem->res->start,
resource_size(mem->res), mem->dev_name);
if (IS_ERR_OR_NULL(mem->memregion))
{
pr_emerg("memory region already claimed\n");
ret = -EBUSY;
goto err_devm_request_region;
}
mem->membase = devm_ioremap(&pdev->dev, mem->memregion->start,
resource_size(mem->memregion));
if (IS_ERR_OR_NULL(mem->membase))
{
pr_emerg("ioremap failed\n");
ret = -EBUSY;
goto err_devm_ioremap;
}
mem->regmap = devm_regmap_init_mmio(&pdev->dev, mem->membase, mem->regmap_config);
if (IS_ERR_OR_NULL(mem->regmap))
{
pr_emerg("regmap init failed\n");
ret = -EINVAL;
goto err_devm_regmap_init;
}
return 0;
err_devm_regmap_init:
devm_iounmap(&pdev->dev, mem->membase);
err_devm_ioremap:
devm_release_mem_region(&pdev->dev, mem->memregion->start, resource_size(mem->memregion));
err_devm_request_region:
err_of_addr_to_resource:
return ret;
}
static int snd_has_sunxi_pa_pin(struct has_pa_config *pa_cfg, u32 pa_pin_max, u8 enable)
{
int i;
if (pa_pin_max < 1)
{
return 0;
}
if (enable)
{
msleep(pa_cfg[0].msleep);
}
for (i = 0; i < pa_pin_max; i++)
{
if (!pa_cfg[i].used)
continue;
gpio_direction_output(pa_cfg[i].pin, 1);
if (enable)
gpio_set_value(pa_cfg[i].pin, pa_cfg[i].level);
else
gpio_set_value(pa_cfg[i].pin, !pa_cfg[i].level);
}
return 0;
}
/* for pa config */
static struct has_pa_config *snd_has_sunxi_pa_pin_init(struct platform_device *pdev,
u32 *pa_pin_max)
{
int ret, i;
u32 pin_max;
u32 gpio_tmp;
u32 temp_val;
char str[20] = {0};
struct has_pa_config *pa_cfg;
struct device_node *np = pdev->dev.of_node;
*pa_pin_max = 0;
ret = of_property_read_u32(np, "pa-pin-max", &temp_val);
if (ret < 0)
{
return NULL;
}
else
{
pin_max = temp_val;
}
pa_cfg = kzalloc(sizeof(struct has_pa_config) * pin_max, GFP_KERNEL);
if (!pa_cfg)
{
pr_emerg("can't has_pa_config memory\n");
return NULL;
}
for (i = 0; i < pin_max; i++)
{
sprintf(str, "pa-pin-%d", i);
ret = of_get_named_gpio(np, str, 0);
if (ret < 0)
{
pr_emerg("pa-pin-%u get failed\n", i);
pa_cfg[i].used = 0;
continue;
}
gpio_tmp = ret;
if (!gpio_is_valid(gpio_tmp))
{
pr_emerg("pa-pin-%u (%u) is invalid\n", i, gpio_tmp);
pa_cfg[i].used = 0;
continue;
}
ret = devm_gpio_request(&pdev->dev, gpio_tmp, str);
if (ret)
{
pr_emerg("pa-pin-%u (%u) request failed\n", i, gpio_tmp);
pa_cfg[i].used = 0;
continue;
}
pa_cfg[i].used = 1;
pa_cfg[i].pin = gpio_tmp;
sprintf(str, "pa-pin-level-%d", i);
ret = of_property_read_u32(np, str, &temp_val);
if (ret < 0)
{
pa_cfg[i].level = 0;
}
else
{
if (temp_val > 0)
pa_cfg[i].level = 1;
}
sprintf(str, "pa-pin-msleep-%d", i);
ret = of_property_read_u32(np, str, &temp_val);
if (ret < 0)
{
pa_cfg[i].msleep = 0;
}
else
{
pa_cfg[i].msleep = temp_val;
}
}
*pa_pin_max = pin_max;
snd_has_sunxi_pa_pin(pa_cfg, pin_max, 0);
return pa_cfg;
}
static void snd_sunxi_has_dts_params_init(struct platform_device *pdev, struct sunxi_has_dts *dts)
{
int ret = 0;
unsigned int temp_val;
struct device_node *np = pdev->dev.of_node;
/* lineout volume */
ret = of_property_read_u32(np, "lineout-vol", &temp_val);
if (ret < 0)
{
dts->lineout_vol = 0;
}
else
{
dts->lineout_vol = temp_val;
}
/* mic gain for capturing */
ret = of_property_read_u32(np, "mic1gain", &temp_val);
if (ret < 0)
{
dts->mic1gain = 31;
}
else
{
dts->mic1gain = temp_val;
}
ret = of_property_read_u32(np, "mic2gain", &temp_val);
if (ret < 0)
{
dts->mic2gain = 31;
}
else
{
dts->mic2gain = temp_val;
}
ret = of_property_read_u32(np, "adcdelaytime", &temp_val);
if (ret < 0)
{
dts->adc_dtime = 0;
}
else
{
switch (temp_val)
{
case 0:
case 5:
case 10:
case 20:
case 30:
dts->adc_dtime = temp_val;
break;
default:
pr_emerg("adc delay time supoort only 0,5,10,20,30ms\n");
dts->adc_dtime = 0;
break;
}
}
/* lineout & mic1 & mic2 diff or single */
dts->lineout_single = of_property_read_bool(np, "lineout-single");
dts->mic1_single = of_property_read_bool(np, "mic1-single");
dts->mic2_single = of_property_read_bool(np, "mic2-single");
/* tx_hub */
dts->tx_hub_en = of_property_read_bool(np, "tx-hub-en");
/* components func -> rx_sync */
dts->rx_sync_en = of_property_read_bool(np, "rx-sync-en");
if (dts->rx_sync_en)
{
dts->rx_sync_ctl = false;
}
}
static int has_trigger_codec(void)
{
int i;
// unsigned int reg_val;
struct regmap *regmap = g_mem.regmap;
/* 1 */
// regmap_read(regmap, SUNXI_DAC_DPC, &reg_val); // reg_val:0x80000001
/* 2 */
regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << DAC_HUB_EN, 0x1 << DAC_HUB_EN);
regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << EN_DAC, 0x1 << EN_DAC);
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLMUTE, 0x1 << DACLMUTE);
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLEN, 0x1 << DACLEN);
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x1 << LINEOUTLEN);
/* 3 */
/* capture */
/* 4 */
if (clk_set_rate(g_clk.pllaudio, FREQ_IN_OUT))
{
pr_emerg("pllaudio set rate failed\n");
return -EINVAL;
}
if (clk_set_rate(g_clk.dacclk, FREQ_IN_OUT))
{
pr_emerg("dacclk set rate failed\n");
return -EINVAL;
}
/* 5 */
/* set bits */
#if (PCM_FORMAT_BITS != 16)
#error "change code here to adapt format bits"
#endif
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 3 << FIFO_MODE, 3 << FIFO_MODE);
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << TX_SAMPLE_BITS, 0 << TX_SAMPLE_BITS);
/* set rate */
for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++)
{
if (sample_rate_conv[i].samplerate == SAMPLE_RATE)
{
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 0x7 << DAC_FS, sample_rate_conv[i].rate_bit << DAC_FS);
}
}
#if (SAMPLE_RATE != 16000)
#error "change code here to adapt sample rate"
#endif
/* reset the adchpf func setting for different sampling. case 16000:*/
regmap_write(regmap, SUNXI_ADC_DRC_HHPFC, (0x00F623A5 >> 16) & 0xFFFF);
regmap_write(regmap, SUNXI_ADC_DRC_LHPFC, 0x00F623A5 & 0xFFFF);
/* set channels. case 1: one channel, DACL & DACR send same data */
#if (PCM_CHANNEL_NUMBER != 1)
#error "change code here to adapt channel"
#endif
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_MONO_EN, 0x1 << DAC_MONO_EN);
/* 6 */
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << FIFO_FLUSH, 1 << FIFO_FLUSH);
regmap_write(regmap, SUNXI_DAC_FIFOS, 1 << DAC_TXE_INT | 1 << DAC_TXU_INT | 1 << DAC_TXO_INT);
regmap_write(regmap, SUNXI_DAC_CNT, 0);
/* 7 */
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLEN, 0x1 << DACLEN);
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLMUTE, 0x1 << DACLMUTE);
regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << EN_DAC, 0x1 << EN_DAC);
/* 8 */
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x1 << LINEOUTLEN);
/* 9 */
snd_has_sunxi_pa_pin(g_pa_config, g_pa_pin_max, 1);
/* 10 */
regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << DAC_DRQ_EN, 1 << DAC_DRQ_EN);
return 0;
}
static void sunxi_has_codec_init(struct sunxi_has_dts *dts)
{
unsigned int adc_dtime_map;
struct regmap *regmap = g_mem.regmap;
regmap_update_bits(regmap, SUNXI_POWER_REG, 0x7 << ALDO_OUTPUT_VOLTAGE, 3 << ALDO_OUTPUT_VOLTAGE);
regmap_update_bits(regmap, SUNXI_POWER_REG, 0x1 << ALDO_EN, 0x1 << ALDO_EN);
regmap_update_bits(regmap, SUNXI_RAMP_REG, 0x1 << RMC_EN, 0x1 << RMC_EN);
regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << ADCDFEN, 0x1 << ADCDFEN);
switch (dts->adc_dtime)
{
case 5:
adc_dtime_map = 0;
break;
case 10:
adc_dtime_map = 1;
break;
case 20:
adc_dtime_map = 2;
break;
case 30:
adc_dtime_map = 3;
break;
case 0:
default:
regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << ADCDFEN, 0x0 << ADCDFEN);
break;
}
regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x3 << ADCFDT, adc_dtime_map << ADCFDT);
regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << RX_SYNC_EN, 0x0 << RX_SYNC_EN);
/* Digital VOL defeult setting */
regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x3F << DVOL, 0 << DVOL);
/* LINEOUT VOL defeult setting */
regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1F << LINEOUT_VOL,
dts->lineout_vol << LINEOUT_VOL);
/* ADCL MIC1 gain defeult setting */
regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, dts->mic1gain << ADC1_PGA_GAIN_CTRL);
/* ADCR MIC2 gain defeult setting */
regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, dts->mic2gain << ADC2_PGA_GAIN_CTRL);
/* ADC IOP params default setting */
regmap_update_bits(regmap, SUNXI_ADC1_REG, 0xFF << ADC1_IOPMIC, 0x55 << ADC1_IOPMIC);
regmap_update_bits(regmap, SUNXI_ADC2_REG, 0xFF << ADC2_IOPMIC, 0x55 << ADC2_IOPMIC);
/* lineout & mic1 & mic2 diff or single setting */
if (dts->lineout_single)
regmap_update_bits(regmap, SUNXI_DAC_REG,
0x1 << LINEOUTLDIFFEN, 0x0 << LINEOUTLDIFFEN);
else
regmap_update_bits(regmap, SUNXI_DAC_REG,
0x1 << LINEOUTLDIFFEN, 0x1 << LINEOUTLDIFFEN);
if (dts->mic1_single)
regmap_update_bits(regmap, SUNXI_ADC1_REG,
0x1 << MIC1_SIN_EN, 0x1 << MIC1_SIN_EN);
else
regmap_update_bits(regmap, SUNXI_ADC1_REG,
0x1 << MIC1_SIN_EN, 0x0 << MIC1_SIN_EN);
if (dts->mic2_single)
regmap_update_bits(regmap, SUNXI_ADC2_REG,
0x1 << MIC2_SIN_EN, 0x1 << MIC2_SIN_EN);
else
regmap_update_bits(regmap, SUNXI_ADC2_REG,
0x1 << MIC2_SIN_EN, 0x0 << MIC2_SIN_EN);
regmap_update_bits(regmap, SUNXI_DAC_VOL_CTRL, 1 << DAC_VOL_SEL, 1 << DAC_VOL_SEL);
regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 1 << ADC1_2_VOL_EN, 1 << ADC1_2_VOL_EN);
regmap_read(regmap, SUNXI_DAC_VOL_CTRL, &g_reg_val);
// reg_R_val = (g_reg_val & 0xFF);
// reg_L_val = ((g_reg_val >> 8) & 0xFF);
// pr_emerg("reg_val:0x%08x, L:0x%02x R:0x%02x\n", reg_val, reg_L_val, reg_R_val);
// regmap_update_bits(regmap, SUNXI_DAC_VOL_CTRL, 0x000000FF, SOUND_VOLUME); // R
regmap_update_bits(regmap, SUNXI_DAC_VOL_CTRL, 0x0000FFFF, (SOUND_VOLUME << 8 | SOUND_VOLUME)); // L
}
static int doolbell_drv_open(struct inode *node, struct file *file)
{
// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int doolbell_drv_close(struct inode *node, struct file *file)
{
// printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
#define DOOLBELL_STOP _IOWR('D', 100, unsigned int)
#define DOOLBELL_GET_STATUS _IOR('D', 101, unsigned int)
#define DOOLBELL_PLAYING 0
#define DOOLBELL_DONE 1
static long doolbell_drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int value;
switch (cmd) {
case DOOLBELL_STOP: {
complete(&kill_doorbell);
complete(&wakeup_get);
wait_for_completion_interruptible(&relase_done);
if (g_pdev != NULL)
{
return -EINVAL;
}
break;
}
case DOOLBELL_GET_STATUS: {
/* 用户态应该轮询查询状态轮询不用考虑并发读取g_pdev */
if (g_pdev != NULL) // 启动门铃流程未结束
{
value = DOOLBELL_PLAYING;
}
else // 启动门铃流程已经结束
{
value = DOOLBELL_DONE;
}
if (copy_to_user((char __user *)arg, &value, sizeof(value)))
{
return -EINVAL;
}
break;
}
default:
return -EINVAL;
break;
}
return 0;
}
static struct file_operations doorbell_drv = {
.owner = THIS_MODULE,
.open = doolbell_drv_open,
.release = doolbell_drv_close,
.unlocked_ioctl = doolbell_drv_ioctl,
};
static int has_play_sound_dev_probe(struct platform_device *pdev)
{
struct sunxi_regulator rglt;
struct device_node *np;
unsigned int temp_val = 0;
struct sunxi_has_dts temp_dts;
int major = 0;
struct class *doorbell_class;
g_pdev = pdev;
g_mem.dev_name = DRV_NAME;
g_mem.res = &g_res;
g_mem.regmap_config = &g_regmap_config;
// atomic_set(&g_wakeup_src, WAKEUP_SOURCE_NONE);
np = of_find_compatible_node(NULL, NULL, "allwinner,sunxi-snd-plat-aaudio");
if (np) {
if (of_property_read_u32(np, "dac-txdata", &temp_val) < 0) {
pr_emerg("get dac-txdata err!\n");
}
else
{
slave_config.dst_addr = temp_val;
}
}
/* 寄存器地址 */
if (snd_has_sunxi_mem_init(pdev, &g_mem))
{
goto err_mem_init;
}
/* 时钟初始化 */
if (snd_sunxi_has_clk_init(pdev, &g_clk))
{
goto err_clk_init;
}
/* 低压差线性稳压器 */
if (snd_sunxi_has_regulator_init(pdev, &rglt))
{
goto err_regulator_init;
}
/* pa引脚 */
g_pa_config = snd_has_sunxi_pa_pin_init(pdev, &g_pa_pin_max);
if (g_pa_config == NULL)
{
goto err_pa_init;
}
snd_sunxi_has_dts_params_init(pdev, &temp_dts);
/* codec 初始化 */
sunxi_has_codec_init(&temp_dts);
has_trigger_codec();
major = register_chrdev(0, "doorbell", &doorbell_drv);
doorbell_class = class_create(THIS_MODULE, "doorbell_class");
if (IS_ERR(doorbell_class)) {
unregister_chrdev(major, "doorbell");
goto err_pa_init;
}
device_create(doorbell_class, NULL, MKDEV(major, 0), NULL, "doorbell");
has_start_dma_codec();
return 0;
err_pa_init:
err_regulator_init:
err_clk_init:
snd_sunxi_has_clk_deinit();
err_mem_init:
snd_sunxi_has_mem_deinit(pdev);
g_pdev = NULL;
return -EINVAL;
}
static int has_play_sound_dev_remove(struct platform_device *pdev)
{
// has_free_own_device(pdev);
// device_destroy(doorbell_class, MKDEV(major, 0));
// class_destroy(doorbell_class);
// unregister_chrdev(major, "doorbell");
return 0;
}
// 使用全志codec寄存器
static const struct of_device_id has_play_sound_of_match[] = {
{
.compatible = "has," DRV_NAME,
},
{},
};
MODULE_DEVICE_TABLE(of, has_play_sound_of_match);
static struct platform_driver has_play_sond_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = has_play_sound_of_match,
},
.probe = has_play_sound_dev_probe,
.remove = has_play_sound_dev_remove,
};
module_platform_driver(has_play_sond_driver);
MODULE_AUTHOR("@hichs.com.cn");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("play sound during start up");