1045 lines
26 KiB
C
Executable File
1045 lines
26 KiB
C
Executable File
#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
|
||
|
||
struct sunxi_has_mem g_mem;
|
||
struct sunxi_has_clk g_clk;
|
||
struct has_pa_config *g_pa_config = NULL;
|
||
struct platform_device *g_pdev = NULL;
|
||
unsigned int g_pa_pin_max = 0;
|
||
|
||
struct resource g_res;
|
||
|
||
static DECLARE_COMPLETION(doorbell_end);
|
||
static DECLARE_COMPLETION(relase_done);
|
||
|
||
/* 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,
|
||
};
|
||
|
||
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;
|
||
}
|
||
/* 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(&doorbell_end);
|
||
// 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("logo"); // 名称
|
||
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;
|
||
|
||
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*2(7680*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(&doorbell_end);
|
||
// 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:
|
||
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, ®_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);
|
||
}
|
||
|
||
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(&doorbell_end);
|
||
wait_for_completion_interruptible(&relase_done);
|
||
if (g_pdev != NULL)
|
||
{
|
||
return -EINVAL;
|
||
}
|
||
|
||
break;
|
||
}
|
||
case DOOLBELL_GET_STATUS: {
|
||
/* 用户态应该轮询查询状态 */
|
||
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;
|
||
|
||
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");
|